text: // 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 GOOGLE_APIS_GCM_ENGINE_MCS_CLIENT_H_
  #define GOOGLE_APIS_GCM_ENGINE_MCS_CLIENT_H_
  
  #include <stdint.h>
  
  #include <deque>
  #include <map>
  #include <memory>
  #include <string>
  #include <vector>
  
  #include "base/files/file_path.h"
  #include "base/macros.h"
  #include "base/memory/linked_ptr.h"
  #include "base/memory/weak_ptr.h"
  #include "google_apis/gcm/base/gcm_export.h"
  #include "google_apis/gcm/base/mcs_message.h"
  #include "google_apis/gcm/engine/connection_factory.h"
  #include "google_apis/gcm/engine/connection_handler.h"
  #include "google_apis/gcm/engine/gcm_store.h"
  #include "google_apis/gcm/engine/heartbeat_manager.h"
  
  namespace base {
  class Clock;
  class Timer;
  }  // namespace base
  
  namespace google {
  namespace protobuf {
  class MessageLite;
  }  // namespace protobuf
  }  // namespace google
  
  namespace mcs_proto {
  class LoginRequest;
  }
  
  namespace gcm {
  
  class CollapseKey;
  class ConnectionFactory;
  class GCMStatsRecorder;
  struct ReliablePacketInfo;
  
  // An MCS client. This client is in charge of all communications with an
  // MCS endpoint, and is capable of reliably sending/receiving GCM messages.
  // NOTE: Not thread safe. This class should live on the same thread as that
  // network requests are performed on.
  class GCM_EXPORT MCSClient {
   public:
    // Any change made to this enum should have corresponding change in the
    // GetStateString(...) function.
    enum State {
      UNINITIALIZED,  // Uninitialized.
      LOADED,         // GCM Load finished, waiting to connect.
      CONNECTING,     // Connection in progress.
      CONNECTED,      // Connected and running.
    };
  
    // Any change made to this enum should have corresponding change in the
    // GetMessageSendStatusString(...) function in mcs_client.cc.
    enum MessageSendStatus {
      // Message was queued succcessfully.
      QUEUED,
      // Message was sent to the server and the ACK was received.
      SENT,
      // Message not saved, because total queue size limit reached.
      QUEUE_SIZE_LIMIT_REACHED,
      // Message not saved, because app queue size limit reached.
      APP_QUEUE_SIZE_LIMIT_REACHED,
      // Message too large to send.
      MESSAGE_TOO_LARGE,
      // Message not send becuase of TTL = 0 and no working connection.
      NO_CONNECTION_ON_ZERO_TTL,
      // Message exceeded TTL.
      TTL_EXCEEDED,
  
      // NOTE: always keep this entry at the end. Add new status types only
      // immediately above this line. Make sure to update the corresponding
      // histogram enum accordingly.
      SEND_STATUS_COUNT
    };
  
    // Callback for MCSClient's error conditions.
    // TODO(fgorski): Keeping it as a callback with intention to add meaningful
    // error information.
    typedef base::Callback<void()> ErrorCallback;
    // Callback when a message is received.
    typedef base::Callback<void(const MCSMessage& message)>
        OnMessageReceivedCallback;
    // Callback when a message is sent (and receipt has been acknowledged by
    // the MCS endpoint).
    typedef base::Callback<void(int64_t user_serial_number,
                                const std::string& app_id,
                                const std::string& message_id,
                                MessageSendStatus status)> OnMessageSentCallback;
  
    MCSClient(const std::string& version_string,
              base::Clock* clock,
              ConnectionFactory* connection_factory,
              GCMStore* gcm_store,
              GCMStatsRecorder* recorder);
    virtual ~MCSClient();
  
    // Initialize the client. Will load any previous id/token information as well
    // as unacknowledged message information from the GCM storage, if it exists,
    // passing the id/token information back via |initialization_callback| along
    // with a |success == true| result. If no GCM information is present (and
    // this is therefore a fresh client), a clean GCM store will be created and
    // values of 0 will be returned via |initialization_callback| with
    // |success == true|.
    // If an error loading the GCM store is encountered,
    // |initialization_callback| will be invoked with |success == false|.
    void Initialize(const ErrorCallback& initialization_callback,
                    const OnMessageReceivedCallback& message_received_callback,
                    const OnMessageSentCallback& message_sent_callback,
                    std::unique_ptr<GCMStore::LoadResult> load_result);
  
    // Logs the client into the server. Client must be initialized.
    // |android_id| and |security_token| are optional if this is not a new
    // client, else they must be non-zero.
    // Successful login will result in |message_received_callback| being invoked
    // with a valid LoginResponse.
    // Login failure (typically invalid id/token) will shut down the client, and
    // |initialization_callback| to be invoked with |success = false|.
    virtual void Login(uint64_t android_id, uint64_t security_token);
  
    // Sends a message, with or without reliable message queueing (RMQ) support.
    // Will asynchronously invoke the OnMessageSent callback regardless.
    // Whether to use RMQ depends on whether the protobuf has |ttl| set or not.
    // |ttl == 0| denotes the message should only be sent if the connection is
    // open. |ttl > 0| will keep the message saved for |ttl| seconds, after which
    // it will be dropped if it was unable to be sent. When a message is dropped,
    // |message_sent_callback_| is invoked with a TTL expiration error.
    virtual void SendMessage(const MCSMessage& message);
  
    // Returns the current state of the client.
    State state() const { return state_; }
  
    // Returns the size of the send message queue.
    int GetSendQueueSize() const;
  
    // Returns the size of the resend messaage queue.
    int GetResendQueueSize() const;
  
    // Returns text representation of the state enum.
    std::string GetStateString() const;
  
    // Updates the timer used by |heartbeat_manager_| for sending heartbeats.
    void UpdateHeartbeatTimer(std::unique_ptr<base::Timer> timer);
  
    // Allows a caller to set a heartbeat interval (in milliseconds) with which
    // the MCS connection will be monitored on both ends, to detect device
    // presence. In case the newly set interval is smaller than the current one,
    // connection will be restarted with new heartbeat interval. Valid values have
    // to be between GetMax/GetMinClientHeartbeatIntervalMs of HeartbeatManager,
    // otherwise the setting won't take effect.
    void AddHeartbeatInterval(const std::string& scope, int interval_ms);
    void RemoveHeartbeatInterval(const std::string& scope);
  
    HeartbeatManager* GetHeartbeatManagerForTesting() {
      return &heartbeat_manager_;
    }
  
   private:
    typedef uint32_t StreamId;
    typedef std::string PersistentId;
    typedef std::vector<StreamId> StreamIdList;
    typedef std::vector<PersistentId> PersistentIdList;
    typedef std::map<StreamId, PersistentId> StreamIdToPersistentIdMap;
    typedef linked_ptr<ReliablePacketInfo> MCSPacketInternal;
  
    // Resets the internal state and builds a new login request, acknowledging
    // any pending server-to-device messages and rebuilding the send queue
    // from all unacknowledged device-to-server messages.
    // Should only be called when the connection has been reset.
    void ResetStateAndBuildLoginRequest(mcs_proto::LoginRequest* request);
  
    // Send a heartbeat to the MCS server.
    void SendHeartbeat();
  
    // GCM Store callback.
    void OnGCMUpdateFinished(bool success);
  
    // Attempt to send a message.
    void MaybeSendMessage();
  
    // Helper for sending a protobuf along with any unacknowledged ids to the
    // wire.
    void SendPacketToWire(ReliablePacketInfo* packet_info);
  
    // Handle a data message sent to the MCS client system from the MCS server.
    void HandleMCSDataMesssage(
        std::unique_ptr<google::protobuf::MessageLite> protobuf);
  
    // Handle a packet received over the wire.
    void HandlePacketFromWire(
        std::unique_ptr<google::protobuf::MessageLite> protobuf);
  
    // ReliableMessageQueue acknowledgment helpers.
    // Handle a StreamAck sent by the server confirming receipt of all
    // messages up to the message with stream id |last_stream_id_received|.
    void HandleStreamAck(StreamId last_stream_id_received_);
    // Handle a SelectiveAck sent by the server confirming all messages
    // in |id_list|.
    void HandleSelectiveAck(const PersistentIdList& id_list);
    // Handle server confirmation of a device message, including device's
    // acknowledgment of receipt of messages.
    void HandleServerConfirmedReceipt(StreamId device_stream_id);
  
    // Generates a new persistent id for messages.
    // Virtual for testing.
    virtual PersistentId GetNextPersistentId();
  
    // Helper for the heartbeat manager to signal a connection reset.
    void OnConnectionResetByHeartbeat(
        ConnectionFactory::ConnectionResetReason reason);
  
    // Runs the message_sent_callback_ with send |status| of the |protobuf|.
    void NotifyMessageSendStatus(const google::protobuf::MessageLite& protobuf,
                                 MessageSendStatus status);
  
    // Pops the next message from the front of the send queue (cleaning up
    // any associated state).
    MCSPacketInternal PopMessageForSend();
  
    // Gets the minimum interval from the map of scopes to intervals in
    // milliseconds.
    int GetMinHeartbeatIntervalMs();
  
    // Local version string. Sent on login.
    const std::string version_string_;
  
    // Clock for enforcing TTL. Passed in for testing.
    base::Clock* const clock_;
  
    // Client state.
    State state_;
  
    // Callbacks for owner.
    ErrorCallback mcs_error_callback_;
    OnMessageReceivedCallback message_received_callback_;
    OnMessageSentCallback message_sent_callback_;
  
    // The android id and security token in use by this device.
    uint64_t android_id_;
    uint64_t security_token_;
  
    // Factory for creating new connections and connection handlers.
    ConnectionFactory* connection_factory_;
  
    // Connection handler to handle all over-the-wire protocol communication
    // with the mobile connection server.
    ConnectionHandler* connection_handler_;
  
    // -----  Reliablie Message Queue section -----
    // Note: all queues/maps are ordered from oldest (front/begin) message to
    // most recent (back/end).
  
    // Send/acknowledge queues.
    std::deque<MCSPacketInternal> to_send_;
    std::deque<MCSPacketInternal> to_resend_;
  
    // Map of collapse keys to their pending messages.
    std::map<CollapseKey, ReliablePacketInfo*> collapse_key_map_;
  
    // Last device_to_server stream id acknowledged by the server.
    StreamId last_device_to_server_stream_id_received_;
    // Last server_to_device stream id acknowledged by this device.
    StreamId last_server_to_device_stream_id_received_;
    // The stream id for the last sent message. A new message should consume
    // stream_id_out_ + 1.
    StreamId stream_id_out_;
    // The stream id of the last received message. The LoginResponse will always
    // have a stream id of 1, and stream ids increment by 1 for each received
    // message.
    StreamId stream_id_in_;
  
    // The server messages that have not been acked by the device yet. Keyed by
    // server stream id.
    StreamIdToPersistentIdMap unacked_server_ids_;
  
    // Those server messages that have been acked. They must remain tracked
    // until the ack message is itself confirmed. The list of all message ids
    // acknowledged are keyed off the device stream id of the message that
    // acknowledged them.
    std::map<StreamId, PersistentIdList> acked_server_ids_;
  
    // Those server messages from a previous connection that were not fully
    // acknowledged. They do not have associated stream ids, and will be
    // acknowledged on the next login attempt.
    PersistentIdList restored_unackeds_server_ids_;
  
    // The GCM persistent store. Not owned.
    GCMStore* gcm_store_;
  
    // Manager to handle triggering/detecting heartbeats.
    HeartbeatManager heartbeat_manager_;
  
    // Custom heartbeat intervals requested by different components.
    std::map<std::string, int> custom_heartbeat_intervals_;
  
    // Recorder that records GCM activities for debugging purpose. Not owned.
    GCMStatsRecorder* recorder_;
  
    base::WeakPtrFactory<MCSClient> weak_ptr_factory_;
  
    DISALLOW_COPY_AND_ASSIGN(MCSClient);
  };
  
  } // namespace gcm
  
  #endif  // GOOGLE_APIS_GCM_ENGINE_MCS_CLIENT_H_
  