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

#ifndef NET_DEVICE_BOUND_SESSIONS_SESSION_SERVICE_H_
#define NET_DEVICE_BOUND_SESSIONS_SESSION_SERVICE_H_

#include <memory>

#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "net/base/net_export.h"
#include "net/device_bound_sessions/deletion_reason.h"
#include "net/device_bound_sessions/registration_fetcher_param.h"
#include "net/device_bound_sessions/session.h"
#include "net/device_bound_sessions/session_access.h"
#include "net/device_bound_sessions/session_challenge_param.h"
#include "net/device_bound_sessions/session_key.h"
#include "net/log/net_log_with_source.h"

namespace net {
class FirstPartySetMetadata;
class IsolationInfo;
class URLRequest;
class URLRequestContext;
class HttpRequestHeaders;
}

namespace net::device_bound_sessions {

// Main class for Device Bound Session Credentials (DBSC).
// Full information can be found at https://github.com/WICG/dbsc
class NET_EXPORT SessionService {
 public:
  using OnAccessCallback = base::RepeatingCallback<void(const SessionAccess&)>;

  // Records the outcome of an attempt to refresh.
  enum class RefreshResult {
    kRefreshed,           // Refresh was successful.
    kInitializedService,  // Service is now initialized, refresh may still be
                          // needed.
    kUnreachable,         // Refresh endpoint was unreachable.
    kServerError,         // Refresh endpoint served a transient error.
    kQuotaExceeded,       // Refresh quota exceeded.
    kFatalError,          // Refresh failed and session was terminated. No
                          // further refresh needed.
  };
  using RefreshCompleteCallback = base::OnceCallback<void(RefreshResult)>;

  // Indicates the reason for deferring. Exactly one of
  // `is_pending_initialization` or `session_id` will be truthy.
  struct NET_EXPORT DeferralParams {
    // Construct with `is_pending_initialization` set to true.
    DeferralParams();

    // Constructor with `session_id` having a value
    explicit DeferralParams(Session::Id session_id);
    ~DeferralParams();

    DeferralParams(const DeferralParams&);
    DeferralParams& operator=(const DeferralParams&);

    DeferralParams(DeferralParams&&);
    DeferralParams& operator=(DeferralParams&&);

    // Set to true when we defer due to missing initialization.
    bool is_pending_initialization;

    // If `is_pending_initialization` is false, we're deferring due to
    // missing credentials on this session.
    std::optional<Session::Id> session_id;
  };

  // Returns nullptr if unexportable key provider is not supported by the
  // platform or the device.
  static std::unique_ptr<SessionService> Create(
      const URLRequestContext* request_context);

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

  virtual ~SessionService() = default;

  // Called to register a new session after getting a Sec-Session-Registration
  // header.
  // Registration parameters to be used for creating the registration
  // request.
  // Isolation info to be used for registration request, this should be the
  // same as was used for the response with the Sec-Session-Registration
  // header.
  // `net_log` is the log corresponding to the request receiving the
  // Sec-Session-Registration header.
  // 'original_request_initiator` was the initiator for the request that
  // received the Sec-Session-Registration header.
  virtual void RegisterBoundSession(
      OnAccessCallback on_access_callback,
      RegistrationFetcherParam registration_params,
      const IsolationInfo& isolation_info,
      const NetLogWithSource& net_log,
      const std::optional<url::Origin>& original_request_initiator) = 0;

  // Check if a request should be deferred due to the session cookie being
  // missing. This should only be called once the request has the correct
  // cookies added to the request.
  // If multiple sessions needs to be refreshed for this request,
  // any of them can be returned.
  // Returns a `DeferralParams` setting `is_pending_initialization` if
  // the request should be deferred while waiting for initialization, a
  // `DeferralParams` containing the session id if the request should be
  // deferred due to a session, and returns std::nullopt if the request
  // does not need to be deferred.
  // If sessions are skipped without deferring, they will be added to
  // the Secure-Session-Skipped header in `extra_headers`.
  virtual std::optional<DeferralParams> ShouldDefer(
      URLRequest* request,
      HttpRequestHeaders* extra_headers,
      const FirstPartySetMetadata& first_party_set_metadata) = 0;

  // Defer a request and maybe refresh the corresponding session.
  // `deferral` is either the identifier of the session that is required to be
  // refreshed, or indicates the service is not completely initialized.
  // This will refresh the corresponding session if: another deferred request
  // has not already kicked off refresh, the session can be found, and the
  // associated unexportable key id is valid.
  // On completion, calls `callback`.
  virtual void DeferRequestForRefresh(URLRequest* request,
                                      DeferralParams deferral,
                                      RefreshCompleteCallback callback) = 0;

  // Set the challenge for a bound session after getting a
  // Sec-Session-Challenge header.
  virtual void SetChallengeForBoundSession(
      OnAccessCallback on_access_callback,
      const GURL& request_url,
      const SessionChallengeParam& param) = 0;

  // Get all sessions. If sessions have not yet been loaded from disk,
  // defer until completely initialized.
  virtual void GetAllSessionsAsync(
      base::OnceCallback<void(const std::vector<SessionKey>&)> callback) = 0;

  // Delete the session matching `session_key`, notifying
  // `per_request_callback` about any deletions.
  virtual void DeleteSessionAndNotify(
      DeletionReason reason,
      const SessionKey& session_key,
      SessionService::OnAccessCallback per_request_callback) = 0;

  // Delete all sessions that match the filtering arguments. See
  // `device_bound_sessions.mojom` for details on the filtering logic.
  virtual void DeleteAllSessions(
      DeletionReason reason,
      std::optional<base::Time> created_after_time,
      std::optional<base::Time> created_before_time,
      base::RepeatingCallback<bool(const url::Origin&,
                                   const net::SchemefulSite&)>
          origin_and_site_matcher,
      base::OnceClosure completion_callback) = 0;

  // Add an observer for session changes that include `url`. `callback`
  // will only be notified until the destruction of the returned
  // `ScopedClosureRunner`.
  virtual base::ScopedClosureRunner AddObserver(
      const GURL& url,
      base::RepeatingCallback<void(const SessionAccess&)> callback) = 0;

 protected:
  SessionService() = default;
};

}  // namespace net::device_bound_sessions

#endif  // NET_DEVICE_BOUND_SESSIONS_SESSION_SERVICE_H_
