// 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_CAST_CHANNEL_CAST_SOCKET_H_
#define CHROME_BROWSER_EXTENSIONS_API_CAST_CHANNEL_CAST_SOCKET_H_
#include <queue>
#include <string>
#include "base/basictypes.h"
#include "base/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "chrome/common/extensions/api/cast_channel.h"
#include "extensions/browser/api/api_resource.h"
#include "extensions/browser/api/api_resource_manager.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_log.h"
#include "url/gurl.h"
namespace net {
class AddressList;
class CertVerifier;
class SSLClientSocket;
class StreamSocket;
class TCPClientSocket;
class TransportSecurityState;
}
namespace extensions {
namespace api {
namespace cast_channel {
class CastMessage;
// Size (in bytes) of the largest allowed message payload on the wire (without
// the header).
extern const uint32 kMaxMessageSize;
// Size (in bytes) of the message header.
extern const uint32 kMessageHeaderSize;
// This class implements a channel between Chrome and a Cast device using a TCP
// socket. The channel may be unauthenticated (cast://) or authenticated
// (casts://). All CastSocket objects must be used only on the IO thread.
//
// NOTE: Not called "CastChannel" to reduce confusion with the generated API
// code.
class CastSocket : public ApiResource,
public base::SupportsWeakPtr<CastSocket> {
public:
// Object to be informed of incoming messages and errors.
class Delegate {
public:
// An error occurred on the channel.
// It is fine to delete the socket in this callback.
virtual void OnError(const CastSocket* socket, ChannelError error) = 0;
// A message was received on the channel.
// Do NOT delete the socket in this callback.
virtual void OnMessage(const CastSocket* socket,
const MessageInfo& message) = 0;
protected:
virtual ~Delegate() {}
};
// Creates a new CastSocket to |url|. |owner_extension_id| is the id of the
// extension that opened the socket.
CastSocket(const std::string& owner_extension_id,
const GURL& url,
CastSocket::Delegate* delegate,
net::NetLog* net_log);
virtual ~CastSocket();
// The URL for the channel.
const GURL& url() const;
// Whether to perform receiver authentication.
bool auth_required() const { return auth_required_; }
// Channel id for the ApiResourceManager.
int id() const { return channel_id_; }
// Sets the channel id.
void set_id(int channel_id) { channel_id_ = channel_id; }
// Returns the state of the channel.
ReadyState ready_state() const { return ready_state_; }
// Returns the last error that occurred on this channel, or
// CHANNEL_ERROR_NONE if no error has occurred.
ChannelError error_state() const { return error_state_; }
// Connects the channel to the peer. If successful, the channel will be in
// READY_STATE_OPEN.
// It is fine to delete the CastSocket object in |callback|.
virtual void Connect(const net::CompletionCallback& callback);
// Sends a message over a connected channel. The channel must be in
// READY_STATE_OPEN.
//
// Note that if an error occurs the following happens:
// 1. Completion callbacks for all pending writes are invoked with error.
// 2. Delegate::OnError is called once.
// 3. Castsocket is closed.
//
// DO NOT delete the CastSocket object in write completion callback.
// But it is fine to delete the socket in Delegate::OnError
virtual void SendMessage(const MessageInfo& message,
const net::CompletionCallback& callback);
// Closes the channel. On completion, the channel will be in
// READY_STATE_CLOSED.
// It is fine to delete the CastSocket object in |callback|.
virtual void Close(const net::CompletionCallback& callback);
// Fills |channel_info| with the status of this channel.
virtual void FillChannelInfo(ChannelInfo* channel_info) const;
private:
friend class ApiResourceManager<CastSocket>;
friend class CastSocketTest;
static const char* service_name() { return "CastSocketManager"; }
// Internal connection states.
enum ConnectionState {
CONN_STATE_NONE,
CONN_STATE_TCP_CONNECT,
CONN_STATE_TCP_CONNECT_COMPLETE,
CONN_STATE_SSL_CONNECT,
CONN_STATE_SSL_CONNECT_COMPLETE,
CONN_STATE_AUTH_CHALLENGE_SEND,
CONN_STATE_AUTH_CHALLENGE_SEND_COMPLETE,
CONN_STATE_AUTH_CHALLENGE_REPLY_COMPLETE,
};
// Internal write states.
enum WriteState {
WRITE_STATE_NONE,
WRITE_STATE_WRITE,
WRITE_STATE_WRITE_COMPLETE,
WRITE_STATE_DO_CALLBACK,
WRITE_STATE_ERROR,
};
// Internal read states.
enum ReadState {
READ_STATE_NONE,
READ_STATE_READ,
READ_STATE_READ_COMPLETE,
READ_STATE_DO_CALLBACK,
READ_STATE_ERROR,
};
// Creates an instance of TCPClientSocket.
virtual scoped_ptr<net::TCPClientSocket> CreateTcpSocket();
// Creates an instance of SSLClientSocket with the given underlying |socket|.
virtual scoped_ptr<net::SSLClientSocket> CreateSslSocket(
scoped_ptr<net::StreamSocket> socket);
// Returns IPEndPoint for the URL to connect to.
const net::IPEndPoint& ip_endpoint() const { return ip_endpoint_; }
// Extracts peer certificate from SSLClientSocket instance when the socket
// is in cert error state.
// Returns whether certificate is successfully extracted.
virtual bool ExtractPeerCert(std::string* cert);
// Verifies whether the challenge reply received from the peer is valid:
// 1. Signature in the reply is valid.
// 2. Certificate is rooted to a trusted CA.
virtual bool VerifyChallengeReply();
/////////////////////////////////////////////////////////////////////////////
// Following methods work together to implement the following flow:
// 1. Create a new TCP socket and connect to it
// 2. Create a new SSL socket and try connecting to it
// 3. If connection fails due to invalid cert authority, then extract the
// peer certificate from the error.
// 4. Whitelist the peer certificate and try #1 and #2 again.
// 5. If SSL socket is connected successfully, and if protocol is casts://
// then issue an auth challenge request.
// 6. Validate the auth challenge response.
//
// Main method that performs connection state transitions.
void DoConnectLoop(int result);
// Each of the below Do* method is executed in the corresponding
// connection state. For example when connection state is TCP_CONNECT
// DoTcpConnect is called, and so on.
int DoTcpConnect();
int DoTcpConnectComplete(int result);
int DoSslConnect();
int DoSslConnectComplete(int result);
int DoAuthChallengeSend();
int DoAuthChallengeSendComplete(int result);
int DoAuthChallengeReplyComplete(int result);
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// Following methods work together to implement write flow.
//
// Main method that performs write flow state transitions.
void DoWriteLoop(int result);
// Each of the below Do* method is executed in the corresponding
// write state. For example when write state is WRITE_STATE_WRITE_COMPLETE
// DowriteComplete is called, and so on.
int DoWrite();
int DoWriteComplete(int result);
int DoWriteCallback();
int DoWriteError(int result);
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// Following methods work together to implement read flow.
//
// Main method that performs write flow state transitions.
void DoReadLoop(int result);
// Each of the below Do* method is executed in the corresponding
// write state. For example when write state is READ_STATE_READ_COMPLETE
// DoReadComplete is called, and so on.
int DoRead();
int DoReadComplete(int result);
int DoReadCallback();
int DoReadError(int result);
/////////////////////////////////////////////////////////////////////////////
// Runs the external connection callback and resets it.
void DoConnectCallback(int result);
// Verifies that the URL is a valid cast:// or casts:// URL and sets url_ to
// the result.
bool ParseChannelUrl(const GURL& url);
// Adds |message| to the write queue and starts the write loop if needed.
void SendCastMessageInternal(const CastMessage& message,
const net::CompletionCallback& callback);
void PostTaskToStartConnectLoop(int result);
void PostTaskToStartReadLoop();
void StartReadLoop();
// Parses the contents of header_read_buffer_ and sets current_message_size_
// to the size of the body of the message.
bool ProcessHeader();
// Parses the contents of body_read_buffer_ and sets current_message_ to
// the message received.
bool ProcessBody();
// Closes socket, updating the error state and signaling the delegate that
// |error| has occurred.
void CloseWithError(ChannelError error);
// Serializes the content of message_proto (with a header) to |message_data|.
static bool Serialize(const CastMessage& message_proto,
std::string* message_data);
virtual bool CalledOnValidThread() const;
base::ThreadChecker thread_checker_;
// The id of the channel.
int channel_id_;
// The URL of the peer (cast:// or casts://).
GURL url_;
// Delegate to inform of incoming messages and errors.
Delegate* delegate_;
// True if receiver authentication should be performed.
bool auth_required_;
// The IP endpoint of the peer.
net::IPEndPoint ip_endpoint_;
// IOBuffer for reading the message header.
scoped_refptr<net::GrowableIOBuffer> header_read_buffer_;
// IOBuffer for reading the message body.
scoped_refptr<net::GrowableIOBuffer> body_read_buffer_;
// IOBuffer to currently read into.
scoped_refptr<net::GrowableIOBuffer> current_read_buffer_;
// The number of bytes in the current message body.
uint32 current_message_size_;
// Last message received on the socket.
scoped_ptr<CastMessage> current_message_;
// The NetLog for this service.
net::NetLog* net_log_;
// The NetLog source for this service.
net::NetLog::Source net_log_source_;
// CertVerifier is owned by us but should be deleted AFTER SSLClientSocket
// since in some cases the destructor of SSLClientSocket may call a method
// to cancel a cert verification request.
scoped_ptr<net::CertVerifier> cert_verifier_;
scoped_ptr<net::TransportSecurityState> transport_security_state_;
// Owned ptr to the underlying TCP socket.
scoped_ptr<net::TCPClientSocket> tcp_socket_;
// Owned ptr to the underlying SSL socket.
scoped_ptr<net::SSLClientSocket> socket_;
// Certificate of the peer. This field may be empty if the peer
// certificate is not yet fetched.
std::string peer_cert_;
// Reply received from the receiver to a challenge request.
scoped_ptr<CastMessage> challenge_reply_;
// Callback invoked when the socket is connected.
net::CompletionCallback connect_callback_;
// Connection flow state machine state.
ConnectionState connect_state_;
// Write flow state machine state.
WriteState write_state_;
// Read flow state machine state.
ReadState read_state_;
// The last error encountered by the channel.
ChannelError error_state_;
// The current status of the channel.
ReadyState ready_state_;
// Message header struct. If fields are added, be sure to update
// kMessageHeaderSize in the .cc.
struct MessageHeader {
MessageHeader();
// Sets the message size.
void SetMessageSize(size_t message_size);
// Prepends this header to |str|.
void PrependToString(std::string* str);
// Reads |header| from the beginning of |buffer|.
static void ReadFromIOBuffer(net::GrowableIOBuffer* buffer,
MessageHeader* header);
std::string ToString();
// The size of the following protocol message in bytes, in host byte order.
uint32 message_size;
};
// Holds a message to be written to the socket. |callback| is invoked when the
// message is fully written or an error occurrs.
struct WriteRequest {
explicit WriteRequest(const net::CompletionCallback& callback);
~WriteRequest();
// Sets the content of the request by serializing |message| into |io_buffer|
// and prepending the header. Must only be called once.
bool SetContent(const CastMessage& message_proto);
net::CompletionCallback callback;
scoped_refptr<net::DrainableIOBuffer> io_buffer;
};
// Queue of pending writes. The message at the front of the queue is the one
// being written.
std::queue<WriteRequest> write_queue_;
FRIEND_TEST_ALL_PREFIXES(CastSocketTest, TestCastURLs);
FRIEND_TEST_ALL_PREFIXES(CastSocketTest, TestRead);
FRIEND_TEST_ALL_PREFIXES(CastSocketTest, TestReadMany);
FRIEND_TEST_ALL_PREFIXES(CastSocketTest, TestFullSecureConnectionFlowAsync);
DISALLOW_COPY_AND_ASSIGN(CastSocket);
};
} // namespace cast_channel
} // namespace api
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_CAST_CHANNEL_CAST_SOCKET_H_