blob: 3f0d5252e481075c60a6cb65e6195e94eb4fc7a6 [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/browser/renderer_host/p2p/socket_host_tcp.h"
#include "base/sys_byteorder.h"
#include "content/common/p2p_messages.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/socket/tcp_client_socket.h"
namespace {
const int kReadBufferSize = 4096;
const int kPacketHeaderSize = sizeof(uint16);
} // namespace
namespace content {
P2PSocketHostTcp::P2PSocketHostTcp(IPC::Sender* message_sender, int id)
: P2PSocketHost(message_sender, id),
connected_(false) {
}
P2PSocketHostTcp::~P2PSocketHostTcp() {
if (state_ == STATE_OPEN) {
DCHECK(socket_.get());
socket_.reset();
}
}
bool P2PSocketHostTcp::InitAccepted(const net::IPEndPoint& remote_address,
net::StreamSocket* socket) {
DCHECK(socket);
DCHECK_EQ(state_, STATE_UNINITIALIZED);
remote_address_ = remote_address;
socket_.reset(socket);
state_ = STATE_OPEN;
DoRead();
return state_ != STATE_ERROR;
}
bool P2PSocketHostTcp::Init(const net::IPEndPoint& local_address,
const net::IPEndPoint& remote_address) {
DCHECK_EQ(state_, STATE_UNINITIALIZED);
remote_address_ = remote_address;
state_ = STATE_CONNECTING;
scoped_ptr<net::TCPClientSocket> tcp_socket(new net::TCPClientSocket(
net::AddressList(remote_address),
NULL, net::NetLog::Source()));
if (tcp_socket->Bind(local_address) != net::OK) {
OnError();
return false;
}
socket_.reset(tcp_socket.release());
int result = socket_->Connect(
base::Bind(&P2PSocketHostTcp::OnConnected, base::Unretained(this)));
if (result != net::ERR_IO_PENDING) {
OnConnected(result);
}
return state_ != STATE_ERROR;
}
void P2PSocketHostTcp::OnError() {
socket_.reset();
if (state_ == STATE_UNINITIALIZED || state_ == STATE_CONNECTING ||
state_ == STATE_OPEN) {
message_sender_->Send(new P2PMsg_OnError(id_));
}
state_ = STATE_ERROR;
}
void P2PSocketHostTcp::OnConnected(int result) {
DCHECK_EQ(state_, STATE_CONNECTING);
DCHECK_NE(result, net::ERR_IO_PENDING);
if (result != net::OK) {
OnError();
return;
}
if (!socket_->SetSendBufferSize(kMaxSendBufferSize)) {
LOG(WARNING) << "Failed to set send buffer size for TCP socket.";
}
net::IPEndPoint address;
result = socket_->GetLocalAddress(&address);
if (result < 0) {
LOG(ERROR) << "P2PSocket::Init(): unable to get local address: "
<< result;
OnError();
return;
}
VLOG(1) << "Local address: " << address.ToString();
state_ = STATE_OPEN;
message_sender_->Send(new P2PMsg_OnSocketCreated(id_, address));
DoRead();
}
void P2PSocketHostTcp::DoRead() {
int result;
do {
if (!read_buffer_) {
read_buffer_ = new net::GrowableIOBuffer();
read_buffer_->SetCapacity(kReadBufferSize);
} else if (read_buffer_->RemainingCapacity() < kReadBufferSize) {
// Make sure that we always have at least kReadBufferSize of
// remaining capacity in the read buffer. Normally all packets
// are smaller than kReadBufferSize, so this is not really
// required.
read_buffer_->SetCapacity(read_buffer_->capacity() + kReadBufferSize -
read_buffer_->RemainingCapacity());
}
result = socket_->Read(read_buffer_, read_buffer_->RemainingCapacity(),
base::Bind(&P2PSocketHostTcp::OnRead,
base::Unretained(this)));
DidCompleteRead(result);
} while (result > 0);
}
void P2PSocketHostTcp::OnRead(int result) {
DidCompleteRead(result);
if (state_ == STATE_OPEN) {
DoRead();
}
}
void P2PSocketHostTcp::OnPacket(std::vector<char>& data) {
if (!connected_) {
P2PSocketHost::StunMessageType type;
bool stun = GetStunPacketType(&*data.begin(), data.size(), &type);
if (stun && IsRequestOrResponse(type)) {
connected_ = true;
} else if (!stun || type == STUN_DATA_INDICATION) {
LOG(ERROR) << "Received unexpected data packet from "
<< remote_address_.ToString()
<< " before STUN binding is finished. "
<< "Terminating connection.";
OnError();
return;
}
}
message_sender_->Send(new P2PMsg_OnDataReceived(id_, remote_address_, data));
}
void P2PSocketHostTcp::DidCompleteRead(int result) {
DCHECK_EQ(state_, STATE_OPEN);
if (result == net::ERR_IO_PENDING) {
return;
} else if (result < 0){
LOG(ERROR) << "Error when reading from TCP socket: " << result;
OnError();
return;
}
read_buffer_->set_offset(read_buffer_->offset() + result);
if (read_buffer_->offset() > kPacketHeaderSize) {
int packet_size = base::NetToHost16(
*reinterpret_cast<uint16*>(read_buffer_->StartOfBuffer()));
if (packet_size + kPacketHeaderSize <= read_buffer_->offset()) {
// We've got a full packet!
char* start = read_buffer_->StartOfBuffer() + kPacketHeaderSize;
std::vector<char> data(start, start + packet_size);
OnPacket(data);
// Move remaining data to the start of the buffer.
memmove(read_buffer_->StartOfBuffer(), start + packet_size,
read_buffer_->offset() - packet_size - kPacketHeaderSize);
read_buffer_->set_offset(read_buffer_->offset() - packet_size -
kPacketHeaderSize);
}
}
}
void P2PSocketHostTcp::Send(const net::IPEndPoint& to,
const std::vector<char>& data) {
if (!socket_.get()) {
// The Send message may be sent after the an OnError message was
// sent by hasn't been processed the renderer.
return;
}
if (write_buffer_) {
// Silently drop packet if we haven't finished sending previous
// packet.
VLOG(1) << "Dropping TCP packet.";
return;
}
if (!(to == remote_address_)) {
// Renderer should use this socket only to send data to
// |remote_address_|.
NOTREACHED();
OnError();
return;
}
if (!connected_) {
P2PSocketHost::StunMessageType type;
bool stun = GetStunPacketType(&*data.begin(), data.size(), &type);
if (!stun || type == STUN_DATA_INDICATION) {
LOG(ERROR) << "Page tried to send a data packet to " << to.ToString()
<< " before STUN binding is finished.";
OnError();
return;
}
}
int size = kPacketHeaderSize + data.size();
write_buffer_ = new net::DrainableIOBuffer(new net::IOBuffer(size), size);
*reinterpret_cast<uint16*>(write_buffer_->data()) =
base::HostToNet16(data.size());
memcpy(write_buffer_->data() + kPacketHeaderSize, &data[0], data.size());
DoWrite();
}
void P2PSocketHostTcp::DoWrite() {
while (true) {
int result = socket_->Write(write_buffer_, write_buffer_->BytesRemaining(),
base::Bind(&P2PSocketHostTcp::OnWritten,
base::Unretained(this)));
if (result >= 0) {
write_buffer_->DidConsume(result);
if (write_buffer_->BytesRemaining() == 0) {
write_buffer_ = NULL;
break;
}
} else {
if (result != net::ERR_IO_PENDING) {
LOG(ERROR) << "Error when sending data in TCP socket: " << result;
OnError();
}
break;
}
}
}
void P2PSocketHostTcp::OnWritten(int result) {
DCHECK(write_buffer_);
DCHECK_NE(result, net::ERR_IO_PENDING);
if (result < 0) {
DCHECK(result != net::ERR_IO_PENDING);
LOG(ERROR) << "Error when sending data in TCP socket: " << result;
OnError();
return;
}
write_buffer_->DidConsume(result);
if (write_buffer_->BytesRemaining() == 0) {
write_buffer_ = NULL;
} else {
DoWrite();
}
}
P2PSocketHost* P2PSocketHostTcp::AcceptIncomingTcpConnection(
const net::IPEndPoint& remote_address, int id) {
NOTREACHED();
OnError();
return NULL;
}
} // namespace content