blob: d1f56863645ac771186fdde0160eb82eb218d5a7 [file] [log] [blame]
// Copyright (c) 2012 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 "content/renderer/p2p/ipc_socket_factory.h"
#include "base/compiler_specific.h"
#include "base/debug/trace_event.h"
#include "base/message_loop.h"
#include "base/message_loop_proxy.h"
#include "content/renderer/p2p/socket_client.h"
#include "content/renderer/p2p/socket_dispatcher.h"
#include "jingle/glue/utils.h"
#include "third_party/libjingle/source/talk/base/asyncpacketsocket.h"
namespace content {
namespace {
// TODO(hclam): This shouldn't be a pre-defined value. Bug: crbug.com/181321.
const int kMaxPendingPackets = 32;
const int kWritableSignalThreshold = 0;
// IpcPacketSocket implements talk_base::AsyncPacketSocket interface
// using P2PSocketClient that works over IPC-channel. It must be used
// on the thread it was created.
class IpcPacketSocket : public talk_base::AsyncPacketSocket,
public P2PSocketClient::Delegate {
public:
IpcPacketSocket();
virtual ~IpcPacketSocket();
// Always takes ownership of client even if initialization fails.
bool Init(P2PSocketType type, P2PSocketClient* client,
const talk_base::SocketAddress& local_address,
const talk_base::SocketAddress& remote_address);
// talk_base::AsyncPacketSocket interface.
virtual talk_base::SocketAddress GetLocalAddress() const OVERRIDE;
virtual talk_base::SocketAddress GetRemoteAddress() const OVERRIDE;
virtual int Send(const void *pv, size_t cb) OVERRIDE;
virtual int SendTo(const void *pv, size_t cb,
const talk_base::SocketAddress& addr) OVERRIDE;
virtual int Close() OVERRIDE;
virtual State GetState() const OVERRIDE;
virtual int GetOption(talk_base::Socket::Option opt, int* value) OVERRIDE;
virtual int SetOption(talk_base::Socket::Option opt, int value) OVERRIDE;
virtual int GetError() const OVERRIDE;
virtual void SetError(int error) OVERRIDE;
// P2PSocketClient::Delegate implementation.
virtual void OnOpen(const net::IPEndPoint& address) OVERRIDE;
virtual void OnIncomingTcpConnection(const net::IPEndPoint& address,
P2PSocketClient* client) OVERRIDE;
virtual void OnSendComplete() OVERRIDE;
virtual void OnError() OVERRIDE;
virtual void OnDataReceived(const net::IPEndPoint& address,
const std::vector<char>& data) OVERRIDE;
private:
enum InternalState {
IS_UNINITIALIZED,
IS_OPENING,
IS_OPEN,
IS_CLOSED,
IS_ERROR,
};
void InitAcceptedTcp(P2PSocketClient* client,
const talk_base::SocketAddress& local_address,
const talk_base::SocketAddress& remote_address);
P2PSocketType type_;
// Message loop on which this socket was created and being used.
base::MessageLoop* message_loop_;
// Corresponding P2P socket client.
scoped_refptr<P2PSocketClient> client_;
// Local address is allocated by the browser process, and the
// renderer side doesn't know the address until it receives OnOpen()
// event from the browser.
talk_base::SocketAddress local_address_;
// Remote address for client TCP connections.
talk_base::SocketAddress remote_address_;
// Current state of the object.
InternalState state_;
// Number which have been sent to the browser, but for which we haven't
// received response.
int send_packets_pending_;
// Set to true once EWOULDBLOCK was returned from Send(). Indicates that the
// caller expects SignalWritable notification.
bool writable_signal_expected_;
// Current error code. Valid when state_ == IS_ERROR.
int error_;
DISALLOW_COPY_AND_ASSIGN(IpcPacketSocket);
};
IpcPacketSocket::IpcPacketSocket()
: type_(P2P_SOCKET_UDP),
message_loop_(base::MessageLoop::current()),
state_(IS_UNINITIALIZED),
send_packets_pending_(0),
writable_signal_expected_(false),
error_(0) {}
IpcPacketSocket::~IpcPacketSocket() {
if (state_ == IS_OPENING || state_ == IS_OPEN ||
state_ == IS_ERROR) {
Close();
}
}
bool IpcPacketSocket::Init(P2PSocketType type, P2PSocketClient* client,
const talk_base::SocketAddress& local_address,
const talk_base::SocketAddress& remote_address) {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
DCHECK_EQ(state_, IS_UNINITIALIZED);
type_ = type;
client_ = client;
local_address_ = local_address;
remote_address_ = remote_address;
state_ = IS_OPENING;
net::IPEndPoint local_endpoint;
if (!jingle_glue::SocketAddressToIPEndPoint(local_address, &local_endpoint)) {
return false;
}
net::IPEndPoint remote_endpoint;
if (!jingle_glue::SocketAddressToIPEndPoint(
remote_address, &remote_endpoint)) {
return false;
}
client_->Init(type, local_endpoint, remote_endpoint, this);
return true;
}
void IpcPacketSocket::InitAcceptedTcp(
P2PSocketClient* client,
const talk_base::SocketAddress& local_address,
const talk_base::SocketAddress& remote_address) {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
DCHECK_EQ(state_, IS_UNINITIALIZED);
client_ = client;
local_address_ = local_address;
remote_address_ = remote_address;
state_ = IS_OPEN;
client_->set_delegate(this);
}
// talk_base::AsyncPacketSocket interface.
talk_base::SocketAddress IpcPacketSocket::GetLocalAddress() const {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
return local_address_;
}
talk_base::SocketAddress IpcPacketSocket::GetRemoteAddress() const {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
return remote_address_;
}
int IpcPacketSocket::Send(const void *data, size_t data_size) {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
return SendTo(data, data_size, remote_address_);
}
int IpcPacketSocket::SendTo(const void *data, size_t data_size,
const talk_base::SocketAddress& address) {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
switch (state_) {
case IS_UNINITIALIZED:
NOTREACHED();
return EWOULDBLOCK;
case IS_OPENING:
return EWOULDBLOCK;
case IS_CLOSED:
return ENOTCONN;
case IS_ERROR:
return error_;
case IS_OPEN:
// Continue sending the packet.
break;
}
if (send_packets_pending_ > kMaxPendingPackets) {
TRACE_EVENT_INSTANT1("p2p", "MaxPendingPacketsWouldBlock",
TRACE_EVENT_SCOPE_THREAD, "id", client_->socket_id());
writable_signal_expected_ = true;
error_ = EWOULDBLOCK;
return -1;
}
const char* data_char = reinterpret_cast<const char*>(data);
std::vector<char> data_vector(data_char, data_char + data_size);
net::IPEndPoint address_chrome;
if (!jingle_glue::SocketAddressToIPEndPoint(address, &address_chrome)) {
NOTREACHED();
return -1;
}
++send_packets_pending_;
client_->Send(address_chrome, data_vector);
// Fake successful send. The caller ignores result anyway.
return data_size;
}
int IpcPacketSocket::Close() {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
client_->Close();
state_ = IS_CLOSED;
return 0;
}
talk_base::AsyncPacketSocket::State IpcPacketSocket::GetState() const {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
switch (state_) {
case IS_UNINITIALIZED:
NOTREACHED();
return STATE_CLOSED;
case IS_OPENING:
return STATE_BINDING;
case IS_OPEN:
if (type_ == P2P_SOCKET_TCP_CLIENT) {
return STATE_CONNECTED;
} else {
return STATE_BOUND;
}
case IS_CLOSED:
case IS_ERROR:
return STATE_CLOSED;
}
NOTREACHED();
return STATE_CLOSED;
}
int IpcPacketSocket::GetOption(talk_base::Socket::Option opt, int* value) {
// We don't support socket options for IPC sockets.
return -1;
}
int IpcPacketSocket::SetOption(talk_base::Socket::Option opt, int value) {
// We don't support socket options for IPC sockets.
return -1;
}
int IpcPacketSocket::GetError() const {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
return error_;
}
void IpcPacketSocket::SetError(int error) {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
error_ = error;
}
void IpcPacketSocket::OnOpen(const net::IPEndPoint& address) {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
if (!jingle_glue::IPEndPointToSocketAddress(address, &local_address_)) {
// Always expect correct IPv4 address to be allocated.
NOTREACHED();
OnError();
return;
}
state_ = IS_OPEN;
SignalAddressReady(this, local_address_);
if (type_ == P2P_SOCKET_TCP_CLIENT)
SignalConnect(this);
}
void IpcPacketSocket::OnIncomingTcpConnection(
const net::IPEndPoint& address,
P2PSocketClient* client) {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
scoped_ptr<IpcPacketSocket> socket(new IpcPacketSocket());
talk_base::SocketAddress remote_address;
if (!jingle_glue::IPEndPointToSocketAddress(address, &remote_address)) {
// Always expect correct IPv4 address to be allocated.
NOTREACHED();
}
socket->InitAcceptedTcp(client, local_address_, remote_address);
SignalNewConnection(this, socket.release());
}
void IpcPacketSocket::OnSendComplete() {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
--send_packets_pending_;
DCHECK_GE(send_packets_pending_, 0);
if (writable_signal_expected_ &&
send_packets_pending_ <= kWritableSignalThreshold) {
SignalReadyToSend(this);
writable_signal_expected_ = false;
}
}
void IpcPacketSocket::OnError() {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
state_ = IS_ERROR;
error_ = ECONNABORTED;
}
void IpcPacketSocket::OnDataReceived(const net::IPEndPoint& address,
const std::vector<char>& data) {
DCHECK_EQ(base::MessageLoop::current(), message_loop_);
talk_base::SocketAddress address_lj;
if (!jingle_glue::IPEndPointToSocketAddress(address, &address_lj)) {
// We should always be able to convert address here because we
// don't expect IPv6 address on IPv4 connections.
NOTREACHED();
return;
}
SignalReadPacket(this, &data[0], data.size(), address_lj);
}
} // namespace
IpcPacketSocketFactory::IpcPacketSocketFactory(
P2PSocketDispatcher* socket_dispatcher)
: socket_dispatcher_(socket_dispatcher) {
}
IpcPacketSocketFactory::~IpcPacketSocketFactory() {
}
talk_base::AsyncPacketSocket* IpcPacketSocketFactory::CreateUdpSocket(
const talk_base::SocketAddress& local_address, int min_port, int max_port) {
talk_base::SocketAddress crome_address;
P2PSocketClient* socket_client = new P2PSocketClient(socket_dispatcher_);
scoped_ptr<IpcPacketSocket> socket(new IpcPacketSocket());
// TODO(sergeyu): Respect local_address and port limits here (need
// to pass them over IPC channel to the browser).
if (!socket->Init(P2P_SOCKET_UDP, socket_client,
local_address, talk_base::SocketAddress())) {
return NULL;
}
return socket.release();
}
talk_base::AsyncPacketSocket* IpcPacketSocketFactory::CreateServerTcpSocket(
const talk_base::SocketAddress& local_address, int min_port, int max_port,
bool ssl) {
// TODO(sergeyu): Implement SSL support.
if (ssl)
return NULL;
talk_base::SocketAddress crome_address;
P2PSocketClient* socket_client = new P2PSocketClient(socket_dispatcher_);
scoped_ptr<IpcPacketSocket> socket(new IpcPacketSocket());
if (!socket->Init(P2P_SOCKET_TCP_SERVER, socket_client, local_address,
talk_base::SocketAddress())) {
return NULL;
}
return socket.release();
}
talk_base::AsyncPacketSocket* IpcPacketSocketFactory::CreateClientTcpSocket(
const talk_base::SocketAddress& local_address,
const talk_base::SocketAddress& remote_address,
const talk_base::ProxyInfo& proxy_info,
const std::string& user_agent, bool ssl) {
// TODO(sergeyu): Implement SSL support.
if (ssl)
return NULL;
talk_base::SocketAddress crome_address;
P2PSocketClient* socket_client = new P2PSocketClient(socket_dispatcher_);
scoped_ptr<IpcPacketSocket> socket(new IpcPacketSocket());
if (!socket->Init(P2P_SOCKET_TCP_CLIENT, socket_client, local_address,
remote_address))
return NULL;
return socket.release();
}
} // namespace content