// 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.

#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_SOCKET_H_
#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_SOCKET_H_

#include <queue>
#include <string>

#include "base/basictypes.h"
#include "base/cancelable_callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
#include "base/threading/thread_checker.h"
#include "base/timer/timer.h"
#include "extensions/browser/api/api_resource.h"
#include "extensions/browser/api/api_resource_manager.h"
#include "extensions/browser/api/cast_channel/cast_socket.h"
#include "extensions/browser/api/cast_channel/cast_transport.h"
#include "extensions/common/api/cast_channel.h"
#include "extensions/common/api/cast_channel/logging.pb.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"

namespace net {
class AddressList;
class CertVerifier;
class SSLClientSocket;
class StreamSocket;
class TCPClientSocket;
class TransportSecurityState;
}

namespace extensions {
namespace core_api {
namespace cast_channel {
class CastMessage;
class Logger;
struct LastErrors;
class MessageFramer;

// Public interface of the CastSocket class.
class CastSocket : public ApiResource {
 public:
  explicit CastSocket(const std::string& owner_extension_id);
  ~CastSocket() override {}

  // Used by BrowserContextKeyedAPIFactory.
  static const char* service_name() { return "CastSocketImplManager"; }

  // Connects the channel to the peer. If successful, the channel will be in
  // READY_STATE_OPEN.  DO NOT delete the CastSocketImpl object in |callback|.
  // Instead use Close().
  // |callback| will be invoked with any ChannelError that occurred, or
  // CHANNEL_ERROR_NONE if successful.
  // |delegate| receives message receipt and error events.
  // Ownership of |delegate| is transferred to this CastSocket.
  virtual void Connect(scoped_ptr<CastTransport::Delegate> delegate,
                       base::Callback<void(ChannelError)> callback) = 0;

  // Closes the channel if not already closed. On completion, the channel will
  // be in READY_STATE_CLOSED.
  //
  // It is fine to delete this object in |callback|.
  virtual void Close(const net::CompletionCallback& callback) = 0;

  // The IP endpoint for the destination of the channel.
  virtual const net::IPEndPoint& ip_endpoint() const = 0;

  // Channel id generated by the ApiResourceManager.
  virtual int id() const = 0;

  // Sets the channel id generated by ApiResourceManager.
  virtual void set_id(int id) = 0;

  // The authentication level requested for the channel.
  virtual ChannelAuthType channel_auth() const = 0;

  // Returns a cast:// or casts:// URL for the channel endpoint.
  // For backwards compatibility.
  virtual std::string cast_url() const = 0;

  // The ready state of the channel.
  virtual ReadyState ready_state() const = 0;

  // Returns the last error that occurred on this channel, or
  // CHANNEL_ERROR_NONE if no error has occurred.
  virtual ChannelError error_state() const = 0;

  // Marks a socket as invalid due to an error. Errors close the socket
  // and any further socket operations will return the error code
  // net::SOCKET_NOT_CORRECTED.
  // Setting the error state does not close the socket if it is open.
  virtual void SetErrorState(ChannelError error_state) = 0;

  // Returns a pointer to the socket's message transport layer. Can be used to
  // send and receive CastMessages over the socket.
  virtual CastTransport* transport() const = 0;
};

// This class implements a channel between Chrome and a Cast device using a TCP
// socket with SSL.  The channel may authenticate that the receiver is a genuine
// Cast device.  All CastSocketImpl objects must be used only on the IO thread.
//
// NOTE: Not called "CastChannel" to reduce confusion with the generated API
// code.
class CastSocketImpl : public CastSocket {
 public:
  // Creates a new CastSocket that connects to |ip_endpoint| with
  // |channel_auth|. |owner_extension_id| is the id of the extension that opened
  // the socket.  |channel_auth| must not be CHANNEL_AUTH_NONE.
  // Parameters:
  // |owner_extension_id|: ID of the extension calling the API.
  // |ip_endpoint|: IP address of the remote host.
  // |channel_auth|: Authentication method used for connecting to a Cast
  //                 receiver.
  // |net_log|: Log of socket events.
  // |connect_timeout|: Connection timeout interval.
  // |logger|: Log of cast channel events.
  CastSocketImpl(const std::string& owner_extension_id,
                 const net::IPEndPoint& ip_endpoint,
                 ChannelAuthType channel_auth,
                 net::NetLog* net_log,
                 const base::TimeDelta& connect_timeout,
                 const scoped_refptr<Logger>& logger);

  // Ensures that the socket is closed.
  ~CastSocketImpl() override;

  // CastSocket interface.
  void Connect(scoped_ptr<CastTransport::Delegate> delegate,
               base::Callback<void(ChannelError)> callback) override;
  CastTransport* transport() const override;
  void Close(const net::CompletionCallback& callback) override;
  const net::IPEndPoint& ip_endpoint() const override;
  int id() const override;
  void set_id(int channel_id) override;
  ChannelAuthType channel_auth() const override;
  std::string cast_url() const override;
  ReadyState ready_state() const override;
  ChannelError error_state() const override;

  // Required by ApiResourceManager.
  static const char* service_name() { return "CastSocketManager"; }

 protected:
  // CastTransport::Delegate methods for receiving handshake messages.
  class AuthTransportDelegate : public CastTransport::Delegate {
   public:
    AuthTransportDelegate(CastSocketImpl* socket);

    // CastTransport::Delegate interface.
    void OnError(ChannelError error_state,
                 const LastErrors& last_errors) override;
    void OnMessage(const CastMessage& message) override;

   private:
    CastSocketImpl* socket_;
  };

  // Replaces the internally-constructed transport object with one provided
  // by the caller (e.g. a mock).
  void SetTransportForTesting(scoped_ptr<CastTransport> transport);

  // Delegate for receiving handshake messages/errors.
  AuthTransportDelegate auth_delegate_;

 private:
  FRIEND_TEST_ALL_PREFIXES(CastSocketTest, TestConnectAuthMessageCorrupted);
  FRIEND_TEST_ALL_PREFIXES(CastSocketTest,
                           TestConnectChallengeReplyReceiveError);
  FRIEND_TEST_ALL_PREFIXES(CastSocketTest,
                           TestConnectChallengeVerificationFails);
  friend class AuthTransportDelegate;
  friend class ApiResourceManager<CastSocketImpl>;
  friend class CastSocketTest;
  friend class TestCastSocket;

  void SetErrorState(ChannelError error_state) override;

  // Frees resources and cancels pending callbacks.  |ready_state_| will be set
  // READY_STATE_CLOSED on completion.  A no-op if |ready_state_| is already
  // READY_STATE_CLOSED.
  void CloseInternal();

  // 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);
  // 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();

  // Invoked by a cancelable closure when connection setup time
  // exceeds the interval specified at |connect_timeout|.
  void OnConnectTimeout();

  /////////////////////////////////////////////////////////////////////////////
  // 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);
  /////////////////////////////////////////////////////////////////////////////

  // Schedules asynchrous connection loop processing in the MessageLoop.
  void PostTaskToStartConnectLoop(int result);

  // Runs the external connection callback and resets it.
  void DoConnectCallback();

  virtual bool CalledOnValidThread() const;

  virtual base::Timer* GetTimer();

  void SetConnectState(proto::ConnectionState connect_state);
  void SetReadyState(ReadyState ready_state);

  base::ThreadChecker thread_checker_;

  const std::string owner_extension_id_;
  // The id of the channel.
  int channel_id_;
  // The IP endpoint that the the channel is connected to.
  net::IPEndPoint ip_endpoint_;
  // Receiver authentication requested for the channel.
  ChannelAuthType channel_auth_;
  // The NetLog for this service.
  net::NetLog* net_log_;
  // The NetLog source for this service.
  net::NetLog::Source net_log_source_;

  // Shared logging object, used to log CastSocket events for diagnostics.
  scoped_refptr<Logger> logger_;

  // 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 or fails to connect.
  base::Callback<void(ChannelError)> connect_callback_;

  // Callback invoked by |connect_timeout_timer_| to cancel the connection.
  base::CancelableClosure connect_timeout_callback_;

  // Duration to wait before timing out.
  base::TimeDelta connect_timeout_;

  // Timer invoked when the connection has timed out.
  scoped_ptr<base::Timer> connect_timeout_timer_;

  // Set when a timeout is triggered and the connection process has
  // canceled.
  bool is_canceled_;

  // Connection flow state machine state.
  proto::ConnectionState connect_state_;

  // Write flow state machine state.
  proto::WriteState write_state_;

  // Read flow state machine state.
  proto::ReadState read_state_;

  // The last error encountered by the channel.
  ChannelError error_state_;

  // The current status of the channel.
  ReadyState ready_state_;

  // Task invoked to (re)start the connect loop.  Canceled on entry to the
  // connect loop.
  base::CancelableClosure connect_loop_callback_;

  // Task invoked to send the auth challenge.  Canceled when the auth challenge
  // has been sent.
  base::CancelableClosure send_auth_challenge_callback_;

  // Cast message formatting and parsing layer.
  scoped_ptr<CastTransport> transport_;

  // Caller's message read and error handling delegate.
  scoped_ptr<CastTransport::Delegate> read_delegate_;

  DISALLOW_COPY_AND_ASSIGN(CastSocketImpl);
};
}  // namespace cast_channel
}  // namespace core_api
}  // namespace extensions

#endif  // EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_SOCKET_H_