// Copyright 2017 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 CHROMEOS_SERVICES_SECURE_CHANNEL_SECURE_CHANNEL_H_
#define CHROMEOS_SERVICES_SECURE_CHANNEL_SECURE_CHANNEL_H_

#include "base/containers/queue.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "chromeos/components/multidevice/remote_device_ref.h"
#include "chromeos/services/secure_channel/authenticator.h"
#include "chromeos/services/secure_channel/connection.h"
#include "chromeos/services/secure_channel/connection_observer.h"
#include "chromeos/services/secure_channel/device_to_device_authenticator.h"
#include "chromeos/services/secure_channel/secure_context.h"

namespace chromeos {

namespace secure_channel {

// An authenticated bi-directional channel for exchanging messages with remote
// devices. |SecureChannel| manages a |Connection| by initializing it and
// authenticating it via a security handshake once the connection has occurred.
// Once the channel has been authenticated, messages sent are automatically
// encrypted and messages received are automatically decrypted.
class SecureChannel : public ConnectionObserver {
 public:
  // Enumeration of possible states of connecting to a remote device.
  //   DISCONNECTED: There is no connection to the device, nor is there a
  //       pending connection attempt.
  //   CONNECTING: There is an ongoing connection attempt.
  //   CONNECTED: There is a Bluetooth connection to the device, but the
  //       connection has not yet been authenticated.
  //   AUTHENTICATING: There is an active connection that is currently in the
  //       process of authenticating via a 3-message authentication handshake.
  //   AUTHENTICATED: The connection has been authenticated, and arbitrary
  //       messages can be sent/received to/from the device.
  //   DISCONNECTING: The connection has started disconnecting but has not yet
  //       finished.
  enum class Status {
    DISCONNECTED,
    CONNECTING,
    CONNECTED,
    AUTHENTICATING,
    AUTHENTICATED,
    DISCONNECTING
  };

  static std::string StatusToString(const Status& status);

  class Observer {
   public:
    virtual void OnSecureChannelStatusChanged(SecureChannel* secure_channel,
                                              const Status& old_status,
                                              const Status& new_status) {}

    virtual void OnMessageReceived(SecureChannel* secure_channel,
                                   const std::string& feature,
                                   const std::string& payload) {}

    // Called when a message has been sent successfully; |sequence_number|
    // corresponds to the value returned by an earlier call to SendMessage().
    virtual void OnMessageSent(SecureChannel* secure_channel,
                               int sequence_number) {}
  };

  class Factory {
   public:
    static std::unique_ptr<SecureChannel> NewInstance(
        std::unique_ptr<Connection> connection);

    static void SetInstanceForTesting(Factory* factory);

   protected:
    virtual std::unique_ptr<SecureChannel> BuildInstance(
        std::unique_ptr<Connection> connection);

   private:
    static Factory* factory_instance_;
  };

  ~SecureChannel() override;

  virtual void Initialize();

  // Sends a message over the connection and returns a sequence number. If the
  // message is successfully sent, observers will be notified that the message
  // has been sent and will be provided this sequence number.
  virtual int SendMessage(const std::string& feature,
                          const std::string& payload);

  virtual void Disconnect();

  virtual void AddObserver(Observer* observer);
  virtual void RemoveObserver(Observer* observer);

  // Returns the RSSI of the connection; if no derived class overrides this
  // function, base::nullopt is returned.
  virtual void GetConnectionRssi(
      base::OnceCallback<void(base::Optional<int32_t>)> callback);

  // The |responder_auth| message. Returns null if |secure_context_| is null or
  // status() != AUTHENTICATED.
  virtual base::Optional<std::string> GetChannelBindingData();

  Status status() const { return status_; }

  // ConnectionObserver:
  void OnConnectionStatusChanged(Connection* connection,
                                 Connection::Status old_status,
                                 Connection::Status new_status) override;
  void OnMessageReceived(const Connection& connection,
                         const WireMessage& wire_message) override;
  void OnSendCompleted(const Connection& connection,
                       const WireMessage& wire_message,
                       bool success) override;

 protected:
  SecureChannel(std::unique_ptr<Connection> connection);

  Status status_;

 private:
  friend class SecureChannelConnectionTest;

  // Message waiting to be sent. Note that this is *not* the message that will
  // end up being sent over the wire; before that can be done, the payload must
  // be encrypted.
  struct PendingMessage {
    PendingMessage(const std::string& feature,
                   const std::string& payload,
                   int sequence_number);
    virtual ~PendingMessage();

    const std::string feature;
    const std::string payload;
    const int sequence_number;
  };

  void TransitionToStatus(const Status& new_status);
  void Authenticate();
  void ProcessMessageQueue();
  void OnMessageEncoded(const std::string& feature,
                        int sequence_number,
                        const std::string& encoded_message);
  void OnMessageDecoded(const std::string& feature,
                        const std::string& decoded_message);
  void OnAuthenticationResult(Authenticator::Result result,
                              std::unique_ptr<SecureContext> secure_context);

  std::unique_ptr<Connection> connection_;
  std::unique_ptr<Authenticator> authenticator_;
  std::unique_ptr<SecureContext> secure_context_;
  base::queue<std::unique_ptr<PendingMessage>> queued_messages_;
  std::unique_ptr<PendingMessage> pending_message_;
  int next_sequence_number_ = 0;
  base::ObserverList<Observer>::Unchecked observer_list_;
  base::WeakPtrFactory<SecureChannel> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(SecureChannel);
};

}  // namespace secure_channel

}  // namespace chromeos

#endif  // CHROMEOS_SERVICES_SECURE_CHANNEL_SECURE_CHANNEL_H_
