chromium/chromeos/ash/services/device_sync/cryptauth_feature_status_setter_impl.h

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROMEOS_ASH_SERVICES_DEVICE_SYNC_CRYPTAUTH_FEATURE_STATUS_SETTER_IMPL_H_
#define CHROMEOS_ASH_SERVICES_DEVICE_SYNC_CRYPTAUTH_FEATURE_STATUS_SETTER_IMPL_H_

#include <memory>
#include <optional>
#include <ostream>
#include <string>

#include "base/containers/queue.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/ash/components/multidevice/software_feature.h"
#include "chromeos/ash/services/device_sync/cryptauth_feature_status_setter.h"
#include "chromeos/ash/services/device_sync/feature_status_change.h"
#include "chromeos/ash/services/device_sync/network_request_error.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_devicesync.pb.h"

namespace ash {

namespace device_sync {

class CryptAuthClient;
class CryptAuthClientFactory;

// An implementation of CryptAuthFeatureStatusSetter, using instances of
// CryptAuthClient to make the BatchSetFeatureStatuses API calls to CryptAuth.
// The requests made via SetFeatureStatus() are queued and processed
// sequentially. This implementation handles timeouts internally, so a callback
// passed to SetFeatureStatus() is always guaranteed to be invoked.
class CryptAuthFeatureStatusSetterImpl : public CryptAuthFeatureStatusSetter {
 public:
  class Factory {
   public:
    static std::unique_ptr<CryptAuthFeatureStatusSetter> Create(
        const std::string& instance_id,
        const std::string& instance_id_token,
        CryptAuthClientFactory* client_factory,
        std::unique_ptr<base::OneShotTimer> timer =
            std::make_unique<base::OneShotTimer>());

    static void SetFactoryForTesting(Factory* test_factory);

   protected:
    virtual ~Factory();
    virtual std::unique_ptr<CryptAuthFeatureStatusSetter> CreateInstance(
        const std::string& instance_id,
        const std::string& instance_id_token,
        CryptAuthClientFactory* client_factory,
        std::unique_ptr<base::OneShotTimer> timer) = 0;

   private:
    static Factory* test_factory_;
  };

  CryptAuthFeatureStatusSetterImpl(const CryptAuthFeatureStatusSetterImpl&) =
      delete;
  CryptAuthFeatureStatusSetterImpl& operator=(
      const CryptAuthFeatureStatusSetterImpl&) = delete;

  ~CryptAuthFeatureStatusSetterImpl() override;

 private:
  enum class State { kIdle, kWaitingForBatchSetFeatureStatusesResponse };

  friend std::ostream& operator<<(std::ostream& stream, const State& state);

  static std::optional<base::TimeDelta> GetTimeoutForState(State state);

  struct Request {
    Request(const std::string& device_id,
            multidevice::SoftwareFeature feature,
            FeatureStatusChange status_change,
            base::OnceClosure success_callback,
            base::OnceCallback<void(NetworkRequestError)> error_callback);

    Request(Request&& request);

    ~Request();

    const std::string device_id;
    const multidevice::SoftwareFeature feature;
    const FeatureStatusChange status_change;
    base::OnceClosure success_callback;
    base::OnceCallback<void(NetworkRequestError)> error_callback;
  };

  CryptAuthFeatureStatusSetterImpl(const std::string& instance_id,
                                   const std::string& instance_id_token,
                                   CryptAuthClientFactory* client_factory,
                                   std::unique_ptr<base::OneShotTimer> timer);

  // CryptAuthFeatureStatusSetter:
  void SetFeatureStatus(
      const std::string& device_id,
      multidevice::SoftwareFeature feature,
      FeatureStatusChange status_change,
      base::OnceClosure success_callback,
      base::OnceCallback<void(NetworkRequestError)> error_callback) override;

  void SetState(State state);
  void OnTimeout();

  void ProcessRequestQueue();
  void OnBatchSetFeatureStatusesSuccess(
      const cryptauthv2::BatchSetFeatureStatusesResponse& response);
  void OnBatchSetFeatureStatusesFailure(NetworkRequestError error);
  void FinishAttempt(std::optional<NetworkRequestError> error);

  State state_ = State::kIdle;
  base::TimeTicks last_state_change_timestamp_;
  base::queue<Request> pending_requests_;

  std::string instance_id_;
  std::string instance_id_token_;
  raw_ptr<CryptAuthClientFactory> client_factory_ = nullptr;
  std::unique_ptr<CryptAuthClient> cryptauth_client_;
  std::unique_ptr<base::OneShotTimer> timer_;
  base::WeakPtrFactory<CryptAuthFeatureStatusSetterImpl> weak_ptr_factory_{
      this};
};

}  // namespace device_sync

}  // namespace ash

#endif  //  CHROMEOS_ASH_SERVICES_DEVICE_SYNC_CRYPTAUTH_FEATURE_STATUS_SETTER_IMPL_H_