Skip to content

Commit

Permalink
Upstream go/eurcl/1009691
Browse files Browse the repository at this point in the history
This adds logic to wait for a DRM key to be available before pushing
encrypted buffers to starboard.

Test: added unit tests and fixed test breakages.

Bug: b:271608792
Change-Id: I158232cf696b67c740d8a3b20f67289a7d25cfb7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5767244
Commit-Queue: Antonio Rivera <antoniori@google.com>
Reviewed-by: Shawn Quereshi <shawnq@google.com>
Cr-Commit-Position: refs/heads/main@{#1338183}
  • Loading branch information
antoniori-eng authored and Chromium LUCI CQ committed Aug 6, 2024
1 parent 82569e3 commit d56c2f0
Show file tree
Hide file tree
Showing 13 changed files with 1,006 additions and 7 deletions.
1 change: 1 addition & 0 deletions chromecast/starboard/media/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import("//chromecast/build/tests/cast_test.gni")
cast_test_group("starboard_media_tests") {
tests = [
"//chromecast/starboard/media/cdm:starboard_decryptor_cast_test",
"//chromecast/starboard/media/cdm:starboard_drm_key_tracker_test",
"//chromecast/starboard/media/media:media_pipeline_backend_starboard_test",
"//chromecast/starboard/media/media:mime_utils_test",
"//chromecast/starboard/media/media:starboard_audio_decoder_test",
Expand Down
22 changes: 22 additions & 0 deletions chromecast/starboard/media/cdm/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ cast_source_set("starboard_decryptor_cast") {
]

deps = [
":starboard_drm_key_tracker",
"//base",
"//chromecast/media/base",
"//chromecast/media/cdm",
Expand All @@ -22,10 +23,20 @@ cast_source_set("starboard_decryptor_cast") {
]
}

cast_source_set("starboard_drm_key_tracker") {
sources = [
"starboard_drm_key_tracker.cc",
"starboard_drm_key_tracker.h",
]

deps = [ "//base" ]
}

test("starboard_decryptor_cast_test") {
sources = [ "starboard_decryptor_cast_test.cc" ]
deps = [
":starboard_decryptor_cast",
":starboard_drm_key_tracker",
"//base",
"//base/test:run_all_unittests",
"//base/test:test_support",
Expand All @@ -37,3 +48,14 @@ test("starboard_decryptor_cast_test") {
"//url:url",
]
}

test("starboard_drm_key_tracker_test") {
sources = [ "starboard_drm_key_tracker_test.cc" ]
deps = [
":starboard_drm_key_tracker",
"//base/test:run_all_unittests",
"//base/test:test_support",
"//testing/gmock",
"//testing/gtest",
]
}
68 changes: 66 additions & 2 deletions chromecast/starboard/media/cdm/starboard_decryptor_cast.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/hash/hash.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "chromecast/media/base/decrypt_context_impl.h"
#include "chromecast/starboard/media/cdm/starboard_drm_key_tracker.h"
#include "chromecast/starboard/media/media/starboard_api_wrapper.h"
#include "google_apis/google_api_keys.h"
#include "media/base/cdm_callback_promise.h"
Expand Down Expand Up @@ -59,6 +61,25 @@ class DummyDecryptContext : public DecryptContextImpl {
}
};

std::string DrmKeyStatusToString(StarboardDrmKeyStatus status) {
switch (status) {
case kStarboardDrmKeyStatusUsable:
return "kStarboardDrmKeyStatusUsable";
case kStarboardDrmKeyStatusExpired:
return "kStarboardDrmKeyStatusExpired";
case kStarboardDrmKeyStatusReleased:
return "kStarboardDrmKeyStatusReleased";
case kStarboardDrmKeyStatusRestricted:
return "kStarboardDrmKeyStatusRestricted";
case kStarboardDrmKeyStatusDownscaled:
return "kStarboardDrmKeyStatusDownscaled";
case kStarboardDrmKeyStatusPending:
return "kStarboardDrmKeyStatusPending";
case kStarboardDrmKeyStatusError:
return "kStarboardDrmKeyStatusError";
}
}

// Converts a starboard DRM status to a CdmPromise exception. This must not be
// called for a success status. Defaults to NotSupportedError.
::media::CdmPromise::Exception StarboardDrmErrorStatusToCdmException(
Expand Down Expand Up @@ -241,8 +262,9 @@ void StarboardDecryptorCast::CloseSession(
promises.push_back(std::move(promise));

if (promises.size() == 1) {
// This is the first request to close the session; call starboard to perform
// the close logic.
// This is the first request to close the session; mark the session as
// removed and call starboard to perform the close logic
StarboardDrmKeyTracker::GetInstance().RemoveKeysForSession(web_session_id);
starboard_->DrmCloseSession(drm_system_, web_session_id.c_str(),
web_session_id.size());
} else {
Expand Down Expand Up @@ -308,9 +330,14 @@ StarboardDecryptorCast::~StarboardDecryptorCast() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

if (drm_system_) {
LOG(INFO) << "Destroying DRM system with address " << drm_system_;
// Once this call returns, all DRM-related callbacks from Starboard are
// guaranteed to be finished.
starboard_->DrmDestroySystem(drm_system_);

for (const std::string& session_id : session_ids_) {
StarboardDrmKeyTracker::GetInstance().RemoveKeysForSession(session_id);
}
}

RejectPendingPromises();
Expand All @@ -328,6 +355,7 @@ void StarboardDecryptorCast::InitializeInternal() {
/*key_system=*/"com.widevine.alpha",
/*callback_handler=*/&callback_handler_);
CHECK(drm_system_) << "Failed to create an SbDrmSystem";
LOG(INFO) << "Created DRM system with address " << drm_system_;

server_certificate_updatable_ =
starboard_->DrmIsServerCertificateUpdatable(drm_system_);
Expand Down Expand Up @@ -583,6 +611,16 @@ void StarboardDecryptorCast::OnKeyStatusesChanged(
const StarboardDrmKeyId& key_id = key_id_and_status.first;
const StarboardDrmKeyStatus status = key_id_and_status.second;

const std::string key_name(
reinterpret_cast<const char*>(&key_id.identifier),
key_id.identifier_size);
CHECK_GE(key_id.identifier_size, 0);
const size_t key_hash = base::FastHash(base::make_span(
key_id.identifier, static_cast<size_t>(key_id.identifier_size)));
LOG(INFO) << "DRM key (hash) " << key_hash << " changed status to "
<< DrmKeyStatusToString(status) << " for DRM system with address "
<< drm_system << ", for session " << session_id;

auto key_info = std::make_unique<::media::CdmKeyInformation>();
key_info->key_id.assign(key_id.identifier,
key_id.identifier + key_id.identifier_size);
Expand All @@ -591,6 +629,32 @@ void StarboardDecryptorCast::OnKeyStatusesChanged(

usable_keys_exist =
usable_keys_exist || (status == kStarboardDrmKeyStatusUsable);

switch (status) {
case kStarboardDrmKeyStatusUsable:
case kStarboardDrmKeyStatusRestricted:
case kStarboardDrmKeyStatusDownscaled:
// As long as the key is available to the DRM system in some way, we
// should treat the key as available so that the MediaPipelineBackend
// can push buffers for the given key.
StarboardDrmKeyTracker::GetInstance().AddKey(key_name, session_id);
break;
case kStarboardDrmKeyStatusPending:
// The key status will be updated later; do nothing for now.
break;
case kStarboardDrmKeyStatusExpired:
case kStarboardDrmKeyStatusReleased:
case kStarboardDrmKeyStatusError:
// The key is no longer usable.
StarboardDrmKeyTracker::GetInstance().RemoveKey(key_name, session_id);
break;
}
}

if (usable_keys_exist) {
// At least one key is available for the session, so we need to track the
// session to clean it up on destruction.
session_ids_.insert(session_id);
}

OnSessionKeysChange(session_id, usable_keys_exist, std::move(keys_info));
Expand Down
5 changes: 5 additions & 0 deletions chromecast/starboard/media/cdm/starboard_decryptor_cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#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"
Expand Down Expand Up @@ -231,6 +232,10 @@ class StarboardDecryptorCast : public CastCdm {
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};
};
Expand Down
102 changes: 102 additions & 0 deletions chromecast/starboard/media/cdm/starboard_decryptor_cast_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "base/functional/callback.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "chromecast/starboard/media/cdm/starboard_drm_key_tracker.h"
#include "chromecast/starboard/media/media/mock_starboard_api_wrapper.h"
#include "chromecast/starboard/media/media/starboard_api_wrapper.h"
#include "media/base/cdm_callback_promise.h"
Expand Down Expand Up @@ -282,6 +283,8 @@ TEST_F(StarboardDecryptorCastTest, SendsSessionUpdateToStarboard) {
}

TEST_F(StarboardDecryptorCastTest, CallsKeyChangeCallbackOnKeyUpdate) {
StarboardDrmKeyTracker::GetInstance().ClearStateForTesting();

const std::string session_id = "some_session";
const std::vector<uint8_t> key_id = {1, 2, 3, 4, 5, 6};
const auto key_status = CdmKeyInformation::KeyStatus::USABLE;
Expand Down Expand Up @@ -338,6 +341,105 @@ TEST_F(StarboardDecryptorCastTest, CallsKeyChangeCallbackOnKeyUpdate) {
// The functions in decryptor_provided_callbacks post tasks to
// task_environment_.
task_environment_.RunUntilIdle();

// Verify that StarboardDrmKeyTracker was updated.
const std::string key_str(reinterpret_cast<const char*>(key_id.data()),
key_id.size());
EXPECT_TRUE(StarboardDrmKeyTracker::GetInstance().HasKey(key_str));

// Verify that the key is removed when the decryptor is destroyed.
decryptor = nullptr;
EXPECT_FALSE(StarboardDrmKeyTracker::GetInstance().HasKey(key_str));
}

TEST_F(StarboardDecryptorCastTest,
RemovesKeyFromStarboardDrmKeyTrackerWhenKeyIsReleased) {
StarboardDrmKeyTracker::GetInstance().ClearStateForTesting();

const std::string session_id = "some_session";
const std::vector<uint8_t> key_id = {1, 2, 3, 4, 5, 6};
const auto key_status = CdmKeyInformation::KeyStatus::USABLE;
const auto key_released_status = CdmKeyInformation::KeyStatus::RELEASED;
const uint32_t system_code = 0;
// The starboard representation of key_id.
StarboardDrmKeyId starboard_key_id;
CHECK_LE(key_id.size(), NumElements(starboard_key_id.identifier));
memcpy(starboard_key_id.identifier, key_id.data(), key_id.size());
starboard_key_id.identifier_size = key_id.size();
// The starboard representation of key_status.
const StarboardDrmKeyStatus starboard_key_status =
kStarboardDrmKeyStatusUsable;
const StarboardDrmKeyStatus starboard_key_released_status =
kStarboardDrmKeyStatusReleased;

scoped_refptr<StarboardDecryptorCast> decryptor = new StarboardDecryptorCast(
/*create_provision_fetcher_cb=*/base::BindRepeating(
&StarboardDecryptorCastTest::CreateProvisionFetcher,
base::Unretained(this)),
/*media_resource_tracker=*/nullptr);

// This will get populated by decryptor.
const StarboardDrmSystemCallbackHandler* decryptor_provided_callbacks =
nullptr;

EXPECT_CALL(*starboard_, CreateDrmSystem("com.widevine.alpha", _))
.WillOnce(DoAll(SaveArg<1>(&decryptor_provided_callbacks),
Return(&fake_drm_system_)));

EXPECT_CALL(
session_keys_change_cb_,
Call(StrEq(session_id), true,
ElementsAre(Pointee(AllOf(
Field(&CdmKeyInformation::key_id, ElementsAreArray(key_id)),
Field(&CdmKeyInformation::status, Eq(key_status)),
Field(&CdmKeyInformation::system_code, Eq(system_code)))))))
.Times(1);
EXPECT_CALL(
session_keys_change_cb_,
Call(StrEq(session_id), false,
ElementsAre(Pointee(AllOf(
Field(&CdmKeyInformation::key_id, ElementsAreArray(key_id)),
Field(&CdmKeyInformation::status, Eq(key_released_status)),
Field(&CdmKeyInformation::system_code, Eq(system_code)))))))
.Times(1);

decryptor->SetStarboardApiWrapperForTest(std::move(starboard_));
decryptor->Initialize(
base::BindLambdaForTesting(session_message_cb_.AsStdFunction()),
base::BindLambdaForTesting(session_closed_cb_.AsStdFunction()),
base::BindLambdaForTesting(session_keys_change_cb_.AsStdFunction()),
base::BindLambdaForTesting(
session_expiration_update_cb_.AsStdFunction()));

ASSERT_THAT(decryptor_provided_callbacks, NotNull());
ASSERT_THAT(decryptor_provided_callbacks->key_statuses_changed_fn, NotNull());
// Notify the decryptor that the key status changed. This should trigger the
// expected call to session_keys_change_cb_ above.
decryptor_provided_callbacks->key_statuses_changed_fn(
&fake_drm_system_, decryptor_provided_callbacks->context,
session_id.c_str(), session_id.size(), /*number_of_keys=*/1,
&starboard_key_id, &starboard_key_status);

// The functions in decryptor_provided_callbacks post tasks to
// task_environment_.
task_environment_.RunUntilIdle();

// Verify that StarboardDrmKeyTracker was updated.
const std::string key_str(reinterpret_cast<const char*>(key_id.data()),
key_id.size());
EXPECT_TRUE(StarboardDrmKeyTracker::GetInstance().HasKey(key_str));

// Verify that the key is removed when the status changes to removed.
decryptor_provided_callbacks->key_statuses_changed_fn(
&fake_drm_system_, decryptor_provided_callbacks->context,
session_id.c_str(), session_id.size(), /*number_of_keys=*/1,
&starboard_key_id, &starboard_key_released_status);

// The functions in decryptor_provided_callbacks post tasks to
// task_environment_.
task_environment_.RunUntilIdle();

EXPECT_FALSE(StarboardDrmKeyTracker::GetInstance().HasKey(key_str));
}

TEST_F(StarboardDecryptorCastTest, CreatesSessionAndGeneratesLicenseRequest) {
Expand Down
Loading

0 comments on commit d56c2f0

Please sign in to comment.