diff options
author | rockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-20 20:51:13 +0000 |
---|---|---|
committer | rockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-20 20:51:13 +0000 |
commit | 058e5c7415c391e2641cdc30792d8de672bf4355 (patch) | |
tree | c0e7489f90cdfd388585cf5aca3591e9b142e684 /chrome | |
parent | dfb0f4eb3ed201d428ed75a9afc1c09f43bb4663 (diff) | |
download | chromium_src-058e5c7415c391e2641cdc30792d8de672bf4355.zip chromium_src-058e5c7415c391e2641cdc30792d8de672bf4355.tar.gz chromium_src-058e5c7415c391e2641cdc30792d8de672bf4355.tar.bz2 |
Update serial API.
Undoing revert from r236265.
R=miket@chromium.org, rpaquay@chromium.org
TBR=rch@chromium.org
BUG=155861,148741,140125,169555,171948,281908
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=236252
Review URL: https://codereview.chromium.org/27246008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@236283 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
22 files changed, 2387 insertions, 799 deletions
diff --git a/chrome/browser/extensions/api/api_resource_manager.h b/chrome/browser/extensions/api/api_resource_manager.h index 5596cfa..b459c22 100644 --- a/chrome/browser/extensions/api/api_resource_manager.h +++ b/chrome/browser/extensions/api/api_resource_manager.h @@ -24,6 +24,7 @@ namespace extensions { namespace api { +class SerialEventDispatcher; class TCPServerSocketEventDispatcher; class TCPSocketEventDispatcher; class UDPSocketEventDispatcher; @@ -154,6 +155,7 @@ class ApiResourceManager : public ProfileKeyedAPI, } private: + friend class api::SerialEventDispatcher; friend class api::TCPServerSocketEventDispatcher; friend class api::TCPSocketEventDispatcher; friend class api::UDPSocketEventDispatcher; diff --git a/chrome/browser/extensions/api/serial/OWNERS b/chrome/browser/extensions/api/serial/OWNERS index 452ae4e..29f7cfb 100644 --- a/chrome/browser/extensions/api/serial/OWNERS +++ b/chrome/browser/extensions/api/serial/OWNERS @@ -1,3 +1,4 @@ ikarienator@chromium.org miket@chromium.org +rockot@chromium.org rpaquay@chromium.org diff --git a/chrome/browser/extensions/api/serial/serial_api.cc b/chrome/browser/extensions/api/serial/serial_api.cc index a15f7e1..ebf2a66 100644 --- a/chrome/browser/extensions/api/serial/serial_api.cc +++ b/chrome/browser/extensions/api/serial/serial_api.cc @@ -4,41 +4,56 @@ #include "chrome/browser/extensions/api/serial/serial_api.h" +#include <algorithm> + #include "base/values.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/api/serial/serial_connection.h" +#include "chrome/browser/extensions/api/serial/serial_event_dispatcher.h" #include "chrome/browser/extensions/api/serial/serial_port_enumerator.h" +#include "chrome/common/extensions/api/serial.h" #include "content/public/browser/browser_thread.h" using content::BrowserThread; -namespace serial = extensions::api::serial; - namespace extensions { -const char kConnectionIdKey[] = "connectionId"; -const char kDataKey[] = "data"; -const char kBytesReadKey[] = "bytesRead"; -const char kBytesWrittenKey[] = "bytesWritten"; -const char kBitrateKey[] = "bitrate"; -const char kDataBitKey[] = "dataBit"; -const char kParityKey[] = "parityBit"; -const char kStopBitKey[] = "stopBit"; -const char kSuccessKey[] = "success"; -const char kDcdKey[] = "dcd"; -const char kCtsKey[] = "cts"; +namespace api { +namespace { + +// It's a fool's errand to come up with a default bitrate, because we don't get +// to control both sides of the communication. Unless the other side has +// implemented auto-bitrate detection (rare), if we pick the wrong rate, then +// you're gonna have a bad time. Close doesn't count. +// +// But we'd like to pick something that has a chance of working, and 9600 is a +// good balance between popularity and speed. So 9600 it is. +const int kDefaultBufferSize = 4096; +const int kDefaultBitrate = 9600; +const serial::DataBits kDefaultDataBits = serial::DATA_BITS_EIGHT; +const serial::ParityBit kDefaultParityBit = serial::PARITY_BIT_NO; +const serial::StopBits kDefaultStopBits = serial::STOP_BITS_ONE; +const int kDefaultReceiveTimeout = 0; +const int kDefaultSendTimeout = 0; + +const char kErrorOpenFailed[] = "Failed to open the port."; +const char kErrorSerialConnectionNotFound[] = "Serial connection not found."; const char kErrorGetControlSignalsFailed[] = "Failed to get control signals."; -const char kErrorSetControlSignalsFailed[] = "Failed to set control signals."; -const char kSerialReadInvalidBytesToRead[] = "Number of bytes to read must " - "be a positive number less than 1,048,576."; + +template <class T> +void SetDefaultScopedPtrValue(scoped_ptr<T>& ptr, const T& value) { + if (!ptr.get()) + ptr.reset(new T(value)); +} + +} // namespace SerialAsyncApiFunction::SerialAsyncApiFunction() : manager_(NULL) { } -SerialAsyncApiFunction::~SerialAsyncApiFunction() { -} +SerialAsyncApiFunction::~SerialAsyncApiFunction() {} bool SerialAsyncApiFunction::PrePrepare() { manager_ = ApiResourceManager<SerialConnection>::Get(GetProfile()); @@ -46,6 +61,10 @@ bool SerialAsyncApiFunction::PrePrepare() { return true; } +bool SerialAsyncApiFunction::Respond() { + return error_.empty(); +} + SerialConnection* SerialAsyncApiFunction::GetSerialConnection( int api_resource_id) { return manager_->Get(extension_->id(), api_resource_id); @@ -55,365 +74,338 @@ void SerialAsyncApiFunction::RemoveSerialConnection(int api_resource_id) { manager_->Remove(extension_->id(), api_resource_id); } -SerialGetPortsFunction::SerialGetPortsFunction() {} +SerialGetDevicesFunction::SerialGetDevicesFunction() {} -bool SerialGetPortsFunction::Prepare() { +bool SerialGetDevicesFunction::Prepare() { set_work_thread_id(BrowserThread::FILE); return true; } -void SerialGetPortsFunction::Work() { +void SerialGetDevicesFunction::Work() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - base::ListValue* ports = new base::ListValue(); + std::vector<linked_ptr<serial::DeviceInfo> > devices; SerialPortEnumerator::StringSet port_names = SerialPortEnumerator::GenerateValidSerialPortNames(); - SerialPortEnumerator::StringSet::const_iterator i = port_names.begin(); - while (i != port_names.end()) { - ports->Append(new base::StringValue(*i++)); + for (SerialPortEnumerator::StringSet::const_iterator iter = + port_names.begin(); + iter != port_names.end(); + ++iter) { + linked_ptr<serial::DeviceInfo> info(new serial::DeviceInfo); + info->path = *iter; + devices.push_back(info); } - - SetResult(ports); + results_ = serial::GetDevices::Results::Create(devices); } -bool SerialGetPortsFunction::Respond() { - return true; -} - -// It's a fool's errand to come up with a default bitrate, because we don't get -// to control both sides of the communication. Unless the other side has -// implemented auto-bitrate detection (rare), if we pick the wrong rate, then -// you're gonna have a bad time. Close doesn't count. -// -// But we'd like to pick something that has a chance of working, and 9600 is a -// good balance between popularity and speed. So 9600 it is. -SerialOpenFunction::SerialOpenFunction() - : bitrate_(9600), databit_(serial::DATA_BIT_EIGHTBIT), - parity_(serial::PARITY_BIT_NOPARITY), - stopbit_(serial::STOP_BIT_ONESTOPBIT) { -} +SerialOpenFunction::SerialOpenFunction() {} -SerialOpenFunction::~SerialOpenFunction() { -} +SerialOpenFunction::~SerialOpenFunction() {} bool SerialOpenFunction::Prepare() { - set_work_thread_id(BrowserThread::FILE); - - params_ = api::serial::Open::Params::Create(*args_); + params_ = serial::Open::Params::Create(*args_); EXTENSION_FUNCTION_VALIDATE(params_.get()); - if (params_->options.get()) { - scoped_ptr<base::DictionaryValue> options = params_->options->ToValue(); - if (options->HasKey(kBitrateKey)) - EXTENSION_FUNCTION_VALIDATE(options->GetInteger(kBitrateKey, &bitrate_)); - if (options->HasKey(kDataBitKey)) { - std::string data; - options->GetString(kDataBitKey, &data); - if (!data.empty()) - databit_ = serial::ParseDataBit(data); - } - if (options->HasKey(kParityKey)) { - std::string parity; - options->GetString(kParityKey, &parity); - if (!parity.empty()) - parity_ = serial::ParseParityBit(parity); - } - if (options->HasKey(kStopBitKey)) { - std::string stopbit; - options->GetString(kStopBitKey, &stopbit); - if (!stopbit.empty()) - stopbit_ = serial::ParseStopBit(stopbit); - } - } + // Fill in any omitted options to ensure a known initial configuration. + if (!params_->options.get()) + params_->options.reset(new serial::ConnectionOptions()); + serial::ConnectionOptions* options = params_->options.get(); + + SetDefaultScopedPtrValue(options->persistent, false); + SetDefaultScopedPtrValue(options->buffer_size, kDefaultBufferSize); + SetDefaultScopedPtrValue(options->bitrate, kDefaultBitrate); + SetDefaultScopedPtrValue(options->cts_flow_control, false); + SetDefaultScopedPtrValue(options->receive_timeout, kDefaultReceiveTimeout); + SetDefaultScopedPtrValue(options->send_timeout, kDefaultSendTimeout); + + if (options->data_bits == serial::DATA_BITS_NONE) + options->data_bits = kDefaultDataBits; + if (options->parity_bit == serial::PARITY_BIT_NONE) + options->parity_bit = kDefaultParityBit; + if (options->stop_bits == serial::STOP_BITS_NONE) + options->stop_bits = kDefaultStopBits; + + serial_event_dispatcher_ = SerialEventDispatcher::Get(GetProfile()); + DCHECK(serial_event_dispatcher_); return true; } void SerialOpenFunction::AsyncWorkStart() { - Work(); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + connection_ = CreateSerialConnection(params_->path, extension_->id()); + connection_->Open(base::Bind(&SerialOpenFunction::OnOpen, this)); } -void SerialOpenFunction::Work() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - const SerialPortEnumerator::StringSet name_set( - SerialPortEnumerator::GenerateValidSerialPortNames()); - if (DoesPortExist(params_->port)) { - SerialConnection* serial_connection = CreateSerialConnection( - params_->port, - bitrate_, - databit_, - parity_, - stopbit_, - extension_->id()); - CHECK(serial_connection); - int id = manager_->Add(serial_connection); - CHECK(id); - - bool open_result = serial_connection->Open(); - if (!open_result) { - serial_connection->Close(); - RemoveSerialConnection(id); - id = -1; - } +void SerialOpenFunction::OnOpen(bool success) { + DCHECK(connection_); - base::DictionaryValue* result = new base::DictionaryValue(); - result->SetInteger(kConnectionIdKey, id); - SetResult(result); - AsyncWorkCompleted(); + if (success) { + if (!connection_->Configure(*params_->options.get())) { + connection_->Close(); + delete connection_; + connection_ = NULL; + } } else { - base::DictionaryValue* result = new base::DictionaryValue(); - result->SetInteger(kConnectionIdKey, -1); - SetResult(result); - AsyncWorkCompleted(); + delete connection_; + connection_ = NULL; } -} -SerialConnection* SerialOpenFunction::CreateSerialConnection( - const std::string& port, - int bitrate, - serial::DataBit databit, - serial::ParityBit parity, - serial::StopBit stopbit, - const std::string& owner_extension_id) { - return new SerialConnection(port, bitrate, databit, parity, stopbit, - owner_extension_id); + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&SerialOpenFunction::FinishOpen, this)); } -bool SerialOpenFunction::DoesPortExist(const std::string& port) { - const SerialPortEnumerator::StringSet name_set( - SerialPortEnumerator::GenerateValidSerialPortNames()); - return SerialPortEnumerator::DoesPortExist(name_set, params_->port); -} +void SerialOpenFunction::FinishOpen() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (!connection_) { + error_ = kErrorOpenFailed; + } else { + int id = manager_->Add(connection_); + serial_event_dispatcher_->PollConnection(extension_->id(), id); -bool SerialOpenFunction::Respond() { - return true; + serial::OpenInfo open_info; + open_info.connection_id = id; + results_ = serial::Open::Results::Create(open_info); + } + AsyncWorkCompleted(); } -SerialCloseFunction::SerialCloseFunction() { +SerialConnection* SerialOpenFunction::CreateSerialConnection( + const std::string& port, const std::string& extension_id) const { + return new SerialConnection(port, extension_id); } -SerialCloseFunction::~SerialCloseFunction() { -} +SerialUpdateFunction::SerialUpdateFunction() {} -bool SerialCloseFunction::Prepare() { - set_work_thread_id(BrowserThread::FILE); +SerialUpdateFunction::~SerialUpdateFunction() {} - params_ = api::serial::Close::Params::Create(*args_); +bool SerialUpdateFunction::Prepare() { + params_ = serial::Update::Params::Create(*args_); EXTENSION_FUNCTION_VALIDATE(params_.get()); return true; } -void SerialCloseFunction::Work() { - bool close_result = false; - SerialConnection* serial_connection = GetSerialConnection( - params_->connection_id); - if (serial_connection) { - serial_connection->Close(); - RemoveSerialConnection(params_->connection_id); - close_result = true; +void SerialUpdateFunction::Work() { + SerialConnection* connection = GetSerialConnection(params_->connection_id); + if (!connection) { + error_ = kErrorSerialConnectionNotFound; + return; } - - SetResult(new base::FundamentalValue(close_result)); + bool success = connection->Configure(params_->options); + results_ = serial::Update::Results::Create(success); } -bool SerialCloseFunction::Respond() { +SerialCloseFunction::SerialCloseFunction() {} + +SerialCloseFunction::~SerialCloseFunction() {} + +bool SerialCloseFunction::Prepare() { + params_ = serial::Close::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; } -SerialReadFunction::SerialReadFunction() { +void SerialCloseFunction::Work() { + SerialConnection* connection = GetSerialConnection(params_->connection_id); + if (!connection) { + error_ = kErrorSerialConnectionNotFound; + return; + } + connection->Close(); + RemoveSerialConnection(params_->connection_id); + results_ = serial::Close::Results::Create(true); } -SerialReadFunction::~SerialReadFunction() { -} +SerialSendFunction::SerialSendFunction() {} -bool SerialReadFunction::Prepare() { - set_work_thread_id(BrowserThread::FILE); +SerialSendFunction::~SerialSendFunction() {} - params_ = api::serial::Read::Params::Create(*args_); +bool SerialSendFunction::Prepare() { + params_ = serial::Send::Params::Create(*args_); EXTENSION_FUNCTION_VALIDATE(params_.get()); - if (params_->bytes_to_read <= 0 || params_->bytes_to_read >= 1024 * 1024) { - error_ = kSerialReadInvalidBytesToRead; - return false; - } return true; } -void SerialReadFunction::Work() { - int bytes_read = -1; - scoped_refptr<net::IOBufferWithSize> io_buffer( - new net::IOBufferWithSize(params_->bytes_to_read)); - SerialConnection* serial_connection(GetSerialConnection( - params_->connection_id)); - - if (serial_connection) - bytes_read = serial_connection->Read(io_buffer); +void SerialSendFunction::AsyncWorkStart() { + SerialConnection* connection = GetSerialConnection(params_->connection_id); + if (!connection) { + error_ = kErrorSerialConnectionNotFound; + AsyncWorkCompleted(); + return; + } - base::DictionaryValue* result = new base::DictionaryValue(); + if (!connection->Send(params_->data, + base::Bind(&SerialSendFunction::OnSendComplete, + this))) { + OnSendComplete(0, serial::SEND_ERROR_PENDING); + } +} - // The API is defined to require a 'data' value, so we will always - // create a BinaryValue, even if it's zero-length. - if (bytes_read < 0) - bytes_read = 0; - result->SetInteger(kBytesReadKey, bytes_read); - result->Set(kDataKey, base::BinaryValue::CreateWithCopiedBuffer( - io_buffer->data(), bytes_read)); - SetResult(result); +void SerialSendFunction::OnSendComplete(int bytes_sent, + serial::SendError error) { + serial::SendInfo send_info; + send_info.bytes_sent = bytes_sent; + send_info.error = error; + results_ = serial::Send::Results::Create(send_info); + AsyncWorkCompleted(); } -bool SerialReadFunction::Respond() { +SerialFlushFunction::SerialFlushFunction() {} + +SerialFlushFunction::~SerialFlushFunction() {} + +bool SerialFlushFunction::Prepare() { + params_ = serial::Flush::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); return true; } -SerialWriteFunction::SerialWriteFunction() - : io_buffer_(NULL), io_buffer_size_(0) { -} +void SerialFlushFunction::Work() { + SerialConnection* connection = GetSerialConnection(params_->connection_id); + if (!connection) { + error_ = kErrorSerialConnectionNotFound; + return; + } -SerialWriteFunction::~SerialWriteFunction() { + bool success = connection->Flush(); + results_ = serial::Flush::Results::Create(success); } -bool SerialWriteFunction::Prepare() { - set_work_thread_id(BrowserThread::FILE); +SerialSetPausedFunction::SerialSetPausedFunction() {} - params_ = api::serial::Write::Params::Create(*args_); - EXTENSION_FUNCTION_VALIDATE(params_.get()); +SerialSetPausedFunction::~SerialSetPausedFunction() {} - io_buffer_size_ = params_->data.size(); - io_buffer_ = new net::WrappedIOBuffer(params_->data.data()); +bool SerialSetPausedFunction::Prepare() { + params_ = serial::SetPaused::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + serial_event_dispatcher_ = SerialEventDispatcher::Get(GetProfile()); + DCHECK(serial_event_dispatcher_); return true; } -void SerialWriteFunction::Work() { - int bytes_written = -1; - SerialConnection* serial_connection = GetSerialConnection( - params_->connection_id); - if (serial_connection) - bytes_written = serial_connection->Write(io_buffer_, io_buffer_size_); - else - error_ = kSerialConnectionNotFoundError; - - base::DictionaryValue* result = new base::DictionaryValue(); - result->SetInteger(kBytesWrittenKey, bytes_written); - SetResult(result); -} +void SerialSetPausedFunction::Work() { + SerialConnection* connection = GetSerialConnection(params_->connection_id); + if (!connection) { + error_ = kErrorSerialConnectionNotFound; + return; + } -bool SerialWriteFunction::Respond() { - return true; -} + if (params_->paused != connection->paused()) { + connection->set_paused(params_->paused); + if (!params_->paused) { + serial_event_dispatcher_->PollConnection(extension_->id(), + params_->connection_id); + } + } -SerialFlushFunction::SerialFlushFunction() { + results_ = serial::SetPaused::Results::Create(); } -SerialFlushFunction::~SerialFlushFunction() { -} +SerialGetInfoFunction::SerialGetInfoFunction() {} -bool SerialFlushFunction::Prepare() { - set_work_thread_id(BrowserThread::FILE); +SerialGetInfoFunction::~SerialGetInfoFunction() {} - params_ = api::serial::Flush::Params::Create(*args_); +bool SerialGetInfoFunction::Prepare() { + params_ = serial::GetInfo::Params::Create(*args_); EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; } -void SerialFlushFunction::Work() { - bool flush_result = false; - SerialConnection* serial_connection = GetSerialConnection( - params_->connection_id); - if (serial_connection) { - serial_connection->Flush(); - flush_result = true; +void SerialGetInfoFunction::Work() { + SerialConnection* connection = GetSerialConnection(params_->connection_id); + if (!connection) { + error_ = kErrorSerialConnectionNotFound; + return; } - SetResult(new base::FundamentalValue(flush_result)); + serial::ConnectionInfo info; + info.connection_id = params_->connection_id; + connection->GetInfo(&info); + results_ = serial::GetInfo::Results::Create(info); } -bool SerialFlushFunction::Respond() { +SerialGetConnectionsFunction::SerialGetConnectionsFunction() {} + +SerialGetConnectionsFunction::~SerialGetConnectionsFunction() {} + +bool SerialGetConnectionsFunction::Prepare() { return true; } -SerialGetControlSignalsFunction::SerialGetControlSignalsFunction() - : api_response_(false) { +void SerialGetConnectionsFunction::Work() { + std::vector<linked_ptr<serial::ConnectionInfo> > infos; + const base::hash_set<int>* connection_ids = manager_->GetResourceIds( + extension_->id()); + if (connection_ids) { + for (base::hash_set<int>::const_iterator it = connection_ids->begin(); + it != connection_ids->end(); ++it) { + int connection_id = *it; + SerialConnection *connection = GetSerialConnection(connection_id); + if (connection) { + linked_ptr<serial::ConnectionInfo> info(new serial::ConnectionInfo()); + info->connection_id = connection_id; + connection->GetInfo(info.get()); + infos.push_back(info); + } + } + } + results_ = serial::GetConnections::Results::Create(infos); } -SerialGetControlSignalsFunction::~SerialGetControlSignalsFunction() { -} +SerialGetControlSignalsFunction::SerialGetControlSignalsFunction() {} -bool SerialGetControlSignalsFunction::Prepare() { - set_work_thread_id(BrowserThread::FILE); +SerialGetControlSignalsFunction::~SerialGetControlSignalsFunction() {} - params_ = api::serial::GetControlSignals::Params::Create(*args_); +bool SerialGetControlSignalsFunction::Prepare() { + params_ = serial::GetControlSignals::Params::Create(*args_); EXTENSION_FUNCTION_VALIDATE(params_.get()); return true; } void SerialGetControlSignalsFunction::Work() { - base::DictionaryValue *result = new base::DictionaryValue(); - SerialConnection* serial_connection = GetSerialConnection( - params_->connection_id); - if (serial_connection) { - SerialConnection::ControlSignals control_signals = { 0 }; - if (serial_connection->GetControlSignals(control_signals)) { - api_response_ = true; - result->SetBoolean(kDcdKey, control_signals.dcd); - result->SetBoolean(kCtsKey, control_signals.cts); - } else { - error_ = kErrorGetControlSignalsFailed; - } - } else { - error_ = kSerialConnectionNotFoundError; - result->SetBoolean(kSuccessKey, false); + SerialConnection* connection = GetSerialConnection(params_->connection_id); + if (!connection) { + error_ = kErrorSerialConnectionNotFound; + return; } - SetResult(result); -} + serial::ControlSignals signals; + if (!connection->GetControlSignals(&signals)) { + error_ = kErrorGetControlSignalsFailed; + return; + } -bool SerialGetControlSignalsFunction::Respond() { - return api_response_; + results_ = serial::GetControlSignals::Results::Create(signals); } -SerialSetControlSignalsFunction::SerialSetControlSignalsFunction() { -} +SerialSetControlSignalsFunction::SerialSetControlSignalsFunction() {} -SerialSetControlSignalsFunction::~SerialSetControlSignalsFunction() { -} +SerialSetControlSignalsFunction::~SerialSetControlSignalsFunction() {} bool SerialSetControlSignalsFunction::Prepare() { - set_work_thread_id(BrowserThread::FILE); - - params_ = api::serial::SetControlSignals::Params::Create(*args_); + params_ = serial::SetControlSignals::Params::Create(*args_); EXTENSION_FUNCTION_VALIDATE(params_.get()); return true; } void SerialSetControlSignalsFunction::Work() { - SerialConnection* serial_connection = GetSerialConnection( - params_->connection_id); - if (serial_connection) { - SerialConnection::ControlSignals control_signals = { 0 }; - control_signals.should_set_dtr = params_->options.dtr.get() != NULL; - if (control_signals.should_set_dtr) - control_signals.dtr = *(params_->options.dtr); - control_signals.should_set_rts = params_->options.rts.get() != NULL; - if (control_signals.should_set_rts) - control_signals.rts = *(params_->options.rts); - if (serial_connection->SetControlSignals(control_signals)) { - SetResult(new base::FundamentalValue(true)); - } else { - error_ = kErrorSetControlSignalsFailed; - SetResult(new base::FundamentalValue(false)); - } - } else { - error_ = kSerialConnectionNotFoundError; - SetResult(new base::FundamentalValue(false)); + SerialConnection* connection = GetSerialConnection(params_->connection_id); + if (!connection) { + error_ = kErrorSerialConnectionNotFound; + return; } -} -bool SerialSetControlSignalsFunction::Respond() { - return true; + bool success = connection->SetControlSignals(params_->signals); + results_ = serial::SetControlSignals::Results::Create(success); } +} // namespace api + } // namespace extensions diff --git a/chrome/browser/extensions/api/serial/serial_api.h b/chrome/browser/extensions/api/serial/serial_api.h index 6798ab5..4bd6475 100644 --- a/chrome/browser/extensions/api/serial/serial_api.h +++ b/chrome/browser/extensions/api/serial/serial_api.h @@ -11,15 +11,14 @@ #include "chrome/browser/extensions/api/api_function.h" #include "chrome/browser/extensions/api/api_resource_manager.h" #include "chrome/common/extensions/api/serial.h" -#include "net/base/io_buffer.h" - -namespace serial = extensions::api::serial; namespace extensions { class SerialConnection; -extern const char kConnectionIdKey[]; +namespace api { + +class SerialEventDispatcher; class SerialAsyncApiFunction : public AsyncApiFunction { public: @@ -30,6 +29,7 @@ class SerialAsyncApiFunction : public AsyncApiFunction { // AsyncApiFunction: virtual bool PrePrepare() OVERRIDE; + virtual bool Respond() OVERRIDE; SerialConnection* GetSerialConnection(int api_resource_id); void RemoveSerialConnection(int api_resource_id); @@ -37,19 +37,18 @@ class SerialAsyncApiFunction : public AsyncApiFunction { ApiResourceManager<SerialConnection>* manager_; }; -class SerialGetPortsFunction : public SerialAsyncApiFunction { +class SerialGetDevicesFunction : public SerialAsyncApiFunction { public: - DECLARE_EXTENSION_FUNCTION("serial.getPorts", SERIAL_GETPORTS) + DECLARE_EXTENSION_FUNCTION("serial.getDevices", SERIAL_GETDEVICES) - SerialGetPortsFunction(); + SerialGetDevicesFunction(); protected: - virtual ~SerialGetPortsFunction() {} + virtual ~SerialGetDevicesFunction() {} // AsyncApiFunction: virtual bool Prepare() OVERRIDE; virtual void Work() OVERRIDE; - virtual bool Respond() OVERRIDE; }; class SerialOpenFunction : public SerialAsyncApiFunction { @@ -64,26 +63,42 @@ class SerialOpenFunction : public SerialAsyncApiFunction { // AsyncApiFunction: virtual bool Prepare() OVERRIDE; virtual void AsyncWorkStart() OVERRIDE; - virtual void Work() OVERRIDE; - virtual bool Respond() OVERRIDE; - // Overrideable for testing. virtual SerialConnection* CreateSerialConnection( const std::string& port, - int bitrate, - serial::DataBit databit, - serial::ParityBit parity, - serial::StopBit stopbit, - const std::string& owner_extension_id); + const std::string& extension_id) const; - virtual bool DoesPortExist(const std::string& port); + private: + void OnOpen(bool success); + void FinishOpen(); + + scoped_ptr<serial::Open::Params> params_; + + // SerialEventDispatcher is owned by a Profile. + SerialEventDispatcher* serial_event_dispatcher_; + + // This connection is created within SerialOpenFunction. + // From there it is either destroyed in OnOpen (upon failure) + // or its ownership is transferred to the profile's. + // ApiResourceManager<SerialConnection>. + SerialConnection* connection_; +}; + +class SerialUpdateFunction : public SerialAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("serial.update", SERIAL_UPDATE); + + SerialUpdateFunction(); + + protected: + virtual ~SerialUpdateFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; private: - scoped_ptr<api::serial::Open::Params> params_; - int bitrate_; - api::serial::DataBit databit_; - api::serial::ParityBit parity_; - api::serial::StopBit stopbit_; + scoped_ptr<serial::Update::Params> params_; }; class SerialCloseFunction : public SerialAsyncApiFunction { @@ -98,48 +113,77 @@ class SerialCloseFunction : public SerialAsyncApiFunction { // AsyncApiFunction: virtual bool Prepare() OVERRIDE; virtual void Work() OVERRIDE; - virtual bool Respond() OVERRIDE; private: - scoped_ptr<api::serial::Close::Params> params_; + scoped_ptr<serial::Close::Params> params_; }; -class SerialReadFunction : public SerialAsyncApiFunction { +class SerialSetPausedFunction : public SerialAsyncApiFunction { public: - DECLARE_EXTENSION_FUNCTION("serial.read", SERIAL_READ) + DECLARE_EXTENSION_FUNCTION("serial.setPaused", SERIAL_SETPAUSED) - SerialReadFunction(); + SerialSetPausedFunction(); protected: - virtual ~SerialReadFunction(); + virtual ~SerialSetPausedFunction(); // AsyncApiFunction: virtual bool Prepare() OVERRIDE; virtual void Work() OVERRIDE; - virtual bool Respond() OVERRIDE; private: - scoped_ptr<api::serial::Read::Params> params_; + scoped_ptr<serial::SetPaused::Params> params_; + SerialEventDispatcher* serial_event_dispatcher_; }; -class SerialWriteFunction : public SerialAsyncApiFunction { +class SerialGetInfoFunction : public SerialAsyncApiFunction { public: - DECLARE_EXTENSION_FUNCTION("serial.write", SERIAL_WRITE) + DECLARE_EXTENSION_FUNCTION("serial.getInfo", SERIAL_GETINFO) - SerialWriteFunction(); + SerialGetInfoFunction(); protected: - virtual ~SerialWriteFunction(); + virtual ~SerialGetInfoFunction(); // AsyncApiFunction: virtual bool Prepare() OVERRIDE; virtual void Work() OVERRIDE; - virtual bool Respond() OVERRIDE; private: - scoped_ptr<api::serial::Write::Params> params_; - scoped_refptr<net::IOBuffer> io_buffer_; - size_t io_buffer_size_; + scoped_ptr<serial::GetInfo::Params> params_; +}; + +class SerialGetConnectionsFunction : public SerialAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("serial.getConnections", SERIAL_GETCONNECTIONS); + + SerialGetConnectionsFunction(); + + protected: + virtual ~SerialGetConnectionsFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; +}; + +class SerialSendFunction : public SerialAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("serial.send", SERIAL_SEND) + + SerialSendFunction(); + + protected: + virtual ~SerialSendFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void AsyncWorkStart() OVERRIDE; + + private: + void OnSendComplete(int bytes_sent, serial::SendError error); + + scoped_ptr<serial::Send::Params> params_; }; class SerialFlushFunction : public SerialAsyncApiFunction { @@ -154,10 +198,9 @@ class SerialFlushFunction : public SerialAsyncApiFunction { // AsyncApiFunction: virtual bool Prepare() OVERRIDE; virtual void Work() OVERRIDE; - virtual bool Respond() OVERRIDE; private: - scoped_ptr<api::serial::Flush::Params> params_; + scoped_ptr<serial::Flush::Params> params_; }; class SerialGetControlSignalsFunction : public SerialAsyncApiFunction { @@ -173,11 +216,9 @@ class SerialGetControlSignalsFunction : public SerialAsyncApiFunction { // AsyncApiFunction: virtual bool Prepare() OVERRIDE; virtual void Work() OVERRIDE; - virtual bool Respond() OVERRIDE; private: - scoped_ptr<api::serial::GetControlSignals::Params> params_; - bool api_response_; + scoped_ptr<serial::GetControlSignals::Params> params_; }; class SerialSetControlSignalsFunction : public SerialAsyncApiFunction { @@ -193,12 +234,13 @@ class SerialSetControlSignalsFunction : public SerialAsyncApiFunction { // AsyncApiFunction: virtual bool Prepare() OVERRIDE; virtual void Work() OVERRIDE; - virtual bool Respond() OVERRIDE; private: - scoped_ptr<api::serial::SetControlSignals::Params> params_; + scoped_ptr<serial::SetControlSignals::Params> params_; }; +} // namespace api + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_API_H_ diff --git a/chrome/browser/extensions/api/serial/serial_apitest.cc b/chrome/browser/extensions/api/serial/serial_apitest.cc index 43b44b2..30165ac 100644 --- a/chrome/browser/extensions/api/serial/serial_apitest.cc +++ b/chrome/browser/extensions/api/serial/serial_apitest.cc @@ -13,6 +13,7 @@ #include "chrome/browser/extensions/extension_function_test_utils.h" #include "chrome/browser/extensions/extension_test_message_listener.h" #include "chrome/browser/ui/browser.h" +#include "chrome/common/extensions/api/serial.h" #include "content/public/browser/browser_thread.h" #include "extensions/browser/extension_function.h" #include "testing/gmock/include/gmock/gmock.h" @@ -22,8 +23,6 @@ using testing::Return; using content::BrowserThread; -namespace serial = extensions::api::serial; - namespace { class SerialApiTest : public ExtensionApiTest { @@ -35,42 +34,43 @@ class SerialApiTest : public ExtensionApiTest { namespace extensions { -class FakeSerialGetPortsFunction : public AsyncExtensionFunction { +class FakeSerialGetDevicesFunction : public AsyncExtensionFunction { public: virtual bool RunImpl() OVERRIDE { - base::ListValue* ports = new base::ListValue(); - ports->Append(Value::CreateStringValue("/dev/fakeserial")); - ports->Append(Value::CreateStringValue("\\\\COM800\\")); - SetResult(ports); + base::ListValue* devices = new base::ListValue(); + base::DictionaryValue* device0 = new base::DictionaryValue(); + device0->SetString("path", "/dev/fakeserial"); + base::DictionaryValue* device1 = new base::DictionaryValue(); + device1->SetString("path", "\\\\COM800\\"); + devices->Append(device0); + devices->Append(device1); + SetResult(devices); SendResponse(true); return true; } protected: - virtual ~FakeSerialGetPortsFunction() {} + virtual ~FakeSerialGetDevicesFunction() {} }; class FakeEchoSerialConnection : public SerialConnection { public: explicit FakeEchoSerialConnection( const std::string& port, - int bitrate, - serial::DataBit databit, - serial::ParityBit parity, - serial::StopBit stopbit, const std::string& owner_extension_id) - : SerialConnection(port, bitrate, databit, parity, stopbit, - owner_extension_id), - opened_(true) { - Flush(); - opened_ = false; + : SerialConnection(port, owner_extension_id), + opened_(false) { } virtual ~FakeEchoSerialConnection() { } - virtual bool Open() { + virtual void Open(const OpenCompleteCallback& callback) { DCHECK(!opened_); opened_ = true; + callback.Run(true); + } + + virtual bool Configure(const api::serial::ConnectionOptions& options) { return true; } @@ -78,68 +78,48 @@ class FakeEchoSerialConnection : public SerialConnection { DCHECK(opened_); } - virtual void Flush() { - DCHECK(opened_); - buffer_.clear(); + virtual bool Receive(const ReceiveCompleteCallback& callback) { + read_callback_ = callback; + return true; } - virtual int Read(scoped_refptr<net::IOBufferWithSize> io_buffer) { - DCHECK(io_buffer->data()); - - if (buffer_.empty()) { - return 0; - } - char *data = io_buffer->data(); - int bytes_to_copy = io_buffer->size(); - while (bytes_to_copy-- && !buffer_.empty()) { - *data++ = buffer_.front(); - buffer_.pop_front(); + virtual bool Send(const std::string& data, + const SendCompleteCallback& callback) { + callback.Run(data.length(), api::serial::SEND_ERROR_NONE); + if (!read_callback_.is_null()) { + read_callback_.Run(data, api::serial::RECEIVE_ERROR_NONE); } - return io_buffer->size(); + return true; } - virtual int Write(scoped_refptr<net::IOBuffer> io_buffer, int byte_count) { - DCHECK(io_buffer.get()); - DCHECK_GE(byte_count, 0); - - char *data = io_buffer->data(); - int count = byte_count; - while (count--) - buffer_.push_back(*data++); - return byte_count; + virtual bool GetControlSignals(api::serial::ControlSignals* signals) const { + signals->dcd.reset(new bool(true)); + signals->cts.reset(new bool(true)); + signals->dtr.reset(new bool(true)); + signals->ri.reset(new bool(true)); + return true; } - MOCK_METHOD1(GetControlSignals, bool(ControlSignals &)); - MOCK_METHOD1(SetControlSignals, bool(const ControlSignals &)); + MOCK_METHOD1(SetControlSignals, bool(const api::serial::ControlSignals&)); private: bool opened_; - std::deque<char> buffer_; + ReceiveCompleteCallback read_callback_; DISALLOW_COPY_AND_ASSIGN(FakeEchoSerialConnection); }; -class FakeSerialOpenFunction : public SerialOpenFunction { +class FakeSerialOpenFunction : public api::SerialOpenFunction { protected: virtual SerialConnection* CreateSerialConnection( const std::string& port, - int bitrate, - serial::DataBit databit, - serial::ParityBit parity, - serial::StopBit stopbit, - const std::string& owner_extension_id) OVERRIDE { + const std::string& owner_extension_id) const OVERRIDE { FakeEchoSerialConnection* serial_connection = - new FakeEchoSerialConnection(port, bitrate, databit, parity, stopbit, - owner_extension_id); - EXPECT_CALL(*serial_connection, GetControlSignals(_)). - Times(1).WillOnce(Return(true)); + new FakeEchoSerialConnection(port, owner_extension_id); EXPECT_CALL(*serial_connection, SetControlSignals(_)). Times(1).WillOnce(Return(true)); return serial_connection; } - virtual bool DoesPortExist(const std::string& port) OVERRIDE { - return true; - } protected: virtual ~FakeSerialOpenFunction() {} @@ -147,8 +127,8 @@ class FakeSerialOpenFunction : public SerialOpenFunction { } // namespace extensions -ExtensionFunction* FakeSerialGetPortsFunctionFactory() { - return new extensions::FakeSerialGetPortsFunction(); +ExtensionFunction* FakeSerialGetDevicesFunctionFactory() { + return new extensions::FakeSerialGetDevicesFunction(); } ExtensionFunction* FakeSerialOpenFunctionFactory() { @@ -181,8 +161,8 @@ IN_PROC_BROWSER_TEST_F(SerialApiTest, SerialFakeHardware) { #if SIMULATE_SERIAL_PORTS ASSERT_TRUE(ExtensionFunctionDispatcher::OverrideFunction( - "serial.getPorts", - FakeSerialGetPortsFunctionFactory)); + "serial.getDevices", + FakeSerialGetDevicesFunctionFactory)); ASSERT_TRUE(ExtensionFunctionDispatcher::OverrideFunction( "serial.open", FakeSerialOpenFunctionFactory)); diff --git a/chrome/browser/extensions/api/serial/serial_connection.cc b/chrome/browser/extensions/api/serial/serial_connection.cc index aed373c..66e19cf 100644 --- a/chrome/browser/extensions/api/serial/serial_connection.cc +++ b/chrome/browser/extensions/api/serial/serial_connection.cc @@ -8,15 +8,19 @@ #include "base/files/file_path.h" #include "base/lazy_instance.h" +#include "base/platform_file.h" #include "base/strings/string_util.h" #include "chrome/browser/extensions/api/api_resource_manager.h" +#include "chrome/browser/extensions/api/serial/serial_port_enumerator.h" #include "chrome/common/extensions/api/serial.h" -namespace serial = extensions::api::serial; - namespace extensions { -const char kSerialConnectionNotFoundError[] = "Serial connection not found"; +namespace { + +const int kDefaultBufferSize = 4096; + +} static base::LazyInstance<ProfileKeyedAPIFactory< ApiResourceManager<SerialConnection> > > @@ -29,65 +33,242 @@ ApiResourceManager<SerialConnection>::GetFactoryInstance() { return &g_factory.Get(); } -SerialConnection::SerialConnection(const std::string& port, int bitrate, - serial::DataBit databit, - serial::ParityBit parity, - serial::StopBit stopbit, +SerialConnection::SerialConnection(const std::string& port, const std::string& owner_extension_id) - : ApiResource(owner_extension_id), port_(port), bitrate_(bitrate), - databit_(databit), parity_(parity), stopbit_(stopbit), - file_(base::kInvalidPlatformFileValue) { - CHECK_GE(bitrate, 0); + : ApiResource(owner_extension_id), + port_(port), + file_(base::kInvalidPlatformFileValue), + persistent_(false), + buffer_size_(kDefaultBufferSize), + receive_timeout_(0), + send_timeout_(0), + paused_(false), + io_handler_(SerialIoHandler::Create()) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); } SerialConnection::~SerialConnection() { + DCHECK(open_complete_.is_null()); + io_handler_->CancelRead(api::serial::RECEIVE_ERROR_CLOSED); + io_handler_->CancelWrite(api::serial::SEND_ERROR_CLOSED); Close(); } -bool SerialConnection::Open() { - bool created = false; +bool SerialConnection::IsPersistent() const { + return persistent(); +} - // It's the responsibility of the API wrapper around SerialConnection to - // validate the supplied path against the set of valid port names, and - // it is a reasonable assumption that serial port names are ASCII. - CHECK(IsStringASCII(port_)); - base::FilePath file_path( - base::FilePath::FromUTF8Unsafe(MaybeFixUpPortName(port_))); +void SerialConnection::set_buffer_size(int buffer_size) { + buffer_size_ = buffer_size; +} - file_ = base::CreatePlatformFile(file_path, - base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ | - base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_EXCLUSIVE_READ | - base::PLATFORM_FILE_EXCLUSIVE_WRITE | - base::PLATFORM_FILE_TERMINAL_DEVICE, &created, NULL); - if (file_ == base::kInvalidPlatformFileValue) { - return false; +void SerialConnection::set_receive_timeout(int receive_timeout) { + receive_timeout_ = receive_timeout; +} + +void SerialConnection::set_send_timeout(int send_timeout) { + send_timeout_ = send_timeout; +} + +void SerialConnection::set_paused(bool paused) { + paused_ = paused; + if (paused) { + io_handler_->CancelRead(api::serial::RECEIVE_ERROR_NONE); } - return PostOpen(); +} + +void SerialConnection::Open(const OpenCompleteCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(open_complete_.is_null()); + open_complete_ = callback; + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&SerialConnection::StartOpen, base::Unretained(this))); } void SerialConnection::Close() { + DCHECK(open_complete_.is_null()); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (file_ != base::kInvalidPlatformFileValue) { - base::ClosePlatformFile(file_); + base::PlatformFile file = file_; file_ = base::kInvalidPlatformFileValue; + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, + base::Bind(&SerialConnection::DoClose, file)); } } -int SerialConnection::Read(scoped_refptr<net::IOBufferWithSize> io_buffer) { - DCHECK(io_buffer->data()); - return base::ReadPlatformFileAtCurrentPos(file_, io_buffer->data(), - io_buffer->size()); +bool SerialConnection::Receive(const ReceiveCompleteCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (!receive_complete_.is_null()) + return false; + receive_complete_ = callback; + io_handler_->Read(buffer_size_); + receive_timeout_task_.reset(); + if (receive_timeout_ > 0) { + receive_timeout_task_.reset(new TimeoutTask( + base::Bind(&SerialConnection::OnReceiveTimeout, AsWeakPtr()), + base::TimeDelta::FromMilliseconds(receive_timeout_))); + } + return true; +} + +bool SerialConnection::Send(const std::string& data, + const SendCompleteCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (!send_complete_.is_null()) + return false; + send_complete_ = callback; + io_handler_->Write(data); + send_timeout_task_.reset(); + if (send_timeout_ > 0) { + send_timeout_task_.reset(new TimeoutTask( + base::Bind(&SerialConnection::OnSendTimeout, AsWeakPtr()), + base::TimeDelta::FromMilliseconds(send_timeout_))); + } + return true; } -int SerialConnection::Write(scoped_refptr<net::IOBuffer> io_buffer, - int byte_count) { - DCHECK(io_buffer->data()); - DCHECK_GE(byte_count, 0); - return base::WritePlatformFileAtCurrentPos(file_, io_buffer->data(), - byte_count); +bool SerialConnection::Configure( + const api::serial::ConnectionOptions& options) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (options.persistent.get()) + set_persistent(*options.persistent); + if (options.name.get()) + set_name(*options.name); + if (options.buffer_size.get()) + set_buffer_size(*options.buffer_size); + if (options.receive_timeout.get()) + set_receive_timeout(*options.receive_timeout); + if (options.send_timeout.get()) + set_send_timeout(*options.send_timeout); + bool success = ConfigurePort(options); + io_handler_->CancelRead(api::serial::RECEIVE_ERROR_NONE); + return success; } -void SerialConnection::Flush() { - base::FlushPlatformFile(file_); +void SerialConnection::SetIoHandlerForTest( + scoped_refptr<SerialIoHandler> handler) { + io_handler_ = handler; +} + +bool SerialConnection::GetInfo(api::serial::ConnectionInfo* info) const { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + info->paused = paused_; + info->persistent = persistent_; + info->name = name_; + info->buffer_size = buffer_size_; + info->receive_timeout = receive_timeout_; + info->send_timeout = send_timeout_; + return GetPortInfo(info); +} + +void SerialConnection::StartOpen() { + DCHECK(!open_complete_.is_null()); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK_EQ(file_, base::kInvalidPlatformFileValue); + const SerialPortEnumerator::StringSet name_set( + SerialPortEnumerator::GenerateValidSerialPortNames()); + base::PlatformFile file = base::kInvalidPlatformFileValue; + if (SerialPortEnumerator::DoesPortExist(name_set, port_)) { + // It's the responsibility of the API wrapper around SerialConnection to + // validate the supplied path against the set of valid port names, and + // it is a reasonable assumption that serial port names are ASCII. + DCHECK(IsStringASCII(port_)); + base::FilePath path( + base::FilePath::FromUTF8Unsafe(MaybeFixUpPortName(port_))); + int flags = base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_EXCLUSIVE_READ | + base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_EXCLUSIVE_WRITE | + base::PLATFORM_FILE_ASYNC | + base::PLATFORM_FILE_TERMINAL_DEVICE; + file = base::CreatePlatformFile(path, flags, NULL, NULL); + } + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&SerialConnection::FinishOpen, base::Unretained(this), file)); +} + +void SerialConnection::FinishOpen(base::PlatformFile file) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(!open_complete_.is_null()); + DCHECK_EQ(file_, base::kInvalidPlatformFileValue); + OpenCompleteCallback callback = open_complete_; + open_complete_.Reset(); + + if (file == base::kInvalidPlatformFileValue) { + callback.Run(false); + return; + } + + file_ = file; + io_handler_->Initialize( + file_, + base::Bind(&SerialConnection::OnAsyncReadComplete, AsWeakPtr()), + base::Bind(&SerialConnection::OnAsyncWriteComplete, AsWeakPtr())); + + bool success = PostOpen(); + if (!success) { + Close(); + } + + callback.Run(success); +} + +// static +void SerialConnection::DoClose(base::PlatformFile port) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + if (port != base::kInvalidPlatformFileValue) { + base::ClosePlatformFile(port); + } +} + +void SerialConnection::OnReceiveTimeout() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + io_handler_->CancelRead(api::serial::RECEIVE_ERROR_TIMEOUT); +} + +void SerialConnection::OnSendTimeout() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + io_handler_->CancelWrite(api::serial::SEND_ERROR_TIMEOUT); +} + +void SerialConnection::OnAsyncReadComplete(const std::string& data, + api::serial::ReceiveError error) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(!receive_complete_.is_null()); + ReceiveCompleteCallback callback = receive_complete_; + receive_complete_.Reset(); + receive_timeout_task_.reset(); + callback.Run(data, error); +} + +void SerialConnection::OnAsyncWriteComplete(int bytes_sent, + api::serial::SendError error) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(!send_complete_.is_null()); + SendCompleteCallback callback = send_complete_; + send_complete_.Reset(); + send_timeout_task_.reset(); + callback.Run(bytes_sent, error); +} + +SerialConnection::TimeoutTask::TimeoutTask(const base::Closure& closure, + const base::TimeDelta& delay) + : weak_factory_(this), + closure_(closure), + delay_(delay) { + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&TimeoutTask::Run, weak_factory_.GetWeakPtr()), + delay_); +} + +SerialConnection::TimeoutTask::~TimeoutTask() {} + +void SerialConnection::TimeoutTask::Run() const { + closure_.Run(); } } // namespace extensions diff --git a/chrome/browser/extensions/api/serial/serial_connection.h b/chrome/browser/extensions/api/serial/serial_connection.h index f881301..6044142 100644 --- a/chrome/browser/extensions/api/serial/serial_connection.h +++ b/chrome/browser/extensions/api/serial/serial_connection.h @@ -8,64 +8,137 @@ #include <set> #include <string> +#include "base/callback.h" #include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" #include "base/platform_file.h" +#include "base/time/time.h" #include "chrome/browser/extensions/api/api_resource.h" #include "chrome/browser/extensions/api/api_resource_manager.h" +#include "chrome/browser/extensions/api/serial/serial_io_handler.h" #include "chrome/common/extensions/api/serial.h" #include "content/public/browser/browser_thread.h" -#include "net/base/io_buffer.h" +#include "net/base/file_stream.h" using content::BrowserThread; -namespace serial = extensions::api::serial; - namespace extensions { -extern const char kSerialConnectionNotFoundError[]; - // Encapsulates an open serial port. Platform-specific implementations are in // _win and _posix versions of the the .cc file. -class SerialConnection : public ApiResource { +// NOTE: Instances of this object should only be constructed on the IO thread, +// and all methods should only be called on the IO thread unless otherwise +// noted. +class SerialConnection : public ApiResource, + public base::SupportsWeakPtr<SerialConnection> { public: - SerialConnection(const std::string& port, int bitrate, - serial::DataBit databit, serial::ParityBit parity, - serial::StopBit stopbit, + typedef base::Callback<void(bool success)> OpenCompleteCallback; + + // This is the callback type expected by Receive. Note that an error result + // does not necessarily imply an empty |data| string, since a receive may + // complete partially before being interrupted by an error condition. + typedef base::Callback<void(const std::string& data, + api::serial::ReceiveError error)> + ReceiveCompleteCallback; + + // This is the callback type expected by Send. Note that an error result + // does not necessarily imply 0 bytes sent, since a send may complete + // partially before being interrupted by an error condition. + typedef base::Callback<void(int bytes_sent, + api::serial::SendError error)> + SendCompleteCallback; + + SerialConnection(const std::string& port, const std::string& owner_extension_id); virtual ~SerialConnection(); - virtual bool Open(); + // ApiResource override. + virtual bool IsPersistent() const OVERRIDE; + + void set_persistent(bool persistent) { persistent_ = persistent; } + bool persistent() const { return persistent_; } + + void set_name(const std::string& name) { name_ = name; } + const std::string& name() const { return name_; } + + void set_buffer_size(int buffer_size); + int buffer_size() const { return buffer_size_; } + + void set_receive_timeout(int receive_timeout); + int receive_timeout() const { return receive_timeout_; } + + void set_send_timeout(int send_timeout); + int send_timeout() const { return send_timeout_; } + + void set_paused(bool paused); + bool paused() const { return paused_; } + + // Initiates an asynchronous Open of the device. It is the caller's + // responsibility to ensure that this SerialConnection stays alive + // until |callback| is run. + virtual void Open(const OpenCompleteCallback& callback); + + // Initiate a Close of the device. The SerialConnection instance will + // have its internal state reset synchronously upon calling this, but + // the underlying OS handle will be closed asynchronously. virtual void Close(); - virtual void Flush(); - - virtual int Read(scoped_refptr<net::IOBufferWithSize> io_buffer); - virtual int Write(scoped_refptr<net::IOBuffer> io_buffer, int byte_count); - - struct ControlSignals { - // Sent from workstation to device. The should_set_ values indicate whether - // SetControlSignals should change the given signal (true) or else leave it - // as-is (false). - bool should_set_dtr; - bool dtr; - bool should_set_rts; - bool rts; - - // Received by workstation from device. DCD (Data Carrier Detect) is - // equivalent to RLSD (Receive Line Signal Detect) on some platforms. - bool dcd; - bool cts; - }; - virtual bool GetControlSignals(ControlSignals &control_signals); - virtual bool SetControlSignals(const ControlSignals &control_signals); + // Begins an asynchronous receive operation. Calling this while a Receive + // is already pending is a no-op and returns |false| without calling + // |callback|. + virtual bool Receive(const ReceiveCompleteCallback& callback); + + // Begins an asynchronous send operation. Calling this while a Send + // is already pending is a no-op and returns |false| without calling + // |callback|. + virtual bool Send(const std::string& data, + const SendCompleteCallback& callback); - static const BrowserThread::ID kThreadId = BrowserThread::FILE; + // Flushes input and output buffers. + virtual bool Flush() const; + + // Configures some subset of port options for this connection. + // Omitted options are unchanged. Returns |true| iff the configuration + // changes were successful. + virtual bool Configure(const api::serial::ConnectionOptions& options); + + // Connection configuration query. Fills values in an existing + // ConnectionInfo. Returns |true| iff the connection's information + // was successfully retrieved. + virtual bool GetInfo(api::serial::ConnectionInfo* info) const; + + // Reads current control signals (DCD, CTS, etc.) into an existing + // ControlSignals structure. Returns |true| iff the signals were + // successfully read. + virtual bool GetControlSignals(api::serial::ControlSignals* control_signals) + const; + + // Sets one or more control signals (DTR and/or RTS). Returns |true| iff + // the signals were successfully set. Unininitialized flags in the + // ControlSignals structure are left unchanged. + virtual bool SetControlSignals( + const api::serial::ControlSignals& control_signals); + + static const BrowserThread::ID kThreadId = BrowserThread::IO; protected: - // Do platform-specific work after a successful Open(). + // Overrides |io_handler_| for testing. + virtual void SetIoHandlerForTest(scoped_refptr<SerialIoHandler> handler); + + // Performs platform-specific, one-time port configuration on open. bool PostOpen(); - // Platform-specific port name adapter + // Performs platform-specific port configuration. Returns |true| iff + // configuration was successful. + bool ConfigurePort(const api::serial::ConnectionOptions& options); + + // Performs a platform-specific port configuration query. Fills values in an + // existing ConnectionInfo. Returns |true| iff port configuration was + // successfully retrieved. + bool GetPortInfo(api::serial::ConnectionInfo* info) const; + + // Possibly fixes up a serial port path name in a platform-specific manner. static std::string MaybeFixUpPortName(const std::string &port_name); private: @@ -73,12 +146,93 @@ class SerialConnection : public ApiResource { static const char* service_name() { return "SerialConnectionManager"; } + + // Encapsulates a cancelable, delayed timeout task. Posts a delayed + // task upon construction and implicitly cancels the task upon + // destruction if it hasn't run yet. + class TimeoutTask { + public: + TimeoutTask(const base::Closure& closure, const base::TimeDelta& delay); + ~TimeoutTask(); + + private: + void Run() const; + + base::WeakPtrFactory<TimeoutTask> weak_factory_; + base::Closure closure_; + base::TimeDelta delay_; + }; + + // Continues an Open operation on the FILE thread. + void StartOpen(); + + // Finalizes an Open operation (continued from StartOpen) on the IO thread. + void FinishOpen(base::PlatformFile file); + + // Continues a Close operation on the FILE thread. + static void DoClose(base::PlatformFile port); + + // Handles a receive timeout. + void OnReceiveTimeout(); + + // Handles a send timeout. + void OnSendTimeout(); + + // Receives read completion notification from the |io_handler_|. + void OnAsyncReadComplete(const std::string& data, + api::serial::ReceiveError error); + + // Receives write completion notification from the |io_handler_|. + void OnAsyncWriteComplete(int bytes_sent, api::serial::SendError error); + + // The pathname of the serial device. std::string port_; - int bitrate_; - serial::DataBit databit_; - serial::ParityBit parity_; - serial::StopBit stopbit_; + + // File handle for the opened serial device. This value is only modified from + // the IO thread. base::PlatformFile file_; + + // Flag indicating whether or not the connection should persist when + // its host app is suspended. + bool persistent_; + + // User-specified connection name. + std::string name_; + + // Size of the receive buffer. + int buffer_size_; + + // Amount of time (in ms) to wait for a Read to succeed before triggering a + // timeout response via onReceiveError. + int receive_timeout_; + + // Amount of time (in ms) to wait for a Write to succeed before triggering + // a timeout response. + int send_timeout_; + + // Flag indicating that the connection is paused. A paused connection will not + // raise new onReceive events. + bool paused_; + + // Callback to handle the completion of a pending Open() request. + OpenCompleteCallback open_complete_; + + // Callback to handle the completion of a pending Receive() request. + ReceiveCompleteCallback receive_complete_; + + // Callback to handle the completion of a pending Send() request. + SendCompleteCallback send_complete_; + + // Closure which will trigger a receive timeout unless cancelled. Reset on + // initialization and after every successful Receive(). + scoped_ptr<TimeoutTask> receive_timeout_task_; + + // Write timeout closure. Reset on initialization and after every successful + // Send(). + scoped_ptr<TimeoutTask> send_timeout_task_; + + // Asynchronous I/O handler. + scoped_refptr<SerialIoHandler> io_handler_; }; } // namespace extensions diff --git a/chrome/browser/extensions/api/serial/serial_connection_posix.cc b/chrome/browser/extensions/api/serial/serial_connection_posix.cc index f70a4cf..014797a 100644 --- a/chrome/browser/extensions/api/serial/serial_connection_posix.cc +++ b/chrome/browser/extensions/api/serial/serial_connection_posix.cc @@ -2,172 +2,273 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/posix/eintr_wrapper.h" #include "chrome/browser/extensions/api/serial/serial_connection.h" #include <sys/ioctl.h> #include <termios.h> +#if defined(OS_LINUX) +#include <linux/serial.h> +#endif + namespace extensions { namespace { - int getBaudRate(int bitrate_) { - switch (bitrate_) { - case 0: - return B0; - case 50: - return B50; - case 75: - return B75; - case 110: - return B110; - case 134: - return B134; - case 150: - return B150; - case 200: - return B200; - case 300: - return B300; - case 600: - return B600; - case 1200: - return B1200; - case 1800: - return B1800; - case 2400: - return B2400; - case 4800: - return B4800; - case 9600: - return B9600; - case 19200: - return B19200; - case 38400: - return B38400; + +// Convert an integral bit rate to a nominal one. Returns |true| +// if the conversion was successful and |false| otherwise. +bool BitrateToSpeedConstant(int bitrate, speed_t* speed) { +#define BITRATE_TO_SPEED_CASE(x) case x: *speed = B ## x; return true; + switch (bitrate) { + BITRATE_TO_SPEED_CASE(0) + BITRATE_TO_SPEED_CASE(50) + BITRATE_TO_SPEED_CASE(75) + BITRATE_TO_SPEED_CASE(110) + BITRATE_TO_SPEED_CASE(134) + BITRATE_TO_SPEED_CASE(150) + BITRATE_TO_SPEED_CASE(200) + BITRATE_TO_SPEED_CASE(300) + BITRATE_TO_SPEED_CASE(600) + BITRATE_TO_SPEED_CASE(1200) + BITRATE_TO_SPEED_CASE(1800) + BITRATE_TO_SPEED_CASE(2400) + BITRATE_TO_SPEED_CASE(4800) + BITRATE_TO_SPEED_CASE(9600) + BITRATE_TO_SPEED_CASE(19200) + BITRATE_TO_SPEED_CASE(38400) #if defined(OS_POSIX) && !defined(OS_MACOSX) - case 57600: - return B57600; - case 115200: - return B115200; - case 230400: - return B230400; - case 460800: - return B460800; - case 576000: - return B576000; - case 921600: - return B921600; - default: - return B9600; -#else -// MACOSX doesn't define constants bigger than 38400. -// So if it is MACOSX and the value doesn't fit any of the defined constants -// It will setup the bitrate with 'bitrate_' (just forwarding the value) - default: - return bitrate_; + BITRATE_TO_SPEED_CASE(57600) + BITRATE_TO_SPEED_CASE(115200) + BITRATE_TO_SPEED_CASE(230400) + BITRATE_TO_SPEED_CASE(460800) + BITRATE_TO_SPEED_CASE(576000) + BITRATE_TO_SPEED_CASE(921600) #endif - } + default: + return false; } -} // namespace +#undef BITRATE_TO_SPEED_CASE +} -bool SerialConnection::PostOpen() { - struct termios options; +// Convert a known nominal speed into an integral bitrate. Returns |true| +// if the conversion was successful and |false| otherwise. +bool SpeedConstantToBitrate(speed_t speed, int* bitrate) { +#define SPEED_TO_BITRATE_CASE(x) case B ## x: *bitrate = x; return true; + switch (speed) { + SPEED_TO_BITRATE_CASE(0) + SPEED_TO_BITRATE_CASE(50) + SPEED_TO_BITRATE_CASE(75) + SPEED_TO_BITRATE_CASE(110) + SPEED_TO_BITRATE_CASE(134) + SPEED_TO_BITRATE_CASE(150) + SPEED_TO_BITRATE_CASE(200) + SPEED_TO_BITRATE_CASE(300) + SPEED_TO_BITRATE_CASE(600) + SPEED_TO_BITRATE_CASE(1200) + SPEED_TO_BITRATE_CASE(1800) + SPEED_TO_BITRATE_CASE(2400) + SPEED_TO_BITRATE_CASE(4800) + SPEED_TO_BITRATE_CASE(9600) + SPEED_TO_BITRATE_CASE(19200) + SPEED_TO_BITRATE_CASE(38400) +#if defined(OS_POSIX) && !defined(OS_MACOSX) + SPEED_TO_BITRATE_CASE(57600) + SPEED_TO_BITRATE_CASE(115200) + SPEED_TO_BITRATE_CASE(230400) + SPEED_TO_BITRATE_CASE(460800) + SPEED_TO_BITRATE_CASE(576000) + SPEED_TO_BITRATE_CASE(921600) +#endif + default: + return false; + } +#undef SPEED_TO_BITRATE_CASE +} - // Start with existing options and modify. - tcgetattr(file_, &options); +bool SetCustomBitrate(base::PlatformFile file, + struct termios* config, + int bitrate) { +#if defined(OS_LINUX) + struct serial_struct serial; + if (ioctl(file, TIOCGSERIAL, &serial) < 0) { + return false; + } + serial.flags = (serial.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST; + serial.custom_divisor = serial.baud_base / bitrate; + if (serial.custom_divisor < 1) { + serial.custom_divisor = 1; + } + cfsetispeed(config, B38400); + cfsetospeed(config, B38400); + return ioctl(file, TIOCSSERIAL, &serial) >= 0; +#else + return false; +#endif +} - // Bitrate (sometimes erroneously referred to as baud rate). - if (bitrate_ >= 0) { - int bitrate_opt_ = getBaudRate(bitrate_); +} // namespace - cfsetispeed(&options, bitrate_opt_); - cfsetospeed(&options, bitrate_opt_); +bool SerialConnection::ConfigurePort( + const api::serial::ConnectionOptions& options) { + struct termios config; + tcgetattr(file_, &config); + if (options.bitrate.get()) { + if (*options.bitrate >= 0) { + speed_t bitrate_opt = B0; + if (BitrateToSpeedConstant(*options.bitrate, &bitrate_opt)) { + cfsetispeed(&config, bitrate_opt); + cfsetospeed(&config, bitrate_opt); + } else { + // Attempt to set a custom speed. + if (!SetCustomBitrate(file_, &config, *options.bitrate)) { + return false; + } + } + } } - - options.c_cflag &= ~CSIZE; - switch (databit_) { - case serial::DATA_BIT_SEVENBIT: - options.c_cflag |= CS7; - break; - case serial::DATA_BIT_EIGHTBIT: - default: - options.c_cflag |= CS8; - break; - } - switch (stopbit_) { - case serial::STOP_BIT_TWOSTOPBIT: - options.c_cflag |= CSTOPB; - break; - case serial::STOP_BIT_ONESTOPBIT: - default: - options.c_cflag &= ~CSTOPB; - break; - } - switch (parity_) { - case serial::PARITY_BIT_EVENPARITY: - options.c_cflag |= PARENB; - options.c_cflag &= ~PARODD; - break; - case serial::PARITY_BIT_ODDPARITY: - options.c_cflag |= (PARENB | PARODD); - break; - case serial::PARITY_BIT_NOPARITY: - default: - options.c_cflag &= ~(PARENB | PARODD); - break; + if (options.data_bits != api::serial::DATA_BITS_NONE) { + config.c_cflag &= ~CSIZE; + switch (options.data_bits) { + case api::serial::DATA_BITS_SEVEN: + config.c_cflag |= CS7; + break; + case api::serial::DATA_BITS_EIGHT: + default: + config.c_cflag |= CS8; + break; + } } + if (options.parity_bit != api::serial::PARITY_BIT_NONE) { + switch (options.parity_bit) { + case api::serial::PARITY_BIT_EVEN: + config.c_cflag |= PARENB; + config.c_cflag &= ~PARODD; + break; + case api::serial::PARITY_BIT_ODD: + config.c_cflag |= (PARODD | PARENB); + break; + case api::serial::PARITY_BIT_NO: + default: + config.c_cflag &= ~(PARODD | PARENB); + break; + } + } + if (options.stop_bits != api::serial::STOP_BITS_NONE) { + switch (options.stop_bits) { + case api::serial::STOP_BITS_TWO: + config.c_cflag |= CSTOPB; + break; + case api::serial::STOP_BITS_ONE: + default: + config.c_cflag &= ~CSTOPB; + break; + } + } + if (options.cts_flow_control.get()) { + if (*options.cts_flow_control){ + config.c_cflag |= CRTSCTS; + } else { + config.c_cflag &= ~CRTSCTS; + } + } + return tcsetattr(file_, TCSANOW, &config) == 0; +} + +bool SerialConnection::PostOpen() { + struct termios config; + tcgetattr(file_, &config); // Set flags for 'raw' operation - // At least on Linux the flags are persistent and thus we cannot trust - // the default values. - options.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG); - options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | - ICRNL | IXON); - options.c_oflag &= ~OPOST; + config.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG); + config.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | + ICRNL | IXON); + config.c_oflag &= ~OPOST; - // Enable receiver and set local mode - // See http://www.easysw.com/~mike/serial/serial.html to understand. - options.c_cflag |= (CLOCAL | CREAD); + // CLOCAL causes the system to disregard the DCD signal state. + // CREAD enables reading from the port. + config.c_cflag |= (CLOCAL | CREAD); - // Write the options. - tcsetattr(file_, TCSANOW, &options); + return tcsetattr(file_, TCSANOW, &config) == 0; +} - return true; +bool SerialConnection::Flush() const { + return tcflush(file_, TCIOFLUSH) == 0; } -bool SerialConnection::GetControlSignals(ControlSignals &control_signals) { +bool SerialConnection::GetControlSignals(api::serial::ControlSignals* signals) + const { int status; - if (ioctl(file_, TIOCMGET, &status) == 0) { - control_signals.dcd = (status & TIOCM_CAR) != 0; - control_signals.cts = (status & TIOCM_CTS) != 0; - return true; + if (ioctl(file_, TIOCMGET, &status) == -1) { + return false; } - return false; + + signals->dcd.reset(new bool(status & TIOCM_CAR)); + signals->cts.reset(new bool(status & TIOCM_CTS)); + signals->dsr.reset(new bool(status & TIOCM_DSR)); + signals->ri.reset(new bool(status & TIOCM_RI)); + return true; } bool SerialConnection::SetControlSignals( - const ControlSignals &control_signals) { + const api::serial::ControlSignals& signals) { int status; - if (ioctl(file_, TIOCMGET, &status) != 0) + if (ioctl(file_, TIOCMGET, &status) == -1) { return false; + } - if (control_signals.should_set_dtr) { - if (control_signals.dtr) + if (signals.dtr.get()) { + if (*signals.dtr) { status |= TIOCM_DTR; - else + } else { status &= ~TIOCM_DTR; + } } - if (control_signals.should_set_rts) { - if (control_signals.rts) + + if (signals.rts.get()) { + if (*signals.rts){ status |= TIOCM_RTS; - else + } else{ status &= ~TIOCM_RTS; + } } return ioctl(file_, TIOCMSET, &status) == 0; } +bool SerialConnection::GetPortInfo(api::serial::ConnectionInfo* info) const { + struct termios config; + if (tcgetattr(file_, &config) == -1) { + return false; + } + speed_t ispeed = cfgetispeed(&config); + speed_t ospeed = cfgetospeed(&config); + if (ispeed == ospeed) { + int bitrate = 0; + if (SpeedConstantToBitrate(ispeed, &bitrate)) { + info->bitrate.reset(new int(bitrate)); + } + } + if ((config.c_cflag & CSIZE) == CS7) { + info->data_bits = api::serial::DATA_BITS_SEVEN; + } else if ((config.c_cflag & CSIZE) == CS8) { + info->data_bits = api::serial::DATA_BITS_EIGHT; + } else { + info->data_bits = api::serial::DATA_BITS_NONE; + } + if (config.c_cflag & PARENB) { + info->parity_bit = (config.c_cflag & PARODD) ? api::serial::PARITY_BIT_ODD + : api::serial::PARITY_BIT_EVEN; + } else { + info->parity_bit = api::serial::PARITY_BIT_NO; + } + info->stop_bits = (config.c_cflag & CSTOPB) ? api::serial::STOP_BITS_TWO + : api::serial::STOP_BITS_ONE; + info->cts_flow_control.reset(new bool((config.c_cflag & CRTSCTS) != 0)); + return true; +} + std::string SerialConnection::MaybeFixUpPortName( const std::string &port_name) { return port_name; diff --git a/chrome/browser/extensions/api/serial/serial_connection_win.cc b/chrome/browser/extensions/api/serial/serial_connection_win.cc index d6d9b12..876f1b3 100644 --- a/chrome/browser/extensions/api/serial/serial_connection_win.cc +++ b/chrome/browser/extensions/api/serial/serial_connection_win.cc @@ -11,102 +11,225 @@ namespace extensions { namespace { -int getBaudRate(int bitrate_) { - switch (bitrate_) { - case 110: return CBR_110; - case 300: return CBR_300; - case 600: return CBR_600; - case 1200: return CBR_1200; - case 2400: return CBR_2400; - case 4800: return CBR_4800; - case 9600: return CBR_9600; - case 14400: return CBR_14400; - case 19200: return CBR_19200; - case 38400: return CBR_38400; - case 57600: return CBR_57600; - case 115200: return CBR_115200; - case 128000: return CBR_128000; - case 256000: return CBR_256000; - default: return CBR_9600; + +int BitrateToSpeedConstant(int bitrate) { +#define BITRATE_TO_SPEED_CASE(x) case x: return CBR_ ## x; + switch (bitrate) { + BITRATE_TO_SPEED_CASE(110); + BITRATE_TO_SPEED_CASE(300); + BITRATE_TO_SPEED_CASE(600); + BITRATE_TO_SPEED_CASE(1200); + BITRATE_TO_SPEED_CASE(2400); + BITRATE_TO_SPEED_CASE(4800); + BITRATE_TO_SPEED_CASE(9600); + BITRATE_TO_SPEED_CASE(14400); + BITRATE_TO_SPEED_CASE(19200); + BITRATE_TO_SPEED_CASE(38400); + BITRATE_TO_SPEED_CASE(57600); + BITRATE_TO_SPEED_CASE(115200); + BITRATE_TO_SPEED_CASE(128000); + BITRATE_TO_SPEED_CASE(256000); + default: + // If the bitrate doesn't match that of one of the standard + // index constants, it may be provided as-is to the DCB + // structure, according to MSDN. + return bitrate; } +#undef BITRATE_TO_SPEED_CASE } -int getDataBit(serial::DataBit databit) { - switch (databit) { - case serial::DATA_BIT_SEVENBIT: +int DataBitsEnumToConstant(api::serial::DataBits data_bits) { + switch (data_bits) { + case api::serial::DATA_BITS_SEVEN: return 7; - case serial::DATA_BIT_EIGHTBIT: + case api::serial::DATA_BITS_EIGHT: default: return 8; } } -int getParity(serial::ParityBit parity) { - switch (parity) { - case serial::PARITY_BIT_EVENPARITY: +int ParityBitEnumToConstant(api::serial::ParityBit parity_bit) { + switch (parity_bit) { + case api::serial::PARITY_BIT_EVEN: return EVENPARITY; - case serial::PARITY_BIT_ODDPARITY: + case api::serial::PARITY_BIT_ODD: return SPACEPARITY; - case serial::PARITY_BIT_NOPARITY: + case api::serial::PARITY_BIT_NO: default: return NOPARITY; } } -int getStopBit(serial::StopBit stopbit) { - switch (stopbit) { - case serial::STOP_BIT_TWOSTOPBIT: +int StopBitsEnumToConstant(api::serial::StopBits stop_bits) { + switch (stop_bits) { + case api::serial::STOP_BITS_TWO: return TWOSTOPBITS; - case serial::STOP_BIT_ONESTOPBIT: + case api::serial::STOP_BITS_ONE: default: return ONESTOPBIT; } } + +int SpeedConstantToBitrate(int speed) { +#define SPEED_TO_BITRATE_CASE(x) case CBR_ ## x: return x; + switch (speed) { + SPEED_TO_BITRATE_CASE(110); + SPEED_TO_BITRATE_CASE(300); + SPEED_TO_BITRATE_CASE(600); + SPEED_TO_BITRATE_CASE(1200); + SPEED_TO_BITRATE_CASE(2400); + SPEED_TO_BITRATE_CASE(4800); + SPEED_TO_BITRATE_CASE(9600); + SPEED_TO_BITRATE_CASE(14400); + SPEED_TO_BITRATE_CASE(19200); + SPEED_TO_BITRATE_CASE(38400); + SPEED_TO_BITRATE_CASE(57600); + SPEED_TO_BITRATE_CASE(115200); + SPEED_TO_BITRATE_CASE(128000); + SPEED_TO_BITRATE_CASE(256000); + default: + // If it's not one of the standard index constants, + // it should be an integral baud rate, according to + // MSDN. + return speed; + } +#undef SPEED_TO_BITRATE_CASE +} + +api::serial::DataBits DataBitsConstantToEnum(int data_bits) { + switch (data_bits) { + case 7: + return api::serial::DATA_BITS_SEVEN; + case 8: + default: + return api::serial::DATA_BITS_EIGHT; + } +} + +api::serial::ParityBit ParityBitConstantToEnum(int parity_bit) { + switch (parity_bit) { + case EVENPARITY: + return api::serial::PARITY_BIT_EVEN; + case ODDPARITY: + return api::serial::PARITY_BIT_ODD; + case NOPARITY: + default: + return api::serial::PARITY_BIT_NO; + } +} + +api::serial::StopBits StopBitsConstantToEnum(int stop_bits) { + switch (stop_bits) { + case TWOSTOPBITS: + return api::serial::STOP_BITS_TWO; + case ONESTOPBIT: + default: + return api::serial::STOP_BITS_ONE; + } +} + } // namespace -bool SerialConnection::PostOpen() { - // Set timeouts so that reads return immediately with whatever could be read - // without blocking. - COMMTIMEOUTS timeouts = { 0 }; - timeouts.ReadIntervalTimeout = MAXDWORD; - if (!::SetCommTimeouts(file_, &timeouts)) +bool SerialConnection::ConfigurePort( + const api::serial::ConnectionOptions& options) { + DCB config = { 0 }; + config.DCBlength = sizeof(config); + if (!GetCommState(file_, &config)) { return false; + } + if (options.bitrate.get()) + config.BaudRate = BitrateToSpeedConstant(*options.bitrate); + if (options.data_bits != api::serial::DATA_BITS_NONE) + config.ByteSize = DataBitsEnumToConstant(options.data_bits); + if (options.parity_bit != api::serial::PARITY_BIT_NONE) + config.Parity = ParityBitEnumToConstant(options.parity_bit); + if (options.stop_bits != api::serial::STOP_BITS_NONE) + config.StopBits = StopBitsEnumToConstant(options.stop_bits); + if (options.cts_flow_control.get()) { + if (*options.cts_flow_control) { + config.fOutxCtsFlow = TRUE; + config.fRtsControl = RTS_CONTROL_HANDSHAKE; + } else { + config.fOutxCtsFlow = FALSE; + config.fRtsControl = RTS_CONTROL_ENABLE; + } + } + return SetCommState(file_, &config) != 0; +} - DCB dcb = { 0 }; - dcb.DCBlength = sizeof(dcb); - if (!GetCommState(file_, &dcb)) +bool SerialConnection::PostOpen() { + // Set a very brief read interval timeout. This prevents the asynchronous I/O + // system from being way too eager to fire off completion events which would + // in turn result in a lot of onReceive events being fired (i.e., one for + // every individual byte received on the serial buffer.) + COMMTIMEOUTS timeouts = { 0 }; + timeouts.ReadIntervalTimeout = 10; + if (!::SetCommTimeouts(file_, &timeouts)) { return false; + } - dcb.BaudRate = getBaudRate(bitrate_); - dcb.ByteSize = getDataBit(databit_); - dcb.Parity = getParity(parity_); - dcb.StopBits = getStopBit(stopbit_); - - if (!SetCommState(file_, &dcb)) + DCB config = { 0 }; + config.DCBlength = sizeof(config); + if (!GetCommState(file_, &config)) { return false; + } + // Setup some sane default state. + config.fBinary = TRUE; + config.fParity = FALSE; + config.fAbortOnError = TRUE; + config.fOutxCtsFlow = FALSE; + config.fOutxDsrFlow = FALSE; + config.fRtsControl = RTS_CONTROL_ENABLE; + config.fDtrControl = DTR_CONTROL_ENABLE; + config.fDsrSensitivity = FALSE; + config.fOutX = FALSE; + config.fInX = FALSE; + return SetCommState(file_, &config) != 0; +} - return true; +bool SerialConnection::Flush() const { + return PurgeComm(file_, PURGE_RXCLEAR | PURGE_TXCLEAR) != 0; } -bool SerialConnection::GetControlSignals(ControlSignals &control_signals) { - DWORD dwModemStatus; - if (!GetCommModemStatus(file_, &dwModemStatus)) +bool SerialConnection::GetControlSignals(api::serial::ControlSignals* signals) + const { + DWORD status; + if (!GetCommModemStatus(file_, &status)) { return false; - control_signals.dcd = (MS_RLSD_ON & dwModemStatus) != 0; - control_signals.cts = (MS_CTS_ON & dwModemStatus) != 0; + } + signals->dcd.reset(new bool((status & MS_RLSD_ON) != 0)); + signals->cts.reset(new bool((status & MS_CTS_ON) != 0)); + signals->dsr.reset(new bool((status & MS_DSR_ON) != 0)); + signals->ri.reset(new bool((status & MS_RING_ON) != 0)); return true; } bool SerialConnection::SetControlSignals( - const ControlSignals &control_signals) { - if (control_signals.should_set_dtr) { - if (!EscapeCommFunction(file_, control_signals.dtr ? SETDTR : CLRDTR)) + const api::serial::ControlSignals& signals) { + if (signals.dtr.get()) { + if (!EscapeCommFunction(file_, *signals.dtr ? SETDTR : CLRDTR)) { return false; + } } - if (control_signals.should_set_rts) { - if (!EscapeCommFunction(file_, control_signals.rts ? SETRTS : CLRRTS)) + if (signals.rts.get()) { + if (!EscapeCommFunction(file_, *signals.rts ? SETRTS : CLRRTS)) { return false; + } + } + return true; +} + +bool SerialConnection::GetPortInfo(api::serial::ConnectionInfo* info) const { + DCB config = { 0 }; + config.DCBlength = sizeof(config); + if (!GetCommState(file_, &config)) { + return false; } + info->bitrate.reset(new int(SpeedConstantToBitrate(config.BaudRate))); + info->data_bits = DataBitsConstantToEnum(config.ByteSize); + info->parity_bit = ParityBitConstantToEnum(config.Parity); + info->stop_bits = StopBitsConstantToEnum(config.StopBits); + info->cts_flow_control.reset(new bool(config.fOutxCtsFlow != 0)); return true; } diff --git a/chrome/browser/extensions/api/serial/serial_event_dispatcher.cc b/chrome/browser/extensions/api/serial/serial_event_dispatcher.cc new file mode 100644 index 0000000..5d475c2 --- /dev/null +++ b/chrome/browser/extensions/api/serial/serial_event_dispatcher.cc @@ -0,0 +1,158 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/serial/serial_event_dispatcher.h" + +#include "chrome/browser/browser_process.h" +#include "chrome/browser/extensions/api/serial/serial_connection.h" +#include "chrome/browser/extensions/event_router.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_manager.h" + +namespace extensions { + +namespace api { + +namespace { + +bool ShouldPauseOnReceiveError(serial::ReceiveError error) { + return error == serial::RECEIVE_ERROR_DEVICE_LOST || + error == serial::RECEIVE_ERROR_SYSTEM_ERROR || + error == serial::RECEIVE_ERROR_CLOSED; +} + +} // namespace + +static base::LazyInstance<ProfileKeyedAPIFactory<SerialEventDispatcher> > + g_factory = LAZY_INSTANCE_INITIALIZER; + +// static +ProfileKeyedAPIFactory<SerialEventDispatcher>* + SerialEventDispatcher::GetFactoryInstance() { + return &g_factory.Get(); +} + +// static +SerialEventDispatcher* SerialEventDispatcher::Get(Profile* profile) { + return ProfileKeyedAPIFactory<SerialEventDispatcher>::GetForProfile(profile); +} + +SerialEventDispatcher::SerialEventDispatcher(Profile* profile) + : thread_id_(SerialConnection::kThreadId), + profile_(profile) { + ApiResourceManager<SerialConnection>* manager = + ApiResourceManager<SerialConnection>::Get(profile); + DCHECK(manager) << "No serial connection manager."; + connections_ = manager->data_; +} + +SerialEventDispatcher::~SerialEventDispatcher() {} + +SerialEventDispatcher::ReceiveParams::ReceiveParams() {} + +SerialEventDispatcher::ReceiveParams::~ReceiveParams() {} + +void SerialEventDispatcher::PollConnection(const std::string& extension_id, + int connection_id) { + DCHECK(BrowserThread::CurrentlyOn(thread_id_)); + + ReceiveParams params; + params.thread_id = thread_id_; + params.profile_id = profile_; + params.extension_id = extension_id; + params.connections = connections_; + params.connection_id = connection_id; + + StartReceive(params); +} + +// static +void SerialEventDispatcher::StartReceive(const ReceiveParams& params) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + SerialConnection* connection = + params.connections->Get(params.extension_id, params.connection_id); + if (!connection) + return; + DCHECK(params.extension_id == connection->owner_extension_id()); + + if (connection->paused()) + return; + + connection->Receive(base::Bind(&ReceiveCallback, params)); +} + +// static +void SerialEventDispatcher::ReceiveCallback(const ReceiveParams& params, + const std::string& data, + serial::ReceiveError error) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + // Note that an error (e.g. timeout) does not necessarily mean that no data + // was read, so we may fire an onReceive regardless of any error code. + if (data.length() > 0) { + serial::ReceiveInfo receive_info; + receive_info.connection_id = params.connection_id; + receive_info.data = data; + scoped_ptr<base::ListValue> args = serial::OnReceive::Create(receive_info); + scoped_ptr<extensions::Event> event( + new extensions::Event(serial::OnReceive::kEventName, args.Pass())); + PostEvent(params, event.Pass()); + } + + if (error != serial::RECEIVE_ERROR_NONE) { + serial::ReceiveErrorInfo error_info; + error_info.connection_id = params.connection_id; + error_info.error = error; + scoped_ptr<base::ListValue> args = + serial::OnReceiveError::Create(error_info); + scoped_ptr<extensions::Event> event( + new extensions::Event(serial::OnReceiveError::kEventName, args.Pass())); + PostEvent(params, event.Pass()); + if (ShouldPauseOnReceiveError(error)) { + SerialConnection* connection = + params.connections->Get(params.extension_id, params.connection_id); + if (connection) + connection->set_paused(true); + } + } + + // Queue up the next read operation. + BrowserThread::PostTask(params.thread_id, + FROM_HERE, + base::Bind(&StartReceive, params)); +} + +// static +void SerialEventDispatcher::PostEvent(const ReceiveParams& params, + scoped_ptr<extensions::Event> event) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&DispatchEvent, + params.profile_id, + params.extension_id, + base::Passed(event.Pass()))); +} + +// static +void SerialEventDispatcher::DispatchEvent(void* profile_id, + const std::string& extension_id, + scoped_ptr<extensions::Event> event) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + Profile* profile = reinterpret_cast<Profile*>(profile_id); + if (!g_browser_process->profile_manager()->IsValidProfile(profile)) + return; + + EventRouter* router = ExtensionSystem::Get(profile)->event_router(); + if (router) + router->DispatchEventToExtension(extension_id, event.Pass()); +} + +} // namespace api + +} // namespace extensions diff --git a/chrome/browser/extensions/api/serial/serial_event_dispatcher.h b/chrome/browser/extensions/api/serial/serial_event_dispatcher.h new file mode 100644 index 0000000..7ed44f9 --- /dev/null +++ b/chrome/browser/extensions/api/serial/serial_event_dispatcher.h @@ -0,0 +1,76 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_EVENT_DISPATCHER_H_ +#define CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_EVENT_DISPATCHER_H_ + +#include "chrome/browser/extensions/api/api_resource_manager.h" +#include "chrome/common/extensions/api/serial.h" + +namespace extensions { + +struct Event; +class SerialConnection; + +namespace api { + +// Per-profile dispatcher for events on serial connections. +class SerialEventDispatcher : public ProfileKeyedAPI { + public: + explicit SerialEventDispatcher(Profile* profile); + virtual ~SerialEventDispatcher(); + + // Start receiving data and firing events for a connection. + void PollConnection(const std::string& extension_id, int connection_id); + + static SerialEventDispatcher* Get(Profile* profile); + + // ProfileKeyedAPI implementation. + static ProfileKeyedAPIFactory<SerialEventDispatcher>* GetFactoryInstance(); + + private: + typedef ApiResourceManager<SerialConnection>::ApiResourceData ConnectionData; + friend class ProfileKeyedAPIFactory<SerialEventDispatcher>; + + // ProfileKeyedAPI implementation. + static const char *service_name() { + return "SerialEventDispatcher"; + } + static const bool kServiceHasOwnInstanceInIncognito = true; + static const bool kServiceIsNULLWhileTesting = true; + + struct ReceiveParams { + ReceiveParams(); + ~ReceiveParams(); + + content::BrowserThread::ID thread_id; + void* profile_id; + std::string extension_id; + scoped_refptr<ConnectionData> connections; + int connection_id; + }; + + static void StartReceive(const ReceiveParams& params); + + static void ReceiveCallback(const ReceiveParams& params, + const std::string& data, + serial::ReceiveError error); + + static void PostEvent(const ReceiveParams& params, + scoped_ptr<extensions::Event> event); + + static void DispatchEvent(void *profile_id, + const std::string& extension_id, + scoped_ptr<extensions::Event> event); + + content::BrowserThread::ID thread_id_; + Profile* const profile_; + scoped_refptr<ConnectionData> connections_; +}; + +} // namespace api + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_EVENT_DISPATCHER_H_ diff --git a/chrome/browser/extensions/api/serial/serial_io_handler.cc b/chrome/browser/extensions/api/serial/serial_io_handler.cc new file mode 100644 index 0000000..683dde6 --- /dev/null +++ b/chrome/browser/extensions/api/serial/serial_io_handler.cc @@ -0,0 +1,120 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/serial/serial_io_handler.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" + +namespace extensions { + +SerialIoHandler::SerialIoHandler() + : file_(base::kInvalidPlatformFileValue), + pending_read_buffer_len_(0), + pending_write_buffer_len_(0) { +} + +SerialIoHandler::~SerialIoHandler() { + DCHECK(CalledOnValidThread()); +} + +void SerialIoHandler::Initialize(base::PlatformFile file, + const ReadCompleteCallback& read_callback, + const WriteCompleteCallback& write_callback) { + DCHECK(CalledOnValidThread()); + DCHECK_EQ(file_, base::kInvalidPlatformFileValue); + + file_ = file; + read_complete_ = read_callback; + write_complete_ = write_callback; + + InitializeImpl(); +} + +void SerialIoHandler::Read(int max_bytes) { + DCHECK(CalledOnValidThread()); + DCHECK(!IsReadPending()); + pending_read_buffer_ = new net::IOBuffer(max_bytes); + pending_read_buffer_len_ = max_bytes; + read_canceled_ = false; + AddRef(); + ReadImpl(); +} + +void SerialIoHandler::Write(const std::string& data) { + DCHECK(CalledOnValidThread()); + DCHECK(!IsWritePending()); + pending_write_buffer_ = new net::IOBuffer(data.length()); + pending_write_buffer_len_ = data.length(); + memcpy(pending_write_buffer_->data(), data.data(), pending_write_buffer_len_); + write_canceled_ = false; + AddRef(); + WriteImpl(); +} + +void SerialIoHandler::ReadCompleted(int bytes_read, + api::serial::ReceiveError error) { + DCHECK(CalledOnValidThread()); + DCHECK(IsReadPending()); + read_complete_.Run(std::string(pending_read_buffer_->data(), bytes_read), + error); + pending_read_buffer_ = NULL; + pending_read_buffer_len_ = 0; + Release(); +} + +void SerialIoHandler::WriteCompleted(int bytes_written, + api::serial::SendError error) { + DCHECK(CalledOnValidThread()); + DCHECK(IsWritePending()); + write_complete_.Run(bytes_written, error); + pending_write_buffer_ = NULL; + pending_write_buffer_len_ = 0; + Release(); +} + +bool SerialIoHandler::IsReadPending() const { + DCHECK(CalledOnValidThread()); + return pending_read_buffer_ != NULL; +} + +bool SerialIoHandler::IsWritePending() const { + DCHECK(CalledOnValidThread()); + return pending_write_buffer_ != NULL; +} + +void SerialIoHandler::CancelRead(api::serial::ReceiveError reason) { + DCHECK(CalledOnValidThread()); + if (IsReadPending()) { + read_canceled_ = true; + read_cancel_reason_ = reason; + CancelReadImpl(); + } +} + +void SerialIoHandler::CancelWrite(api::serial::SendError reason) { + DCHECK(CalledOnValidThread()); + if (IsWritePending()) { + write_canceled_ = true; + write_cancel_reason_ = reason; + CancelWriteImpl(); + } +} + +void SerialIoHandler::QueueReadCompleted(int bytes_read, + api::serial::ReceiveError error) { + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&SerialIoHandler::ReadCompleted, this, + bytes_read, error)); +} + +void SerialIoHandler::QueueWriteCompleted(int bytes_written, + api::serial::SendError error) { + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&SerialIoHandler::WriteCompleted, this, + bytes_written, error)); +} + +} // namespace extensions + diff --git a/chrome/browser/extensions/api/serial/serial_io_handler.h b/chrome/browser/extensions/api/serial/serial_io_handler.h new file mode 100644 index 0000000..a98f520 --- /dev/null +++ b/chrome/browser/extensions/api/serial/serial_io_handler.h @@ -0,0 +1,173 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_IO_HANDLER_H_ +#define CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_IO_HANDLER_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/platform_file.h" +#include "base/threading/non_thread_safe.h" +#include "chrome/common/extensions/api/serial.h" +#include "net/base/io_buffer.h" + +namespace extensions { + +// Provides a simplified interface for performing asynchronous I/O on serial +// devices by hiding platform-specific MessageLoop interfaces. Pending I/O +// operations hold a reference to this object until completion so that memory +// doesn't disappear out from under the OS. +class SerialIoHandler : public base::NonThreadSafe, + public base::RefCounted<SerialIoHandler> { + public: + // Constructs an instance of some platform-specific subclass. + static scoped_refptr<SerialIoHandler> Create(); + + // Called with a string of bytes read, and a result code. Note that an error + // result does not necessarily imply 0 bytes read. + typedef base::Callback<void(const std::string& data, + api::serial::ReceiveError error)> + ReadCompleteCallback; + + // Called with the number of bytes written and a result code. Note that an + // error result does not necessarily imply 0 bytes written. + typedef base::Callback<void(int bytes_written, api::serial::SendError error)> + WriteCompleteCallback; + + // Initializes the handler on the current message loop. Must be called exactly + // once before performing any I/O through the handler. + void Initialize(base::PlatformFile file, + const ReadCompleteCallback& read_callback, + const WriteCompleteCallback& write_callback); + + // Performs an async Read operation. Behavior is undefined if this is called + // while a Read is already pending. Otherwise, the ReadCompleteCallback + // (see above) will eventually be called with a result. + void Read(int max_bytes); + + // Performs an async Write operation. Behavior is undefined if this is called + // while a Write is already pending. Otherwise, the WriteCompleteCallback + // (see above) will eventually be called with a result. + void Write(const std::string& data); + + // Indicates whether or not a read is currently pending. + bool IsReadPending() const; + + // Indicates whether or not a write is currently pending. + bool IsWritePending() const; + + // Attempts to cancel a pending read operation. + void CancelRead(api::serial::ReceiveError reason); + + // Attempts to cancel a pending write operation. + void CancelWrite(api::serial::SendError reason); + + protected: + SerialIoHandler(); + virtual ~SerialIoHandler(); + + // Performs platform-specific initialization. |file_|, |read_complete_| and + // |write_complete_| all hold initialized values before this is called. + virtual void InitializeImpl() {} + + // Performs a platform-specific read operation. This must guarantee that + // ReadCompleted is called when the underlying async operation is completed + // or the SerialIoHandler instance will leak. + // NOTE: Implementations of ReadImpl should never call ReadCompleted directly. + // Use QueueReadCompleted instead to avoid reentrancy. + virtual void ReadImpl() = 0; + + // Performs a platform-specific write operation. This must guarantee that + // WriteCompleted is called when the underlying async operation is completed + // or the SerialIoHandler instance will leak. + // NOTE: Implementations of Writempl should never call WriteCompleted + // directly. Use QueueWriteCompleted instead to avoid reentrancy. + virtual void WriteImpl() = 0; + + // Platform-specific read cancelation. + virtual void CancelReadImpl() = 0; + + // Platform-specific write cancelation. + virtual void CancelWriteImpl() = 0; + + // Called by the implementation to signal that the active read has completed. + // WARNING: Calling this method can destroy the SerialIoHandler instance + // if the associated I/O operation was the only thing keeping it alive. + void ReadCompleted(int bytes_read, api::serial::ReceiveError error); + + // Called by the implementation to signal that the active write has completed. + // WARNING: Calling this method may destroy the SerialIoHandler instance + // if the associated I/O operation was the only thing keeping it alive. + void WriteCompleted(int bytes_written, api::serial::SendError error); + + // Queues a ReadCompleted call on the current thread. This is used to allow + // ReadImpl to immediately signal completion with 0 bytes and an error, + // without being reentrant. + void QueueReadCompleted(int bytes_read, api::serial::ReceiveError error); + + // Queues a WriteCompleted call on the current thread. This is used to allow + // WriteImpl to immediately signal completion with 0 bytes and an error, + // without being reentrant. + void QueueWriteCompleted(int bytes_written, api::serial::SendError error); + + base::PlatformFile file() const { + return file_; + } + + net::IOBuffer* pending_read_buffer() const { + return pending_read_buffer_.get(); + } + + int pending_read_buffer_len() const { + return pending_read_buffer_len_; + } + + api::serial::ReceiveError read_cancel_reason() const { + return read_cancel_reason_; + } + + bool read_canceled() const { + return read_canceled_; + } + + net::IOBuffer* pending_write_buffer() const { + return pending_write_buffer_.get(); + } + + int pending_write_buffer_len() const { + return pending_write_buffer_len_; + } + + api::serial::SendError write_cancel_reason() const { + return write_cancel_reason_; + } + + bool write_canceled() const { + return write_canceled_; + } + + private: + friend class base::RefCounted<SerialIoHandler>; + + base::PlatformFile file_; + + scoped_refptr<net::IOBuffer> pending_read_buffer_; + int pending_read_buffer_len_; + api::serial::ReceiveError read_cancel_reason_; + bool read_canceled_; + + scoped_refptr<net::IOBuffer> pending_write_buffer_; + int pending_write_buffer_len_; + api::serial::SendError write_cancel_reason_; + bool write_canceled_; + + ReadCompleteCallback read_complete_; + WriteCompleteCallback write_complete_; + + DISALLOW_COPY_AND_ASSIGN(SerialIoHandler); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_IO_HANDLER_H_ diff --git a/chrome/browser/extensions/api/serial/serial_io_handler_posix.cc b/chrome/browser/extensions/api/serial/serial_io_handler_posix.cc new file mode 100644 index 0000000..e7ade11 --- /dev/null +++ b/chrome/browser/extensions/api/serial/serial_io_handler_posix.cc @@ -0,0 +1,120 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/serial/serial_io_handler_posix.h" + +#include "base/posix/eintr_wrapper.h" + +namespace extensions { + +// static +scoped_refptr<SerialIoHandler> SerialIoHandler::Create() { + return new SerialIoHandlerPosix(); +} + +void SerialIoHandlerPosix::ReadImpl() { + DCHECK(CalledOnValidThread()); + DCHECK(pending_read_buffer()); + DCHECK_NE(file(), base::kInvalidPlatformFileValue); + + EnsureWatchingReads(); +} + +void SerialIoHandlerPosix::WriteImpl() { + DCHECK(CalledOnValidThread()); + DCHECK(pending_write_buffer()); + DCHECK_NE(file(), base::kInvalidPlatformFileValue); + + EnsureWatchingWrites(); +} + +void SerialIoHandlerPosix::CancelReadImpl() { + DCHECK(CalledOnValidThread()); + is_watching_reads_ = false; + file_read_watcher_.StopWatchingFileDescriptor(); +} + +void SerialIoHandlerPosix::CancelWriteImpl() { + DCHECK(CalledOnValidThread()); + is_watching_writes_ = false; + file_write_watcher_.StopWatchingFileDescriptor(); +} + +SerialIoHandlerPosix::SerialIoHandlerPosix() + : is_watching_reads_(false), + is_watching_writes_(false) { +} + +SerialIoHandlerPosix::~SerialIoHandlerPosix() {} + +void SerialIoHandlerPosix::OnFileCanReadWithoutBlocking(int fd) { + DCHECK(CalledOnValidThread()); + DCHECK_EQ(fd, file()); + + if (pending_read_buffer()) { + int bytes_read = HANDLE_EINTR(read(file(), + pending_read_buffer()->data(), + pending_read_buffer_len())); + if (bytes_read < 0) { + if (errno == ENXIO) { + ReadCompleted(0, api::serial::RECEIVE_ERROR_DEVICE_LOST); + } else { + ReadCompleted(0, api::serial::RECEIVE_ERROR_SYSTEM_ERROR); + } + } else if (bytes_read == 0) { + ReadCompleted(0, api::serial::RECEIVE_ERROR_DEVICE_LOST); + } else { + ReadCompleted(bytes_read, api::serial::RECEIVE_ERROR_NONE); + } + } else { + // Stop watching the fd if we get notifications with no pending + // reads or writes to avoid starving the message loop. + is_watching_reads_ = false; + file_read_watcher_.StopWatchingFileDescriptor(); + } +} + +void SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking(int fd) { + DCHECK(CalledOnValidThread()); + DCHECK_EQ(fd, file()); + + if (pending_write_buffer()) { + int bytes_written = HANDLE_EINTR(write(file(), + pending_write_buffer()->data(), + pending_write_buffer_len())); + if (bytes_written < 0) { + WriteCompleted(0, api::serial::SEND_ERROR_SYSTEM_ERROR); + } else { + WriteCompleted(bytes_written, api::serial::SEND_ERROR_NONE); + } + } else { + // Stop watching the fd if we get notifications with no pending + // writes to avoid starving the message loop. + is_watching_writes_ = false; + file_write_watcher_.StopWatchingFileDescriptor(); + } +} + +void SerialIoHandlerPosix::EnsureWatchingReads() { + DCHECK(CalledOnValidThread()); + DCHECK_NE(file(), base::kInvalidPlatformFileValue); + if (!is_watching_reads_) { + is_watching_reads_ = base::MessageLoopForIO::current()->WatchFileDescriptor( + file(), true, base::MessageLoopForIO::WATCH_READ, + &file_read_watcher_, this); + } +} + +void SerialIoHandlerPosix::EnsureWatchingWrites() { + DCHECK(CalledOnValidThread()); + DCHECK_NE(file(), base::kInvalidPlatformFileValue); + if (!is_watching_writes_) { + is_watching_writes_ = + base::MessageLoopForIO::current()->WatchFileDescriptor( + file(), true, base::MessageLoopForIO::WATCH_WRITE, + &file_write_watcher_, this); + } +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/serial/serial_io_handler_posix.h b/chrome/browser/extensions/api/serial/serial_io_handler_posix.h new file mode 100644 index 0000000..7b0bd64 --- /dev/null +++ b/chrome/browser/extensions/api/serial/serial_io_handler_posix.h @@ -0,0 +1,48 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_IO_HANDLER_POSIX_H_ +#define CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_IO_HANDLER_POSIX_H_ + +#include "base/message_loop/message_loop.h" +#include "chrome/browser/extensions/api/serial/serial_io_handler.h" + +namespace extensions { + +class SerialIoHandlerPosix : public SerialIoHandler, + public base::MessageLoopForIO::Watcher { + protected: + // SerialIoHandler impl. + virtual void ReadImpl() OVERRIDE; + virtual void WriteImpl() OVERRIDE; + virtual void CancelReadImpl() OVERRIDE; + virtual void CancelWriteImpl() OVERRIDE; + + private: + friend class SerialIoHandler; + + SerialIoHandlerPosix(); + virtual ~SerialIoHandlerPosix(); + + // base::MessageLoopForIO::Watcher implementation. + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + + void EnsureWatchingReads(); + void EnsureWatchingWrites(); + + base::MessageLoopForIO::FileDescriptorWatcher file_read_watcher_; + base::MessageLoopForIO::FileDescriptorWatcher file_write_watcher_; + + // Flags indicating if the message loop is watching the device for IO events. + bool is_watching_reads_; + bool is_watching_writes_; + + DISALLOW_COPY_AND_ASSIGN(SerialIoHandlerPosix); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_IO_HANDLER_POSIX_H_ + diff --git a/chrome/browser/extensions/api/serial/serial_io_handler_win.cc b/chrome/browser/extensions/api/serial/serial_io_handler_win.cc new file mode 100644 index 0000000..b083fed --- /dev/null +++ b/chrome/browser/extensions/api/serial/serial_io_handler_win.cc @@ -0,0 +1,145 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/serial/serial_io_handler_win.h" + +#include <windows.h> + +namespace extensions { + +// static +scoped_refptr<SerialIoHandler> SerialIoHandler::Create() { + return new SerialIoHandlerWin(); +} + +void SerialIoHandlerWin::InitializeImpl() { + DCHECK(!comm_context_); + DCHECK(!read_context_); + DCHECK(!write_context_); + + base::MessageLoopForIO::current()->RegisterIOHandler(file(), this); + + comm_context_.reset(new base::MessageLoopForIO::IOContext()); + comm_context_->handler = this; + memset(&comm_context_->overlapped, 0, sizeof(comm_context_->overlapped)); + + read_context_.reset(new base::MessageLoopForIO::IOContext()); + read_context_->handler = this; + memset(&read_context_->overlapped, 0, sizeof(read_context_->overlapped)); + + write_context_.reset(new base::MessageLoopForIO::IOContext()); + write_context_->handler = this; + memset(&write_context_->overlapped, 0, sizeof(write_context_->overlapped)); +} + +void SerialIoHandlerWin::ReadImpl() { + DCHECK(CalledOnValidThread()); + DCHECK(pending_read_buffer()); + DCHECK_NE(file(), base::kInvalidPlatformFileValue); + + DWORD errors; + COMSTAT status; + if (!ClearCommError(file(), &errors, &status) || errors != 0) { + QueueReadCompleted(0, api::serial::RECEIVE_ERROR_SYSTEM_ERROR); + return; + } + + SetCommMask(file(), EV_RXCHAR); + + event_mask_ = 0; + BOOL ok = ::WaitCommEvent(file(), &event_mask_, &comm_context_->overlapped); + if (!ok && GetLastError() != ERROR_IO_PENDING) { + QueueReadCompleted(0, api::serial::RECEIVE_ERROR_SYSTEM_ERROR); + } + is_comm_pending_ = true; +} + +void SerialIoHandlerWin::WriteImpl() { + DCHECK(CalledOnValidThread()); + DCHECK(pending_write_buffer()); + DCHECK_NE(file(), base::kInvalidPlatformFileValue); + + BOOL ok = ::WriteFile(file(), + pending_write_buffer()->data(), + pending_write_buffer_len(), NULL, + &write_context_->overlapped); + if (!ok && GetLastError() != ERROR_IO_PENDING) { + QueueWriteCompleted(0, api::serial::SEND_ERROR_SYSTEM_ERROR); + } +} + +void SerialIoHandlerWin::CancelReadImpl() { + DCHECK(CalledOnValidThread()); + DCHECK_NE(file(), base::kInvalidPlatformFileValue); + if (is_comm_pending_) { + ::CancelIoEx(file(), &comm_context_->overlapped); + } else { + ::CancelIoEx(file(), &read_context_->overlapped); + } +} + +void SerialIoHandlerWin::CancelWriteImpl() { + DCHECK(CalledOnValidThread()); + DCHECK_NE(file(), base::kInvalidPlatformFileValue); + ::CancelIoEx(file(), &write_context_->overlapped); +} + +SerialIoHandlerWin::SerialIoHandlerWin() + : event_mask_(0), + is_comm_pending_(false) { +} + +SerialIoHandlerWin::~SerialIoHandlerWin() { +} + +void SerialIoHandlerWin::OnIOCompleted( + base::MessageLoopForIO::IOContext* context, + DWORD bytes_transferred, + DWORD error) { + DCHECK(CalledOnValidThread()); + if (context == comm_context_) { + is_comm_pending_ = false; + if (read_canceled()) { + ReadCompleted(bytes_transferred, read_cancel_reason()); + } else if (error != ERROR_SUCCESS && error == ERROR_OPERATION_ABORTED) { + ReadCompleted(0, api::serial::RECEIVE_ERROR_SYSTEM_ERROR); + } else if (pending_read_buffer()) { + BOOL ok = ::ReadFile(file(), + pending_read_buffer()->data(), + pending_read_buffer_len(), + NULL, + &read_context_->overlapped); + if (!ok && GetLastError() != ERROR_IO_PENDING) { + ReadCompleted(0, api::serial::RECEIVE_ERROR_SYSTEM_ERROR); + } + } + } else if (context == read_context_) { + if (read_canceled()) { + ReadCompleted(bytes_transferred, read_cancel_reason()); + } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) { + ReadCompleted(0, api::serial::RECEIVE_ERROR_SYSTEM_ERROR); + } else { + ReadCompleted( + bytes_transferred, + error == ERROR_SUCCESS ? api::serial::RECEIVE_ERROR_NONE + : api::serial::RECEIVE_ERROR_SYSTEM_ERROR); + } + } else if (context == write_context_) { + DCHECK(pending_write_buffer()); + if (write_canceled()) { + WriteCompleted(0, write_cancel_reason()); + } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) { + WriteCompleted(0, api::serial::SEND_ERROR_SYSTEM_ERROR); + } else { + WriteCompleted( + bytes_transferred, + error == ERROR_SUCCESS ? api::serial::SEND_ERROR_NONE + : api::serial::SEND_ERROR_SYSTEM_ERROR); + } + } else { + NOTREACHED() << "Invalid IOContext"; + } +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/serial/serial_io_handler_win.h b/chrome/browser/extensions/api/serial/serial_io_handler_win.h new file mode 100644 index 0000000..16cb723 --- /dev/null +++ b/chrome/browser/extensions/api/serial/serial_io_handler_win.h @@ -0,0 +1,58 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_IO_HANDLER_WIN_H_ +#define CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_IO_HANDLER_WIN_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "chrome/browser/extensions/api/serial/serial_io_handler.h" + +namespace extensions { + +class SerialIoHandlerWin : public SerialIoHandler, + public base::MessageLoopForIO::IOHandler { + protected: + // SerialIoHandler implementation. + virtual void InitializeImpl() OVERRIDE; + virtual void ReadImpl() OVERRIDE; + virtual void WriteImpl() OVERRIDE; + virtual void CancelReadImpl() OVERRIDE; + virtual void CancelWriteImpl() OVERRIDE; + + private: + friend class SerialIoHandler; + + SerialIoHandlerWin(); + virtual ~SerialIoHandlerWin(); + + // base::MessageLoopForIO::IOHandler implementation. + virtual void OnIOCompleted(base::MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, + DWORD error) OVERRIDE; + + // Context used for asynchronous WaitCommEvent calls. + scoped_ptr<base::MessageLoopForIO::IOContext> comm_context_; + + // Context used for overlapped reads. + scoped_ptr<base::MessageLoopForIO::IOContext> read_context_; + + // Context used for overlapped writes. + scoped_ptr<base::MessageLoopForIO::IOContext> write_context_; + + // Asynchronous event mask state + DWORD event_mask_; + + // Indicates if a pending read is waiting on initial data arrival via + // WaitCommEvent, as opposed to waiting on actual ReadFile completion + // after a corresponding WaitCommEvent has completed. + bool is_comm_pending_; + + DISALLOW_COPY_AND_ASSIGN(SerialIoHandlerWin); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_SERIAL_SERIAL_IO_HANDLER_WIN_H_ + diff --git a/chrome/browser/extensions/extension_function_histogram_value.h b/chrome/browser/extensions/extension_function_histogram_value.h index de232509..23af274 100644 --- a/chrome/browser/extensions/extension_function_histogram_value.h +++ b/chrome/browser/extensions/extension_function_histogram_value.h @@ -132,7 +132,7 @@ enum HistogramValue { INPUTMETHODPRIVATE_GET, USB_CLOSEDEVICE, TTS_STOP, - SERIAL_GETPORTS, + DELETED_SERIAL_GETPORTS, DELETED_FILEBROWSERPRIVATE_CLEARDRIVECACHE, SERIAL_GETCONTROLSIGNALS, DEVELOPERPRIVATE_ENABLE, @@ -177,7 +177,7 @@ enum HistogramValue { STORAGE_SET, FONTSETTINGS_GETDEFAULTFONTSIZE, EXTENSION_SETUPDATEURLDATA, - SERIAL_WRITE, + DELETED_SERIAL_WRITE, IDLE_QUERYSTATE, DELETED_EXPERIMENTAL_RLZ_GETACCESSPOINTRLZ, WEBSTOREPRIVATE_SETSTORELOGIN, @@ -396,7 +396,7 @@ enum HistogramValue { TABS_DETECTLANGUAGE, METRICSPRIVATE_RECORDVALUE, BOOKMARKMANAGERPRIVATE_SORTCHILDREN, - SERIAL_READ, + DELETED_SERIAL_READ, APP_CURRENTWINDOWINTERNAL_MAXIMIZE, EXPERIMENTAL_DISCOVERY_CLEARALLSUGGESTIONS, DELETED_MANAGEDMODEPRIVATE_ENTER, @@ -693,6 +693,12 @@ enum HistogramValue { SCREENLOCKPRIVATE_SHOWMESSAGE, FEEDBACKPRIVATE_GETHISTOGRAMS, SYSTEM_NETWORK_GETNETWORKINTERFACES, + SERIAL_GETDEVICES, + SERIAL_UPDATE, + SERIAL_SETPAUSED, + SERIAL_GETINFO, + SERIAL_GETCONNECTIONS, + SERIAL_SEND, ENUM_BOUNDARY // Last entry: Add new entries above. }; diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index 8014ba5..0aac4ad 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -450,6 +450,14 @@ 'browser/extensions/api/serial/serial_connection.h', 'browser/extensions/api/serial/serial_connection_posix.cc', 'browser/extensions/api/serial/serial_connection_win.cc', + 'browser/extensions/api/serial/serial_event_dispatcher.cc', + 'browser/extensions/api/serial/serial_event_dispatcher.h', + 'browser/extensions/api/serial/serial_io_handler.cc', + 'browser/extensions/api/serial/serial_io_handler.h', + 'browser/extensions/api/serial/serial_io_handler_posix.cc', + 'browser/extensions/api/serial/serial_io_handler_posix.h', + 'browser/extensions/api/serial/serial_io_handler_win.cc', + 'browser/extensions/api/serial/serial_io_handler_win.h', 'browser/extensions/api/serial/serial_port_enumerator.cc', 'browser/extensions/api/serial/serial_port_enumerator.h', 'browser/extensions/api/serial/serial_port_enumerator_posix.cc', diff --git a/chrome/common/extensions/api/serial.idl b/chrome/common/extensions/api/serial.idl index c6429ff..919de55 100644 --- a/chrome/common/extensions/api/serial.idl +++ b/chrome/common/extensions/api/serial.idl @@ -6,13 +6,33 @@ // connected to a serial port. namespace serial { - callback GetPortsCallback = void (DOMString[] ports); + dictionary DeviceInfo { + // The device's system path. This should be passed as the <code>path</code> + // argument to <code>chrome.serial.open</code> in order to open this device. + DOMString path; + }; + + callback GetDevicesCallback = void (DeviceInfo[] ports); + + enum DataBits { seven, eight }; + enum ParityBit { no, odd, even }; + enum StopBits { one, two }; + + dictionary ConnectionOptions { + // Flag indicating whether or not the connection should be left open when + // the application is suspended (see + // <a href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App + // Lifecycle</a>). The default value is "false." When the application is + // loaded, any serial connections previously opened with persistent=true + // can be fetched with <code>getConnections</code>. + boolean? persistent; + + // An application-defined string to associate with the connection. + DOMString? name; - enum DataBit { sevenbit, eightbit }; - enum ParityBit { noparity, oddparity, evenparity }; - enum StopBit { onestopbit, twostopbit }; + // The size of the buffer used to receive data. The default value is 4096. + long? bufferSize; - dictionary OpenOptions { // The requested bitrate of the connection to be opened. For compatibility // with the widest range of hardware, this number should match one of // commonly-available bitrates, such as 110, 300, 1200, 2400, 4800, 9600, @@ -21,45 +41,125 @@ namespace serial { // bitrate, even if the port itself supports that bitrate. <code>9600</code> // will be passed by default. long? bitrate; - // <code>"eightbit"</code> will be passed by default. - DataBit? dataBit; - // <code>"noparity"</code> will be passed by default. + + // <code>"eight"</code> will be passed by default. + DataBits? dataBits; + + // <code>"no"</code> will be passed by default. ParityBit? parityBit; - // <code>"onestopbit"</code> will be passed by default. - StopBit? stopBit; + + // <code>"one"</code> will be passed by default. + StopBits? stopBits; + + // Flag indicating whether or not to enable RTS/CTS hardware flow control. + // Defaults to false. + boolean? ctsFlowControl; + + // The maximum amount of time (in milliseconds) to wait for new data before + // raising an <code>onReceiveError</code> event with a "timeout" error. + // If zero, receive timeout errors will not be raised for the connection. + // Defaults to 0. + long? receiveTimeout; + + // The maximum amount of time (in milliseconds) to wait for a + // <code>send</code> operation to complete before calling the callback with + // a "timeout" error. If zero, send timeout errors will not be triggered. + // Defaults to 0. + long? sendTimeout; }; + // Result of the <code>open</code> method. dictionary OpenInfo { // The id of the opened connection. long connectionId; }; + // Callback from the <code>open</code> method; callback OpenCallback = void (OpenInfo openInfo); + // Callback from the <code>update</code> method. + callback UpdateCallback = void (boolean result); + // Returns true if operation was successful. callback CloseCallback = void (boolean result); - dictionary ReadInfo { - // The number of bytes received, or a negative number if an error occurred. - // This number will be smaller than the number of bytes requested in the - // original read call if the call would need to block to read that number - // of bytes. - long bytesRead; + // Callback from the <code>setPaused</code> method. + callback SetPausedCallback = void (); - // The data received. - ArrayBuffer data; + // Result of the <code>getInfo</code> method. + dictionary ConnectionInfo { + // The id of the serial port connection. + long connectionId; + + // Flag indicating whether the connection is blocked from firing onReceive + // events. + boolean paused; + + // See <code>ConnectionOptions.persistent</code> + boolean persistent; + + // See <code>ConnectionOptions.name</code> + DOMString name; + + // See <code>ConnectionOptions.bufferSize</code> + long bufferSize; + + // See <code>ConnectionOptions.receiveTimeout</code> + long receiveTimeout; + + // See <code>ConnectionOptions.sendTimeout</code> + long sendTimeout; + + // See <code>ConnectionOptions.bitrate</code>. This field may be omitted + // or inaccurate if a non-standard bitrate is in use, or if an error + // occurred while querying the underlying device. + long? bitrate; + + // See <code>ConnectionOptions.dataBits</code>. This field may be omitted + // if an error occurred while querying the underlying device. + DataBits? dataBits; + + // See <code>ConnectionOptions.parityBit</code>. This field may be omitted + // if an error occurred while querying the underlying device. + ParityBit? parityBit; + + // See <code>ConnectionOptions.stopBits</code>. This field may be omitted + // if an error occurred while querying the underlying device. + StopBits? stopBits; + + // See <code>ConnectionOptions.ctsFlowControl</code>. This field may be + // omitted if an error occurred while querying the underlying device. + boolean? ctsFlowControl; }; - callback ReadCallback = void (ReadInfo readInfo); + callback GetInfoCallback = void (ConnectionInfo connectionInfo); + + callback GetConnectionsCallback = void (ConnectionInfo[] connectionInfos); - dictionary WriteInfo { - // The number of bytes written. - long bytesWritten; + enum SendError { + // The connection was closed. + closed, + + // A send was already pending. + pending, + + // The send timed out. + timeout, + + // A system error occurred and the connection may be unrecoverable. + system_error }; - callback WriteCallback = void (WriteInfo writeInfo); + dictionary SendInfo { + // The number of bytes sent. + long bytesSent; + + // An error code if an error occurred. + SendError? error; + }; + + callback SendCallback = void (SendInfo sendInfo); - // Returns true if operation was successful. callback FlushCallback = void (boolean result); // Boolean true = mark signal (negative serial voltage). @@ -69,78 +169,139 @@ namespace serial { // change. Signals not included in the dictionary will be left unchanged. // // GetControlSignals includes all receivable signals. - dictionary ControlSignalOptions { + dictionary ControlSignals { // Serial control signals that your machine can send. Missing fields will - // be set to false. + // not be changed. boolean? dtr; boolean? rts; - // Serial control signals that your machine can receive. If a get operation - // fails, success will be false, and these fields will be absent. + // Serial control signals that your machine can receive. // // DCD (Data Carrier Detect) is equivalent to RLSD (Receive Line Signal // Detect) on some platforms. boolean? dcd; boolean? cts; + boolean? ri; + boolean? dsr; }; // Returns a snapshot of current control signals. - callback GetControlSignalsCallback = void (ControlSignalOptions options); + callback GetControlSignalsCallback = void (ControlSignals signals); // Returns true if operation was successful. callback SetControlSignalsCallback = void (boolean result); + // Data from an <code>onReceive</code> event. + dictionary ReceiveInfo { + // The connection identifier. + long connectionId; + + // The data received. + ArrayBuffer data; + }; + + enum ReceiveError { + // The connection was closed. + closed, + + // No data has been received for <code>receiveTimeout</code> milliseconds. + timeout, + + // The device was most likely disconnected from the host. + device_lost, + + // A system error occurred and the connection may be unrecoverable. + system_error + }; + + // Data from an <code>onReceiveError</code> event. + dictionary ReceiveErrorInfo { + // The connection identifier. + long connectionId; + + // An error code indicating what went wrong. + ReceiveError error; + }; + interface Functions { - // Returns names of valid ports on this machine, each of which is likely to - // be valid to pass as the port argument to open(). The list is regenerated - // each time this method is called, as port validity is dynamic. - // - // |callback| : Called with the list of ports. - static void getPorts(GetPortsCallback callback); + // Returns information about available serial devices on the system. + // The list is regenerated each time this method is called. + // |callback| : Called with the list of <code>DeviceInfo</code> objects. + static void getDevices(GetDevicesCallback callback); // Opens a connection to the given serial port. - // |port| : The name of the serial port to open. - // |options| : Connection options. + // |path| : The system path of the serial port to open. + // |options| : Port configuration options. // |callback| : Called when the connection has been opened. - static void open(DOMString port, - optional OpenOptions options, + static void open(DOMString path, + optional ConnectionOptions options, OpenCallback callback); + // Update the option settings on an open serial port. + // |connectionId| : The id of the opened connection. + // |options| : Port configuration options. + // |callback| : Called when the configuation has completed. + static void update(long connectionId, + ConnectionOptions options, + UpdateCallback callback); + // Closes an open connection. // |connectionId| : The id of the opened connection. // |callback| : Called when the connection has been closed. - static void close(long connectionId, - CloseCallback callback); + static void close(long connectionId, CloseCallback callback); - // Reads a byte from the given connection. - // |connectionId| : The id of the connection. - // |bytesToRead| : The number of bytes to read. - // |callback| : Called when all the requested bytes have been read or - // when the read blocks. - static void read(long connectionId, - long bytesToRead, - ReadCallback callback); - - // Writes a string to the given connection. + // Pauses or unpauses an open connection. + // |connectionId| : The id of the opened connection. + // |paused| : Flag to indicate whether to pause or unpause. + // |callback| : Called when the connection has been successfully paused or + // unpaused. + static void setPaused(long connectionId, + boolean paused, + SetPausedCallback callback); + + // Retrieves the state of a given connection. + // |connectionId| : The id of the opened connection. + // |callback| : Called with connection state information when available. + static void getInfo(long connectionId, GetInfoCallback callback); + + // Retrieves the list of currently opened serial port connections owned by + // the application. + // |callback| : Called with the list of connections when available. + static void getConnections(GetConnectionsCallback callback); + + // Writes data to the given connection. // |connectionId| : The id of the connection. - // |data| : The string to write. - // |callback| : Called when the string has been written. - static void write(long connectionId, - ArrayBuffer data, - WriteCallback callback); + // |data| : The data to send. + // |callback| : Called when the operation has completed. + static void send(long connectionId, + ArrayBuffer data, + SendCallback callback); // Flushes all bytes in the given connection's input and output buffers. - // |connectionId| : The id of the connection. - // |callback| : Called when the flush is complete. - static void flush(long connectionId, - FlushCallback callback); + static void flush(long connectionId, FlushCallback callback); + // Retrieves the state of control signals on a given connection. + // |connectionId| : The id of the connection. + // |callback| : Called when the control signals are available. static void getControlSignals(long connectionId, GetControlSignalsCallback callback); + // Sets the state of control signals on a given connection. + // |connectionId| : The id of the connection. + // |callback| : Called once the control signals have been set. static void setControlSignals(long connectionId, - ControlSignalOptions options, + ControlSignals signals, SetControlSignalsCallback callback); }; + interface Events { + // Event raised when data has been read from the connection. + // |info| : Event data. + static void onReceive(ReceiveInfo info); + + // Event raised when an error occurred while the runtime was waiting for + // data on the serial port. Once this event is raised, the connection is set + // to <code>paused</code>. + static void onReceiveError(ReceiveErrorInfo info); + }; }; diff --git a/chrome/test/data/extensions/api_test/serial/api/background.js b/chrome/test/data/extensions/api_test/serial/api/background.js index 67c0e30..bdc3df7 100644 --- a/chrome/test/data/extensions/api_test/serial/api/background.js +++ b/chrome/test/data/extensions/api_test/serial/api/background.js @@ -23,19 +23,19 @@ var createTestArrayBuffer = function() { var testSerial = function() { var serialPort = null; var connectionId = -1; - var readTries = 10; - var writeBuffer = createTestArrayBuffer(); - var writeBufferUint8View = new Uint8Array(writeBuffer); - var bufferLength = writeBufferUint8View.length; - var readBuffer = new ArrayBuffer(bufferLength); - var readBufferUint8View = new Uint8Array(readBuffer); - var bytesToRead = bufferLength; + var receiveTries = 10; + var sendBuffer = createTestArrayBuffer(); + var sendBufferUint8View = new Uint8Array(sendBuffer); + var bufferLength = sendBufferUint8View.length; + var receiveBuffer = new ArrayBuffer(bufferLength); + var receiveBufferUint8View = new Uint8Array(receiveBuffer); + var bytesToReceive = bufferLength; var operation = 0; var doNextOperation = function() { switch (operation++) { case 0: - serial.getPorts(onGetPorts); + serial.getDevices(onGetDevices); break; case 1: var bitrate = 57600; @@ -50,13 +50,9 @@ var testSerial = function() { serial.getControlSignals(connectionId,onGetControlSignals); break; case 4: - serial.write(connectionId, writeBuffer, onWrite); - break; - case 5: - serial.read(connectionId, bytesToRead, onRead); - break; - case 6: - serial.flush(connectionId, onFlush); + serial.onReceive.addListener(onReceive); + serial.onReceiveError.addListener(onReceiveError); + serial.send(connectionId, sendBuffer, onSend); break; case 50: // GOTO 4 EVER serial.close(connectionId, onClose); @@ -84,40 +80,35 @@ var testSerial = function() { doNextOperation(); }; - var onFlush = function(result) { - chrome.test.assertTrue(result); - doNextOperation(); - } - - var onRead = function(readInfo) { - bytesToRead -= readInfo.bytesRead; - var readBufferIndex = bufferLength - readInfo.bytesRead; - var messageUint8View = new Uint8Array(readInfo.data); - for (var i = 0; i < readInfo.bytesRead; i++) - readBufferUint8View[i + readBufferIndex] = messageUint8View[i]; - if (bytesToRead == 0) { - chrome.test.assertEq(writeBufferUint8View, readBufferUint8View, - 'Buffer read was not equal to buffer written.'); + var onReceive = function(receiveInfo) { + var data = new Uint8Array(receiveInfo.data); + bytesToReceive -= data.length; + var receiveBufferIndex = bufferLength - data.length; + for (var i = 0; i < data.length; i++) + receiveBufferUint8View[i + receiveBufferIndex] = data[i]; + if (bytesToReceive == 0) { + chrome.test.assertEq(sendBufferUint8View, receiveBufferUint8View, + 'Buffer received was not equal to buffer sent.'); doNextOperation(); - } else { - if (--readTries > 0) - setTimeout(repeatOperation, 100); - else - chrome.test.assertTrue( - false, - 'read() failed to return requested number of bytes.'); + } else if (--receiveTries <= 0) { + chrome.test.fail('receive() failed to return requested number of bytes.'); } }; - var onWrite = function(writeInfo) { - chrome.test.assertEq(bufferLength, writeInfo.bytesWritten, - 'Failed to write byte.'); - doNextOperation(); + var onReceiveError = function(errorInfo) { + chrome.test.fail('Failed to receive serial data'); + }; + + var onSend = function(sendInfo) { + chrome.test.assertEq(bufferLength, sendInfo.bytesSent, + 'Failed to send byte.'); }; var onGetControlSignals = function(options) { - chrome.test.assertTrue(typeof options.dcd != 'undefined'); - chrome.test.assertTrue(typeof options.cts != 'undefined'); + chrome.test.assertTrue(typeof options.dcd != 'undefined', "No DCD set"); + chrome.test.assertTrue(typeof options.cts != 'undefined', "No CTS set"); + chrome.test.assertTrue(typeof options.dtr != 'undefined', "No DTR set"); + chrome.test.assertTrue(typeof options.ri != 'undefined', "No RI set"); doNextOperation(); }; @@ -132,18 +123,18 @@ var testSerial = function() { doNextOperation(); }; - var onGetPorts = function(ports) { - if (ports.length > 0) { + var onGetDevices = function(devices) { + if (devices.length > 0) { var portNumber = 0; - while (portNumber < ports.length) { - if (shouldSkipPort(ports[portNumber])) { + while (portNumber < devices.length) { + if (shouldSkipPort(devices[portNumber].path)) { portNumber++; continue; } else break; } - if (portNumber < ports.length) { - serialPort = ports[portNumber]; + if (portNumber < devices.length) { + serialPort = devices[portNumber].path; doNextOperation(); } else { // We didn't find a port that we think we should try. diff --git a/chrome/test/data/extensions/api_test/serial/real_hardware/background.js b/chrome/test/data/extensions/api_test/serial/real_hardware/background.js index 27e033d..19d86d9 100644 --- a/chrome/test/data/extensions/api_test/serial/real_hardware/background.js +++ b/chrome/test/data/extensions/api_test/serial/real_hardware/background.js @@ -2,73 +2,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// TODO(miket): opening Bluetooth ports on OSX is unreliable. Investigate. -function shouldSkipPort(portName) { - return portName.match(/[Bb]luetooth/); -} - -var testGetPorts = function() { - var onGetPorts = function(ports) { +var testGetDevices = function() { + var onGetDevices = function(devices) { // Any length is potentially valid, because we're on unknown hardware. But - // we are testing at least that the ports member was filled in, so it's + // we are testing at least that the devices member was filled in, so it's // still a somewhat meaningful test. - chrome.test.assertTrue(ports.length >= 0); + chrome.test.assertTrue(devices.length >= 0); chrome.test.succeed(); } - chrome.serial.getPorts(onGetPorts); + chrome.serial.getDevices(onGetDevices); }; -var testMaybeOpenPort = function() { - var onGetPorts = function(ports) { - // We're testing as much as we can here without actually assuming the - // existence of attached hardware. - // - // TODO(miket): is there any chance that just opening a serial port but not - // doing anything could be harmful to devices attached to a developer's - // machine? - if (ports.length > 0) { - var currentPort = 0; - - var onFinishedWithPort = function() { - if (currentPort >= ports.length) - chrome.test.succeed(); - else - testPort(); - }; - - var onClose = function(r) { - onFinishedWithPort(); - }; - - var onOpen = function(connectionInfo) { - var id = connectionInfo.connectionId; - if (id > 0) - chrome.serial.close(id, onClose); - else - onFinishedWithPort(); - }; - - var testPort = function() { - var port = ports[currentPort++]; - - if (shouldSkipPort(port)) { - onFinishedWithPort(); - } else { - console.log("Opening serial device " + port); - chrome.serial.open(port, onOpen); - } - } - - testPort(); - } else { - // There aren't any valid ports on this machine. That's OK. - chrome.test.succeed(); - } - } - - chrome.serial.getPorts(onGetPorts); -}; +// TODO(rockot): As infrastructure is built for testing device APIs in Chrome, +// we should obviously build real hardware tests here. For now, no attempt is +// made to open real devices on the test system. -var tests = [testGetPorts, testMaybeOpenPort]; +var tests = [testGetDevices]; chrome.test.runTests(tests); |