blob: 4bae9c139d7b6c7aac5cd0fd77cf991709759052 [file] [log] [blame]
// Copyright 2014 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 "extensions/browser/api/cast_channel/cast_channel_api.h"
#include <limits>
#include <string>
#include "base/json/json_writer.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/default_tick_clock.h"
#include "base/values.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/api/cast_channel/cast_socket.h"
#include "extensions/browser/api/cast_channel/logger.h"
#include "extensions/browser/api/cast_channel/logging.pb.h"
#include "extensions/browser/event_router.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "url/gurl.h"
// Default timeout interval for connection setup.
// Used if not otherwise specified at ConnectInfo::timeout.
const int kDefaultConnectTimeoutMillis = 5000; // 5 seconds.
namespace extensions {
namespace Close = cast_channel::Close;
namespace OnError = cast_channel::OnError;
namespace OnMessage = cast_channel::OnMessage;
namespace Open = cast_channel::Open;
namespace Send = cast_channel::Send;
using cast_channel::CastSocket;
using cast_channel::ChannelAuthType;
using cast_channel::ChannelError;
using cast_channel::ChannelInfo;
using cast_channel::ConnectInfo;
using cast_channel::ErrorInfo;
using cast_channel::LastErrors;
using cast_channel::Logger;
using cast_channel::MessageInfo;
using cast_channel::ReadyState;
using content::BrowserThread;
namespace {
// T is an extension dictionary (MessageInfo or ChannelInfo)
template <class T>
std::string ParamToString(const T& info) {
scoped_ptr<base::DictionaryValue> dict = info.ToValue();
std::string out;
base::JSONWriter::Write(dict.get(), &out);
return out;
}
// Fills |channel_info| from the destination and state of |socket|.
void FillChannelInfo(const CastSocket& socket, ChannelInfo* channel_info) {
DCHECK(channel_info);
channel_info->channel_id = socket.id();
channel_info->url = socket.CastUrl();
const net::IPEndPoint& ip_endpoint = socket.ip_endpoint();
channel_info->connect_info.ip_address = ip_endpoint.ToStringWithoutPort();
channel_info->connect_info.port = ip_endpoint.port();
channel_info->connect_info.auth = socket.channel_auth();
channel_info->ready_state = socket.ready_state();
channel_info->error_state = socket.error_state();
}
// Fills |error_info| from |error_state| and |last_errors|.
void FillErrorInfo(ChannelError error_state,
const LastErrors& last_errors,
ErrorInfo* error_info) {
error_info->error_state = error_state;
if (last_errors.event_type != cast_channel::proto::EVENT_TYPE_UNKNOWN)
error_info->event_type.reset(new int(last_errors.event_type));
if (last_errors.challenge_reply_error_type !=
cast_channel::proto::CHALLENGE_REPLY_ERROR_NONE) {
error_info->challenge_reply_error_type.reset(
new int(last_errors.challenge_reply_error_type));
}
if (last_errors.net_return_value <= 0)
error_info->net_return_value.reset(new int(last_errors.net_return_value));
if (last_errors.nss_error_code < 0)
error_info->nss_error_code.reset(new int(last_errors.nss_error_code));
}
bool IsValidConnectInfoPort(const ConnectInfo& connect_info) {
return connect_info.port > 0 && connect_info.port <
std::numeric_limits<uint16_t>::max();
}
bool IsValidConnectInfoAuth(const ConnectInfo& connect_info) {
return connect_info.auth == cast_channel::CHANNEL_AUTH_TYPE_SSL_VERIFIED ||
connect_info.auth == cast_channel::CHANNEL_AUTH_TYPE_SSL;
}
bool IsValidConnectInfoIpAddress(const ConnectInfo& connect_info) {
net::IPAddressNumber ip_address;
return net::ParseIPLiteralToNumber(connect_info.ip_address, &ip_address);
}
} // namespace
CastChannelAPI::CastChannelAPI(content::BrowserContext* context)
: browser_context_(context),
logger_(
new Logger(scoped_ptr<base::TickClock>(new base::DefaultTickClock),
base::TimeTicks::UnixEpoch())) {
DCHECK(browser_context_);
}
// static
CastChannelAPI* CastChannelAPI::Get(content::BrowserContext* context) {
return BrowserContextKeyedAPIFactory<CastChannelAPI>::Get(context);
}
scoped_refptr<Logger> CastChannelAPI::GetLogger() {
return logger_;
}
static base::LazyInstance<BrowserContextKeyedAPIFactory<CastChannelAPI> >
g_factory = LAZY_INSTANCE_INITIALIZER;
// static
BrowserContextKeyedAPIFactory<CastChannelAPI>*
CastChannelAPI::GetFactoryInstance() {
return g_factory.Pointer();
}
scoped_ptr<CastSocket> CastChannelAPI::CreateCastSocket(
const std::string& extension_id, const net::IPEndPoint& ip_endpoint,
ChannelAuthType channel_auth, const base::TimeDelta& timeout) {
if (socket_for_test_.get()) {
return socket_for_test_.Pass();
} else {
return scoped_ptr<CastSocket>(
new CastSocket(extension_id,
ip_endpoint,
channel_auth,
this,
ExtensionsBrowserClient::Get()->GetNetLog(),
timeout,
logger_));
}
}
void CastChannelAPI::SetSocketForTest(scoped_ptr<CastSocket> socket_for_test) {
socket_for_test_ = socket_for_test.Pass();
}
void CastChannelAPI::OnError(const CastSocket* socket,
cast_channel::ChannelError error_state,
const cast_channel::LastErrors& last_errors) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
ChannelInfo channel_info;
FillChannelInfo(*socket, &channel_info);
channel_info.error_state = error_state;
ErrorInfo error_info;
FillErrorInfo(error_state, last_errors, &error_info);
scoped_ptr<base::ListValue> results =
OnError::Create(channel_info, error_info);
scoped_ptr<Event> event(new Event(OnError::kEventName, results.Pass()));
extensions::EventRouter::Get(browser_context_)
->DispatchEventToExtension(socket->owner_extension_id(), event.Pass());
}
void CastChannelAPI::OnMessage(const CastSocket* socket,
const MessageInfo& message_info) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
ChannelInfo channel_info;
FillChannelInfo(*socket, &channel_info);
scoped_ptr<base::ListValue> results =
OnMessage::Create(channel_info, message_info);
VLOG(1) << "Sending message " << ParamToString(message_info)
<< " to channel " << ParamToString(channel_info);
scoped_ptr<Event> event(new Event(OnMessage::kEventName, results.Pass()));
extensions::EventRouter::Get(browser_context_)
->DispatchEventToExtension(socket->owner_extension_id(), event.Pass());
}
CastChannelAPI::~CastChannelAPI() {}
CastChannelAsyncApiFunction::CastChannelAsyncApiFunction()
: manager_(NULL), error_(cast_channel::CHANNEL_ERROR_NONE) { }
CastChannelAsyncApiFunction::~CastChannelAsyncApiFunction() { }
bool CastChannelAsyncApiFunction::PrePrepare() {
manager_ = ApiResourceManager<CastSocket>::Get(browser_context());
return true;
}
bool CastChannelAsyncApiFunction::Respond() {
return error_ != cast_channel::CHANNEL_ERROR_NONE;
}
CastSocket* CastChannelAsyncApiFunction::GetSocketOrCompleteWithError(
int channel_id) {
CastSocket* socket = GetSocket(channel_id);
if (!socket) {
SetResultFromError(channel_id,
cast_channel::CHANNEL_ERROR_INVALID_CHANNEL_ID);
AsyncWorkCompleted();
}
return socket;
}
int CastChannelAsyncApiFunction::AddSocket(CastSocket* socket) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(socket);
DCHECK(manager_);
const int id = manager_->Add(socket);
socket->set_id(id);
return id;
}
void CastChannelAsyncApiFunction::RemoveSocket(int channel_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(manager_);
manager_->Remove(extension_->id(), channel_id);
}
void CastChannelAsyncApiFunction::SetResultFromSocket(
const CastSocket& socket) {
ChannelInfo channel_info;
FillChannelInfo(socket, &channel_info);
error_ = socket.error_state();
SetResultFromChannelInfo(channel_info);
}
void CastChannelAsyncApiFunction::SetResultFromError(int channel_id,
ChannelError error) {
ChannelInfo channel_info;
channel_info.channel_id = channel_id;
channel_info.url = "";
channel_info.ready_state = cast_channel::READY_STATE_CLOSED;
channel_info.error_state = error;
channel_info.connect_info.ip_address = "";
channel_info.connect_info.port = 0;
channel_info.connect_info.auth = cast_channel::CHANNEL_AUTH_TYPE_SSL;
SetResultFromChannelInfo(channel_info);
error_ = error;
}
CastSocket* CastChannelAsyncApiFunction::GetSocket(int channel_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(manager_);
return manager_->Get(extension_->id(), channel_id);
}
void CastChannelAsyncApiFunction::SetResultFromChannelInfo(
const ChannelInfo& channel_info) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SetResult(channel_info.ToValue().release());
}
CastChannelOpenFunction::CastChannelOpenFunction()
: new_channel_id_(0) { }
CastChannelOpenFunction::~CastChannelOpenFunction() { }
// TODO(mfoltz): Remove URL parsing when clients have converted to use
// ConnectInfo.
// Allowed schemes for Cast device URLs.
const char kCastInsecureScheme[] = "cast";
const char kCastSecureScheme[] = "casts";
bool CastChannelOpenFunction::ParseChannelUrl(const GURL& url,
ConnectInfo* connect_info) {
DCHECK(connect_info);
VLOG(2) << "ParseChannelUrl";
bool auth_required = false;
if (url.SchemeIs(kCastSecureScheme)) {
auth_required = true;
} else if (!url.SchemeIs(kCastInsecureScheme)) {
return false;
}
// TODO(mfoltz): Test for IPv6 addresses. Brackets or no brackets?
// TODO(mfoltz): Maybe enforce restriction to IPv4 private and IPv6
// link-local networks
const std::string& path = url.path();
// Shortest possible: //A:B
if (path.size() < 5) {
return false;
}
if (path.find("//") != 0) {
return false;
}
size_t colon = path.find_last_of(':');
if (colon == std::string::npos || colon < 3 || colon > path.size() - 2) {
return false;
}
const std::string& ip_address_str = path.substr(2, colon - 2);
const std::string& port_str = path.substr(colon + 1);
VLOG(2) << "IP: " << ip_address_str << " Port: " << port_str;
int port;
if (!base::StringToInt(port_str, &port))
return false;
connect_info->ip_address = ip_address_str;
connect_info->port = port;
connect_info->auth = auth_required ?
cast_channel::CHANNEL_AUTH_TYPE_SSL_VERIFIED :
cast_channel::CHANNEL_AUTH_TYPE_SSL;
return true;
}
net::IPEndPoint* CastChannelOpenFunction::ParseConnectInfo(
const ConnectInfo& connect_info) {
net::IPAddressNumber ip_address;
CHECK(net::ParseIPLiteralToNumber(connect_info.ip_address, &ip_address));
return new net::IPEndPoint(ip_address, connect_info.port);
}
bool CastChannelOpenFunction::PrePrepare() {
api_ = CastChannelAPI::Get(browser_context());
return CastChannelAsyncApiFunction::PrePrepare();
}
bool CastChannelOpenFunction::Prepare() {
params_ = Open::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(params_.get());
// The connect_info parameter may be a string URL like cast:// or casts:// or
// a ConnectInfo object.
std::string cast_url;
switch (params_->connect_info->GetType()) {
case base::Value::TYPE_STRING:
CHECK(params_->connect_info->GetAsString(&cast_url));
connect_info_.reset(new ConnectInfo);
if (!ParseChannelUrl(GURL(cast_url), connect_info_.get())) {
connect_info_.reset();
SetError("Invalid connect_info (invalid Cast URL " + cast_url + ")");
}
break;
case base::Value::TYPE_DICTIONARY:
connect_info_ = ConnectInfo::FromValue(*(params_->connect_info));
if (!connect_info_.get()) {
SetError("connect_info.auth is required");
}
break;
default:
SetError("Invalid connect_info (unknown type)");
break;
}
if (!connect_info_.get()) {
return false;
}
if (!IsValidConnectInfoPort(*connect_info_)) {
SetError("Invalid connect_info (invalid port)");
} else if (!IsValidConnectInfoAuth(*connect_info_)) {
SetError("Invalid connect_info (invalid auth)");
} else if (!IsValidConnectInfoIpAddress(*connect_info_)) {
SetError("Invalid connect_info (invalid IP address)");
}
if (!GetError().empty()) {
return false;
}
channel_auth_ = connect_info_->auth;
ip_endpoint_.reset(ParseConnectInfo(*connect_info_));
return true;
}
void CastChannelOpenFunction::AsyncWorkStart() {
DCHECK(api_);
DCHECK(ip_endpoint_.get());
scoped_ptr<CastSocket> socket = api_->CreateCastSocket(
extension_->id(),
*ip_endpoint_,
channel_auth_,
base::TimeDelta::FromMilliseconds(connect_info_->timeout.get()
? *connect_info_->timeout
: kDefaultConnectTimeoutMillis));
new_channel_id_ = AddSocket(socket.release());
CastSocket* new_socket = GetSocket(new_channel_id_);
api_->GetLogger()->LogNewSocketEvent(*new_socket);
new_socket->Connect(base::Bind(&CastChannelOpenFunction::OnOpen, this));
}
void CastChannelOpenFunction::OnOpen(int result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
VLOG(1) << "Connect finished, OnOpen invoked.";
CastSocket* socket = GetSocket(new_channel_id_);
if (!socket) {
SetResultFromError(new_channel_id_,
cast_channel::CHANNEL_ERROR_CONNECT_ERROR);
} else {
SetResultFromSocket(*socket);
}
AsyncWorkCompleted();
}
CastChannelSendFunction::CastChannelSendFunction() { }
CastChannelSendFunction::~CastChannelSendFunction() { }
bool CastChannelSendFunction::Prepare() {
params_ = Send::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(params_.get());
if (params_->message.namespace_.empty()) {
SetError("message_info.namespace_ is required");
return false;
}
if (params_->message.source_id.empty()) {
SetError("message_info.source_id is required");
return false;
}
if (params_->message.destination_id.empty()) {
SetError("message_info.destination_id is required");
return false;
}
switch (params_->message.data->GetType()) {
case base::Value::TYPE_STRING:
case base::Value::TYPE_BINARY:
break;
default:
SetError("Invalid type of message_info.data");
return false;
}
return true;
}
void CastChannelSendFunction::AsyncWorkStart() {
CastSocket* socket = GetSocketOrCompleteWithError(
params_->channel.channel_id);
if (socket)
socket->SendMessage(params_->message,
base::Bind(&CastChannelSendFunction::OnSend, this));
}
void CastChannelSendFunction::OnSend(int result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
int channel_id = params_->channel.channel_id;
CastSocket* socket = GetSocket(channel_id);
if (result < 0 || !socket) {
SetResultFromError(channel_id,
cast_channel::CHANNEL_ERROR_SOCKET_ERROR);
} else {
SetResultFromSocket(*socket);
}
AsyncWorkCompleted();
}
CastChannelCloseFunction::CastChannelCloseFunction() { }
CastChannelCloseFunction::~CastChannelCloseFunction() { }
bool CastChannelCloseFunction::Prepare() {
params_ = Close::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(params_.get());
return true;
}
void CastChannelCloseFunction::AsyncWorkStart() {
CastSocket* socket = GetSocketOrCompleteWithError(
params_->channel.channel_id);
if (socket)
socket->Close(base::Bind(&CastChannelCloseFunction::OnClose, this));
}
void CastChannelCloseFunction::OnClose(int result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
VLOG(1) << "CastChannelCloseFunction::OnClose result = " << result;
int channel_id = params_->channel.channel_id;
CastSocket* socket = GetSocket(channel_id);
if (result < 0 || !socket) {
SetResultFromError(channel_id,
cast_channel::CHANNEL_ERROR_SOCKET_ERROR);
} else {
SetResultFromSocket(*socket);
// This will delete |socket|.
RemoveSocket(channel_id);
socket = NULL;
}
AsyncWorkCompleted();
}
CastChannelGetLogsFunction::CastChannelGetLogsFunction() {
}
CastChannelGetLogsFunction::~CastChannelGetLogsFunction() {
}
bool CastChannelGetLogsFunction::PrePrepare() {
api_ = CastChannelAPI::Get(browser_context());
return CastChannelAsyncApiFunction::PrePrepare();
}
bool CastChannelGetLogsFunction::Prepare() {
return true;
}
void CastChannelGetLogsFunction::AsyncWorkStart() {
DCHECK(api_);
size_t length = 0;
scoped_ptr<char[]> out = api_->GetLogger()->GetLogs(&length);
if (out.get()) {
SetResult(new base::BinaryValue(out.Pass(), length));
} else {
SetError("Unable to get logs.");
}
api_->GetLogger()->Reset();
AsyncWorkCompleted();
}
} // namespace extensions