// 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 CHROMECAST_STARBOARD_MEDIA_CDM_STARBOARD_DECRYPTOR_CAST_H_
#define CHROMECAST_STARBOARD_MEDIA_CDM_STARBOARD_DECRYPTOR_CAST_H_
#include <cstdint>
#include <memory>
#include <optional>
#include <queue>
#include <string>
#include <utility>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
#include "chromecast/media/cdm/cast_cdm.h"
#include "chromecast/starboard/media/media/starboard_api_wrapper.h"
#include "media/base/cdm_promise.h"
#include "media/base/provision_fetcher.h"
namespace chromecast {
namespace media {
// An implementation of CastCdm that forwards calls to an underlying Starboard
// SbDrmSystem. Currently only supports widevine (the key system passed to
// starboard is hardcoded, as is the provisioning server URL).
//
// The constructor, destructor, and all functions (except the Call* callbacks
// stored in callback_handler_) must be called on the same sequence.
class StarboardDecryptorCast : public CastCdm {
public:
explicit StarboardDecryptorCast(
::media::CreateFetcherCB create_provision_fetcher_cb,
MediaResourceTracker* media_resource_tracker);
// Disallow copy and assign.
StarboardDecryptorCast(const StarboardDecryptorCast&) = delete;
StarboardDecryptorCast& operator=(const StarboardDecryptorCast&) = delete;
// For testing purposes, `starboard` will be used to call starboard functions.
void SetStarboardApiWrapperForTest(
std::unique_ptr<StarboardApiWrapper> starboard);
// ::media::ContentDecryptionModule implementation:
void CreateSessionAndGenerateRequest(
::media::CdmSessionType session_type,
::media::EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data,
std::unique_ptr<::media::NewSessionCdmPromise> promise) override;
void LoadSession(
::media::CdmSessionType session_type,
const std::string& session_id,
std::unique_ptr<::media::NewSessionCdmPromise> promise) override;
void UpdateSession(
const std::string& web_session_id,
const std::vector<uint8_t>& response,
std::unique_ptr<::media::SimpleCdmPromise> promise) override;
void CloseSession(
const std::string& web_session_id,
std::unique_ptr<::media::SimpleCdmPromise> promise) override;
void RemoveSession(
const std::string& session_id,
std::unique_ptr<::media::SimpleCdmPromise> promise) override;
void SetServerCertificate(
const std::vector<uint8_t>& certificate_data,
std::unique_ptr<::media::SimpleCdmPromise> promise) override;
// CastCdm implementation:
std::unique_ptr<DecryptContextImpl> GetDecryptContext(
const std::string& key_id,
EncryptionScheme encryption_scheme) const override;
void SetKeyStatus(const std::string& key_id,
CastKeyStatus key_status,
uint32_t system_code) override;
void SetVideoResolution(int width, int height) override;
private:
using SessionRequest = std::pair<
base::OnceCallback<void(std::unique_ptr<::media::NewSessionCdmPromise>)>,
std::unique_ptr<::media::NewSessionCdmPromise>>;
~StarboardDecryptorCast() override;
// CastCdm implementation:
void InitializeInternal() override;
// Sends the actual request to create the session.
void HandleCreateSessionAndGenerateRequest(
::media::CdmSessionType session_type,
::media::EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data,
std::unique_ptr<::media::NewSessionCdmPromise> promise);
// Rejects all promises in queued_session_requests_.
void RejectPendingPromises();
// Processes any pending requests to create sessions.
void ProcessQueuedSessionRequests();
// Sends a provision request to the Widevine licensing server. This is used to
// create cert.bin in starboard. Note that -- for the gLinux implementation of
// starboard, at least -- cert.bin is not an actual file. It's a
// logical/conceptual file, and its stored in the cache at
// ~/.cache/cobalt/wvcdm.dat.
void SendProvisionRequest(int ticket,
const std::string& session_id,
const std::vector<uint8_t>& content);
// Called by starboard (via CallOnSessionUpdateRequest) once a new session has
// been created.
void OnSessionUpdateRequest(void* drm_system,
int ticket,
StarboardDrmStatus status,
StarboardDrmSessionRequestType type,
std::optional<std::string> error_message,
std::optional<std::string> session_id,
std::vector<uint8_t> content);
// Called by starboard (via CallOnSessionUpdated) once a session has been
// updated.
void OnSessionUpdated(void* drm_system,
int ticket,
StarboardDrmStatus status,
std::optional<std::string> error_message,
std::optional<std::string> session_id);
// Called by starboard (via CallOnKeyStatusesChanged) when the status of keys
// change.
void OnKeyStatusesChanged(
void* drm_system,
std::string session_id,
std::vector<std::pair<StarboardDrmKeyId, StarboardDrmKeyStatus>>
key_ids_and_statuses);
// Called by starboard (via CallOnCertificateUpdated) when a certificate has
// been updated.
void OnCertificateUpdated(void* drm_system,
int ticket,
StarboardDrmStatus status,
std::optional<std::string> error_message);
// Called by starboard (via CallOnSessionClosed) when a session has closed.
void OnSessionClosed(void* drm_system, std::string session_id);
// Calls OnSessionUpdateRequest for `context`, which is an instance of
// StarboardDecryptorCast.
static void CallOnSessionUpdateRequest(void* drm_system,
void* context,
int ticket,
StarboardDrmStatus status,
StarboardDrmSessionRequestType type,
const char* error_message,
const void* session_id,
int session_id_size,
const void* content,
int content_size,
const char* url);
// Calls OnSessionUpdated for `context`, which is an instance of
// StarboardDecryptorCast.
static void CallOnSessionUpdated(void* drm_system,
void* context,
int ticket,
StarboardDrmStatus status,
const char* error_message,
const void* session_id,
int session_id_size);
// Calls OnKeyStatusesChanged for `context`, which is an instance of
// StarboardDecryptorCast.
static void CallOnKeyStatusesChanged(
void* drm_system,
void* context,
const void* session_id,
int session_id_size,
int number_of_keys,
const StarboardDrmKeyId* key_ids,
const StarboardDrmKeyStatus* key_statuses);
// Calls OnCertificateUpdated for `context`, which is an instance of
// StarboardDecryptorCast.
static void CallOnCertificateUpdated(void* drm_system,
void* context,
int ticket,
StarboardDrmStatus status,
const char* error_message);
// Calls OnSessionClosed for `context`, which is an instance of
// StarboardDecryptorCast.
static void CallOnSessionClosed(void* drm_system,
void* context,
const void* session_id,
int session_id_size);
// Called when provisioning the device, in response to an individualization
// request.
void OnProvisionResponse(int ticket,
const std::string& session_id,
bool success,
const std::string& response);
THREAD_CHECKER(thread_checker_);
::media::CreateFetcherCB create_provision_fetcher_cb_;
StarboardDrmSystemCallbackHandler callback_handler_;
std::unique_ptr<StarboardApiWrapper> starboard_;
bool server_certificate_updatable_ = false;
std::queue<SessionRequest> queued_session_requests_;
// Ticket used to track session update requests.
int current_ticket_ = 0;
base::flat_map<int, std::unique_ptr<::media::NewSessionCdmPromise>>
ticket_to_new_session_promise_;
base::flat_map<int, std::unique_ptr<::media::SimpleCdmPromise>>
ticket_to_simple_cdm_promise_;
base::flat_map<std::string,
std::vector<std::unique_ptr<::media::SimpleCdmPromise>>>
session_id_to_simple_cdm_promises_;
// There's a session create/load in processing. If true, all the new session
// create/load operations should be queued.
bool pending_session_setup_ = false;
// An opaque handle to an SbDrmSystem instance.
void* drm_system_ = nullptr;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
std::unique_ptr<::media::ProvisionFetcher> provision_fetcher_;
// Tracks all sessions that have had at least one key created. Used to clean
// up keys in StarboardDrmKeyTracker on destruction.
base::flat_set<std::string> session_ids_;
// This must be destructed first, to invalidate any remaining weak ptrs.
base::WeakPtrFactory<StarboardDecryptorCast> weak_factory_{this};
};
} // namespace media
} // namespace chromecast
#endif // CHROMECAST_STARBOARD_MEDIA_CDM_STARBOARD_DECRYPTOR_CAST_H_