diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp index 09f99a9e..0ed472e7 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp @@ -18,6 +18,7 @@ #include "device_files.h" #include "file_store.h" #include "initialization_data.h" +#include "license_holder.h" #include "license_request.h" #include "log.h" #include "properties.h" @@ -387,14 +388,11 @@ TEST_F(WvCdmEngineTest, DISABLED_NormalDecryptionWebm) { TEST_F(WvCdmEngineTest, LoadKey) { EnsureProvisioned(); - TestLicenseHolder holder(&cdm_engine_); - holder.OpenSession(config_.key_system()); - holder.GenerateKeyRequest(binary_key_id(), ISO_BMFF_VIDEO_MIME_TYPE); - holder.CreateDefaultLicense(); - std::vector key_data(CONTENT_KEY_SIZE, '1'); - wvoec::KeyControlBlock block = {}; - holder.AddKey("key_one", key_data, block); - holder.SignAndLoadLicense(); + LicenseHolder holder("CDM_Streaming", &cdm_engine_, config_); + ASSERT_NO_FATAL_FAILURE(holder.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder.FetchLicense()); + ASSERT_NO_FATAL_FAILURE(holder.LoadLicense()); + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); } // This test generates a renewal and then requests the renewal using the server @@ -402,6 +400,7 @@ TEST_F(WvCdmEngineTest, LoadKey) { // skip this test when you want to set the license and renewal server on the // command line. TEST_F(WvCdmEngineTest, LicenseRenewalSpecifiedServer) { + EnsureProvisioned(); GenerateKeyRequest(binary_key_id(), kCencMimeType); VerifyNewKeyResponse(config_.license_server(), config_.client_auth()); @@ -423,6 +422,7 @@ TEST_F(WvCdmEngineTest, LicenseRenewalSpecifiedServer) { // This test generates a renewal and then requests it from the server specified // by the current test configuration. TEST_F(WvCdmEngineTest, LicenseRenewal) { + EnsureProvisioned(); GenerateKeyRequest(binary_key_id(), kCencMimeType); VerifyNewKeyResponse(config_.license_server(), config_.client_auth()); diff --git a/libwvdrmengine/cdm/core/test/duration_use_case_test.cpp b/libwvdrmengine/cdm/core/test/duration_use_case_test.cpp index 25fa6925..31b8fd7b 100644 --- a/libwvdrmengine/cdm/core/test/duration_use_case_test.cpp +++ b/libwvdrmengine/cdm/core/test/duration_use_case_test.cpp @@ -18,7 +18,7 @@ #include "clock.h" #include "config_test_env.h" #include "initialization_data.h" -#include "license_request.h" +#include "license_holder.h" #include "log.h" #include "metrics_collections.h" #include "odk_structs.h" @@ -36,8 +36,6 @@ namespace wvcdm { namespace { -constexpr int kHttpOk = 200; -const std::string kCencMimeType = "cenc"; // How many seconds to fudge the start or stop playback times. We fudge these // times because there might be a little round off when sleeping, and because // the time that the license was signed or loaded might be off a little bit due @@ -86,27 +84,6 @@ const RenewalPolicy kHeartbeatRenewal = {"CDM_Heartbeat_renewal", 10, 30}; // Key ID in all duration tests. const KeyId kKeyId = "Duration_Key===="; - -class SimpleEventListener : public wvcdm::WvCdmEventListener { - public: - SimpleEventListener() { renewal_needed_ = false; } - // We will want to know when a renewal is needed. - void OnSessionRenewalNeeded(const CdmSessionId& /*session*/) override { - renewal_needed_ = true; - } - void OnSessionKeysChange(const CdmSessionId&, const CdmKeyStatusMap&, - bool) override {} - void OnExpirationUpdate(const CdmSessionId&, - int64_t expiry_time UNUSED) override {} - bool renewal_needed() { return renewal_needed_; } - void set_renewal_needed(bool renewal_needed) { - renewal_needed_ = renewal_needed; - } - - private: - bool renewal_needed_; -}; - } // namespace // All duration tests are parameterized by can_persist = true or false. @@ -114,7 +91,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, public ::testing::WithParamInterface { public: CdmDurationTest(const std::string& content_id) - : content_id_(content_id), + : license_holder_(content_id, &cdm_engine_, config_), first_load_occurred_(false), allow_lenience_(false) { // These are reasonable initial values for most tests. This is an unlimited @@ -150,24 +127,22 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, void SetUp() override { WvCdmTestBase::SetUp(); EnsureProvisioned(); - ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id_)); - can_persist_ = GetParam(); - if (can_persist_) { + license_holder_.set_can_persist(GetParam()); + ASSERT_NO_FATAL_FAILURE(license_holder_.OpenSession()); + if (license_holder_.can_persist()) { // Each content/policy in a test class below should match two policies in // UAT. One policy id matches the string exactly, and one has // _can_persist appended. - content_id_ = content_id_ + "_can_persist"; - license_type_ = kLicenseTypeOffline; - } else { - license_type_ = kLicenseTypeStreaming; + license_holder_.set_content_id(license_holder_.content_id() + + "_can_persist"); } // All times in the license are relative to the rental clock. start_of_rental_clock_ = wvutil::Clock().GetCurrentTime(); - FetchLicense(); + license_holder_.FetchLicense(); } void TearDown() override { - cdm_engine_.CloseSession(session_id_); + license_holder_.CloseSession(); // Log the time used in this test suite. When this comment was written, // these tests took over three hours. If we want to improve that, we need to // track these times. @@ -223,99 +198,20 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, return now - start_of_rental_clock_; } - void OpenSession(CdmSessionId* session_id) { - CdmResponseType status = cdm_engine_.OpenSession( - config_.key_system(), nullptr, &event_listener_, session_id); - ASSERT_EQ(NO_ERROR, status); - ASSERT_TRUE(cdm_engine_.IsOpenSession(*session_id)); - } - - void CloseSession(const CdmSessionId& session_id) { - CdmResponseType status = cdm_engine_.CloseSession(session_id); - ASSERT_EQ(NO_ERROR, status); - ASSERT_FALSE(cdm_engine_.IsOpenSession(session_id)); - } - - void FetchLicense() { - video_widevine::WidevinePsshData pssh; - pssh.set_content_id(content_id_); - const std::string init_data_string = MakePSSH(pssh); - const InitializationData init_data(kCencMimeType, init_data_string); - init_data.DumpToLogs(); - CdmKeyRequest key_request; - ASSERT_NO_FATAL_FAILURE(GenerateKeyRequest(init_data, &key_request)); - ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request)); - } - - void GenerateKeyRequest(const InitializationData& init_data, - CdmKeyRequest* key_request) { - CdmAppParameterMap empty_app_parameters; - CdmKeySetId empty_key_set_id; - CdmResponseType result = cdm_engine_.GenerateKeyRequest( - session_id_, empty_key_set_id, init_data, license_type_, - empty_app_parameters, key_request); - ASSERT_EQ(KEY_MESSAGE, result); - ASSERT_EQ(kKeyRequestTypeInitial, key_request->type); - } - - // Append the content/policy id for the test to the URL. - std::string MakeUrl(const std::string& server_url, - const std::string& policy_id) { - // For these tests, we want to specify the policy, but the UAT server only - // allows us to set the content id as the video_id. So each policy is - // matched to a single license with the same name. The local license - // server, on the other hand, wants to see the policy id in the url. So we - // have to guess which format to use based on the name of the server. - const std::string path = server_url + config_.client_auth(); - std::string video_query; - if (path.find("proxy.uat") != std::string::npos) { - // This is uat or uat-nightly. Set the video_id. - video_query = "video_id=" + policy_id; - } else { - // This is probably a local license server. Set the policy. - video_query = "policy=" + policy_id; - } - // If there is already a parameter, then we don't need to add another - // question mark. - return path + ((path.find("?") == std::string::npos) ? "?" : "&") + - video_query; - } - - void GetKeyResponse(const CdmKeyRequest& key_request) { - // The content id matches the policy id used on UAT. - const std::string url = MakeUrl(config_.license_server(), content_id_); - UrlRequest url_request(url); - ASSERT_TRUE(url_request.is_connected()); - - std::string http_response; - url_request.PostRequest(key_request.message); - ASSERT_TRUE(url_request.GetResponse(&http_response)); - int status_code = url_request.GetStatusCode(http_response); - ASSERT_EQ(kHttpOk, status_code) << "Error with url = " << url; - - LicenseRequest license_request; - license_request.GetDrmMessage(http_response, key_response_); - } - // This ensures that the licenses are loaded into the sessions. void LoadLicense() { if (!first_load_occurred_) { - CdmLicenseType license_type; - ASSERT_EQ(KEY_ADDED, cdm_engine_.AddKey(session_id_, key_response_, - &license_type, &key_set_id_)); - ASSERT_EQ(license_type_, license_type); + license_holder_.LoadLicense(); first_load_occurred_ = true; - } else if (can_persist_) { - // For the persistent license, we use restore key. - ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id_)); - CdmResponseType status = cdm_engine_.RestoreKey(session_id_, key_set_id_); - ASSERT_EQ(KEY_ADDED, status); + } else if (license_holder_.can_persist()) { + ASSERT_NO_FATAL_FAILURE(license_holder_.OpenSession()); + license_holder_.ReloadLicense(); } } void UnloadLicense() { - if (can_persist_) { - CloseSession(session_id_); + if (license_holder_.can_persist()) { + license_holder_.CloseSession(); } } @@ -380,12 +276,8 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, void AllowLenience() { allow_lenience_ = true; } void Decrypt() { - constexpr size_t buffer_size = 500; - const std::vector input(buffer_size, 0); - std::vector output(buffer_size, 0); - const std::vector iv(KEY_IV_SIZE, 0); const uint64_t now = CurrentRentalTime(); - EXPECT_EQ(NO_ERROR, Decrypt(session_id_, kKeyId, input, iv, &output)) + EXPECT_EQ(NO_ERROR, license_holder_.Decrypt(kKeyId)) << "Failed to decrypt when rental clock = " << now << ", and playback clock = " << ((now < start_of_playback_) ? 0 : (now - start_of_playback_)); @@ -403,17 +295,12 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, const bool low_end_device = (wvoec::global_features.api_version < wvoec::kCoreMessagesAPI || !wvoec::global_features.usage_table); - if (allow_lenience_ && low_end_device && can_persist_) { + if (allow_lenience_ && low_end_device && license_holder_.can_persist()) { allow_success = true; } allow_lenience_ = false; // Only allow lenience once per test. - - constexpr size_t buffer_size = 500; - const std::vector input(buffer_size, 0); - std::vector output(buffer_size, 0); - const std::vector iv(KEY_IV_SIZE, 0); const uint64_t now = CurrentRentalTime(); - CdmResponseType status = Decrypt(session_id_, kKeyId, input, iv, &output); + CdmResponseType status = license_holder_.Decrypt(kKeyId); // We always allow failure. that's what we usually expect. if (status == NEED_KEY) return; // No other error code is allowed: either NO_ERROR or NEED_KEY. @@ -428,26 +315,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, << (now < start_of_playback_ ? 0 : now - start_of_playback_); } - CdmResponseType Decrypt(const CdmSessionId& session_id, const KeyId& key_id, - const std::vector& input, - const std::vector& iv, - std::vector* output) { - CdmDecryptionParametersV16 params(key_id); - params.is_secure = false; - CdmDecryptionSample sample(input.data(), output->data(), 0, input.size(), - iv); - CdmDecryptionSubsample subsample(0, input.size()); - sample.subsamples.push_back(subsample); - params.samples.push_back(sample); - - return cdm_engine_.DecryptV16(session_id, params); - } - - CdmSessionId session_id_; - std::string content_id_; - CdmLicenseType license_type_; - CdmKeySetId key_set_id_; - std::string key_response_; + LicenseHolder license_holder_; // Time license requests generated. All test times are relative to this value. uint64_t start_of_rental_clock_; // The start of playback. This is set to the planned start at the beginning of @@ -457,9 +325,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, // not have to reload the streaming license, and we should use RestoreKey // instead of AddKey for the offline license. bool first_load_occurred_; - bool can_persist_; ODK_TimerLimits timer_limits_; - SimpleEventListener event_listener_; // If this is set, then the next time we expect a playback to be terminated, // we will allow lenient failure. bool allow_lenience_; @@ -751,7 +617,8 @@ TEST_P(CdmUseCase_SevenHardTwoSoft, Case4) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 4 * kPlayDuration); UnloadLicense(); - if (!can_persist_) return; // streaming license cannot restart. + // streaming license cannot restart. + if (!license_holder_.can_persist()) return; AllowLenience(); ForbidPlayback(EndOfPlaybackWindow() + kFudge); } @@ -947,7 +814,8 @@ TEST_P(CdmUseCase_SevenSoftTwoSoft, Case4) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 4 * kPlayDuration); UnloadLicense(); - if (!can_persist_) return; // streaming license cannot restart. + // streaming license cannot restart. + if (!license_holder_.can_persist()) return; AllowLenience(); ForbidPlayback(EndOfPlaybackWindow() + kFudge); } @@ -989,7 +857,7 @@ TEST_P(CdmUseCase_SevenSoftTwoSoft, Case7) { EndOfPlaybackWindow() + kPlayDuration); UnloadLicense(); // But forbid restart after playback window. - if (!can_persist_) return; // streaming license cannot restart. + if (!license_holder_.can_persist()) return; ForbidPlayback(EndOfPlaybackWindow() + 2 * kPlayDuration); } @@ -1028,7 +896,7 @@ class RenewalTest : public CdmDurationTest { // Sleep until a renewal request event is generated. If one does not occur // before cutoff, an error happens. void SleepUntilRenewalNeeded(uint64_t cutoff) { - while (!event_listener_.renewal_needed()) { + while (!license_holder_.event_listener().renewal_needed()) { uint64_t now = CurrentRentalTime(); ASSERT_LT(now, cutoff); SleepUntil(now + 1); @@ -1042,34 +910,18 @@ class RenewalTest : public CdmDurationTest { } void RequestRenewal(const RenewalPolicy& renewal_policy) { - event_listener_.set_renewal_needed(false); - CdmKeyRequest request; - const CdmResponseType result = - cdm_engine_.GenerateRenewalRequest(session_id_, &request); - ASSERT_EQ(KEY_MESSAGE, result); - const std::string url = - MakeUrl(config_.renewal_server(), renewal_policy.policy_id); - renewal_in_flight_.reset(new UrlRequest(url)); - ASSERT_TRUE(renewal_in_flight_->is_connected()); - renewal_in_flight_->PostRequest(request.message); + license_holder_.GenerateAndPostRenewalRequest(renewal_policy.policy_id); } void LoadRenewal(uint64_t time_of_load, const RenewalPolicy& renewal_policy) { - ASSERT_NE(renewal_in_flight_, nullptr); - std::string http_response; // Most of the network latency will probably show up in the next few // commands. I think the tests have enough slop to account for reasonable // latency with the current value of kRoundTripTime. But we'll know I made a // mistake if we see errors about "Test Clock skew..." in the SleepUntil // call below. - ASSERT_TRUE(renewal_in_flight_->GetResponse(&http_response)); - int status_code = renewal_in_flight_->GetStatusCode(http_response); - ASSERT_EQ(kHttpOk, status_code); - + license_holder_.FetchRenewal(); SleepUntil(time_of_load); - LicenseRequest license_request; - license_request.GetDrmMessage(http_response, renewal_message_); - EXPECT_EQ(KEY_ADDED, cdm_engine_.RenewKey(session_id_, renewal_message_)); + license_holder_.LoadRenewal(); ComputeCutoff(time_of_load, renewal_policy); } @@ -1260,7 +1112,7 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case4) { RenewAndContinue(start, load_time, stop, kShortRenewal); } UnloadLicense(); - if (can_persist_) { + if (license_holder_.can_persist()) { // Reload license after timer expired. const uint64_t reload_time = current_cutoff_ + 20; ReloadAndAllowPlayback( @@ -1464,7 +1316,7 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case4) { RenewAndContinue(start, load_time, stop, kShortRenewal); } UnloadLicense(); - if (can_persist_) { + if (license_holder_.can_persist()) { // Reload license after timer expired. const uint64_t reload_time = current_cutoff_ + 20; ReloadAndAllowPlayback( diff --git a/libwvdrmengine/cdm/core/test/license_holder.cpp b/libwvdrmengine/cdm/core/test/license_holder.cpp new file mode 100644 index 00000000..140029de --- /dev/null +++ b/libwvdrmengine/cdm/core/test/license_holder.cpp @@ -0,0 +1,227 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include "license_holder.h" + +#include "license_request.h" +#include "oec_device_features.h" +#include "test_base.h" + +namespace wvcdm { +namespace { +constexpr int kHttpOk = 200; +const std::string kCencMimeType = "cenc"; +} // namespace + +LicenseHolder::~LicenseHolder() {} + +void LicenseHolder::OpenSession() { + CdmResponseType status = cdm_engine_->OpenSession( + config_.key_system(), nullptr, &event_listener_, &session_id_); + ASSERT_EQ(NO_ERROR, status); + ASSERT_TRUE(cdm_engine_->IsOpenSession(session_id_)); +} + +void LicenseHolder::CloseSession() { + CdmResponseType status = cdm_engine_->CloseSession(session_id_); + ASSERT_EQ(NO_ERROR, status); + ASSERT_FALSE(cdm_engine_->IsOpenSession(session_id_)); +} + +void LicenseHolder::FetchLicense() { + video_widevine::WidevinePsshData pssh; + pssh.set_content_id(content_id_); + const std::string init_data_string = MakePSSH(pssh); + const InitializationData init_data(kCencMimeType, init_data_string); + init_data.DumpToLogs(); + CdmKeyRequest key_request; + ASSERT_NO_FATAL_FAILURE(GenerateKeyRequest(init_data, &key_request)) + << "Failed for " << content_id(); + ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request)) + << "Failed for " << content_id(); +} + +void LicenseHolder::LoadLicense() { + CdmLicenseType license_type; + ASSERT_EQ(KEY_ADDED, cdm_engine_->AddKey(session_id_, key_response_, + &license_type, &key_set_id_)) + << "Failed to load license for " << content_id(); + if (can_persist_) { + ASSERT_EQ(license_type, kLicenseTypeOffline) + << "Failed for " << content_id(); + } else { + ASSERT_EQ(license_type, kLicenseTypeStreaming) + << "Failed for " << content_id(); + } +} + +void LicenseHolder::ReloadLicense() { + CdmResponseType status = cdm_engine_->RestoreKey(session_id_, key_set_id_); + ASSERT_EQ(KEY_ADDED, status) + << "Failed to reload license for " << content_id(); +} + +void LicenseHolder::GenerateAndPostRenewalRequest( + const std::string& policy_id) { + event_listener_.set_renewal_needed(false); + CdmKeyRequest request; + const CdmResponseType result = + cdm_engine_->GenerateRenewalRequest(session_id_, &request); + ASSERT_EQ(KEY_MESSAGE, result) << "Failed for " << content_id(); + const std::string url = MakeUrl(config_.renewal_server(), policy_id); + renewal_in_flight_.reset(new UrlRequest(url)); + ASSERT_TRUE(renewal_in_flight_->is_connected()) + << "Failed for " << content_id(); + renewal_in_flight_->PostRequest(request.message); +} +void LicenseHolder::FetchRenewal() { + ASSERT_NE(renewal_in_flight_, nullptr) << "Failed for " << content_id(); + ASSERT_TRUE(renewal_in_flight_->GetResponse(&renewal_response_)) + << "Failed for " << content_id(); + int status_code = renewal_in_flight_->GetStatusCode(renewal_response_); + ASSERT_EQ(kHttpOk, status_code) << "Failed for " << content_id(); +} + +void LicenseHolder::LoadRenewal() { + LicenseRequest license_request; + license_request.GetDrmMessage(renewal_response_, renewal_message_); + EXPECT_EQ(KEY_ADDED, cdm_engine_->RenewKey(session_id_, renewal_message_)) + << "Failed for " << content_id(); +} + +void LicenseHolder::RemoveLicense() { + EXPECT_EQ(NO_ERROR, cdm_engine_->RemoveLicense(session_id_)) + << "Failed for " << content_id(); +} + +CdmResponseType LicenseHolder::Decrypt(const std::string& key_id) { + constexpr size_t buffer_size = 500; + const std::vector input(buffer_size, 0); + std::vector output(buffer_size, 0); + const std::vector iv(KEY_IV_SIZE, 0); + CdmDecryptionParametersV16 params(key_id); + params.is_secure = false; + CdmDecryptionSample sample(input.data(), output.data(), 0, input.size(), iv); + CdmDecryptionSubsample subsample(0, input.size()); + sample.subsamples.push_back(subsample); + params.samples.push_back(sample); + return cdm_engine_->DecryptV16(session_id_, params); +} + +void LicenseHolder::DecryptSecure(const KeyId& key_id) { + ASSERT_TRUE(wvoec::global_features.test_secure_buffers); + constexpr size_t buffer_size = 500; + const std::vector input(buffer_size, 0); + const std::vector iv(KEY_IV_SIZE, 0); + + // To create a secure buffer, we need to know the OEMCrypto session id. + CdmQueryMap query_map; + cdm_engine_->QueryOemCryptoSessionId(session_id_, &query_map); + const std::string oec_session_id_string = + query_map[QUERY_KEY_OEMCRYPTO_SESSION_ID]; + uint32_t oec_session_id = std::stoi(oec_session_id_string); + + int secure_buffer_fid; + OEMCrypto_DestBufferDesc output_descriptor; + output_descriptor.type = OEMCrypto_BufferType_Secure; + output_descriptor.buffer.secure.secure_buffer_length = buffer_size; + ASSERT_EQ( + OEMCrypto_AllocateSecureBuffer(oec_session_id, buffer_size, + &output_descriptor, &secure_buffer_fid), + OEMCrypto_SUCCESS); + + ASSERT_NE(output_descriptor.buffer.secure.secure_buffer, nullptr); + // It is OK if OEMCrypto changes the maximum size, but there must + // still be enough room for our data. + ASSERT_GE(output_descriptor.buffer.secure.secure_buffer_length, buffer_size); + output_descriptor.buffer.secure.offset = 0; + + // Now create a sample array for the CDM layer. + CdmDecryptionParametersV16 params(key_id); + params.is_secure = true; + CdmDecryptionSample sample(input.data(), + output_descriptor.buffer.secure.secure_buffer, 0, + input.size(), iv); + CdmDecryptionSubsample subsample(0, input.size()); + sample.subsamples.push_back(subsample); + params.samples.push_back(sample); + CdmResponseType status = cdm_engine_->DecryptV16(session_id_, params); + + // Free the secure buffer before we check the return status. + EXPECT_EQ(OEMCrypto_FreeSecureBuffer(oec_session_id, &output_descriptor, + secure_buffer_fid), + OEMCrypto_SUCCESS); + + ASSERT_EQ(status, NO_ERROR); +} + +void LicenseHolder::FailDecrypt(const KeyId& key_id, + CdmResponseType expected_status) { + CdmResponseType status = Decrypt(key_id); + // If the server knows we cannot handle the key, it would not have given us + // the key. In that case, the status should indicate no key. + if (status != NEED_KEY) { + // Otherwise, we should have gotten the expected error. + ASSERT_EQ(expected_status, status); + } +} + +void LicenseHolder::GenerateKeyRequest(const InitializationData& init_data, + CdmKeyRequest* key_request) { + CdmAppParameterMap empty_app_parameters; + CdmKeySetId empty_key_set_id; + CdmLicenseType license_type = + can_persist_ ? kLicenseTypeOffline : kLicenseTypeStreaming; + CdmResponseType result = cdm_engine_->GenerateKeyRequest( + session_id_, empty_key_set_id, init_data, license_type, + empty_app_parameters, key_request); + ASSERT_EQ(KEY_MESSAGE, result); + ASSERT_EQ(kKeyRequestTypeInitial, key_request->type); +} + +std::string LicenseHolder::MakeUrl(const std::string& server_url, + const std::string& policy_id) { + // For tests, we want to specify the policy, but the UAT server only allows us + // to set the content id as the video_id. So each policy is matched to a + // single license with the same name. The local license server, on the other + // hand, wants to see the policy id in the url. So we have to guess which + // format to use based on the name of the server. + const std::string path = server_url + config_.client_auth(); + std::string video_query; + if (!policy_id.empty()) { + if (path.find("proxy.uat") != std::string::npos) { + // This is uat or uat-nightly. Set the video_id. + video_query = "video_id=" + policy_id; + } else { + // This is probably a local license server. Set the policy. + video_query = "policy=" + policy_id; + } + // If there is already a parameter, then we don't need to add another + // question mark. + return path + ((path.find("?") == std::string::npos) ? "?" : "&") + + video_query; + } else { + return path; + } +} + +void LicenseHolder::GetKeyResponse(const CdmKeyRequest& key_request) { + // The content id matches the policy id used on UAT. + const std::string url = MakeUrl(config_.license_server(), ""); + UrlRequest url_request(url); + ASSERT_TRUE(url_request.is_connected()); + + std::string http_response; + url_request.PostRequest(key_request.message); + ASSERT_TRUE(url_request.GetResponse(&http_response)); + int status_code = url_request.GetStatusCode(http_response); + ASSERT_EQ(kHttpOk, status_code) << "Error with url = " << url << "\n" + << "content_id = " << content_id() << "\n" + << "response = " << http_response; + + LicenseRequest license_request; + license_request.GetDrmMessage(http_response, key_response_); +} + +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/license_holder.h b/libwvdrmengine/cdm/core/test/license_holder.h new file mode 100644 index 00000000..eb1e5e37 --- /dev/null +++ b/libwvdrmengine/cdm/core/test/license_holder.h @@ -0,0 +1,133 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#ifndef WVCDM_CORE_TEST_LICENSE_HOLDER_H_ +#define WVCDM_CORE_TEST_LICENSE_HOLDER_H_ + +#include +#include + +#include + +#include "cdm_engine.h" +#include "config_test_env.h" +#include "url_request.h" +#include "wv_attributes.h" +#include "wv_cdm_event_listener.h" + +#include "log.h" + +namespace wvcdm { + +// An event listener to tell when a renewal is needed. +class SimpleEventListener : public wvcdm::WvCdmEventListener { + public: + SimpleEventListener() { renewal_needed_ = false; } + // We will want to know when a renewal is needed. + void OnSessionRenewalNeeded(const CdmSessionId& /*session*/) override { + renewal_needed_ = true; + } + void OnSessionKeysChange(const CdmSessionId&, const CdmKeyStatusMap&, + bool) override {} + void OnExpirationUpdate(const CdmSessionId&, + int64_t expiry_time UNUSED) override {} + bool renewal_needed() { return renewal_needed_; } + void set_renewal_needed(bool renewal_needed) { + renewal_needed_ = renewal_needed; + } + + private: + bool renewal_needed_ = false; +}; + +class LicenseHolder { + public: + // Set up a new license holder with the specified content id. + LicenseHolder(const std::string& content_id, CdmEngine* cdm_engine, + const ConfigTestEnv& config) + : content_id_(content_id), cdm_engine_(cdm_engine), config_(config) {} + ~LicenseHolder(); + // Open a session for the license. Must be called before FetchLicense or + // ReloadLicense. + void OpenSession(); + // Close the session and release resources. + void CloseSession(); + // Generate a license request, send it to the license server, and wait for the + // response. The response is *not* loaded into the cdm engine. + void FetchLicense(); + // Load the license response into the CDM engine. A call to FetchLicense() + // must be made first. + void LoadLicense(); + // Reload the license. Call OpenSession() before calling + // ReloadLicense(). Also, the key_set_id must have been set previously. The + // key_set_id is set by calling LoadLicense(), or by calling set_key_set_id(). + void ReloadLicense(); + // Generate the renewal request, and send it to the server. + void GenerateAndPostRenewalRequest(const std::string& policy_id); + // Fetch the renewal response. This can add a few seconds of latency. + void FetchRenewal(); + // Load the renewal response that was fetched in FetchRenewal(). + void LoadRenewal(); + // Releases the license and frees up entry in usage table. + void RemoveLicense(); + + // Try to decrypt some random data. This does not verify that the data is + // decrypted correctly. Returns the result of the decrypt operation. + CdmResponseType Decrypt(const std::string& key_id); + // Try to decrypt some random data to a secure buffer. If the test harness + // does not allow creating a secure buffer, then this function fails + // immediately. Otherwise, a secure buffer is created and used for a + // decryption operation. + void DecryptSecure(const KeyId& key_id); + // Try to decrypt some random data, but expect failure. The failure may + // be either the expected_status, or NEED_KEY. We allow NEED_KEY in case + // the server recognized that we cannot support the given key. + void FailDecrypt(const KeyId& key_id, CdmResponseType expected_status); + + const std::string& content_id() const { return content_id_; } + void set_content_id(const std::string& content_id) { + content_id_ = content_id; + } + // The session id. This is only valid after a call to OpenSession. + const std::string& session_id() { return session_id_; } + // Returns true if the license is offline. + bool can_persist() const { return can_persist_; } + // Sets whether the license is offline or not. + void set_can_persist(bool can_persist) { can_persist_ = can_persist; } + uint64_t start_of_rental_clock() const { return start_of_rental_clock_; } + const std::string& key_set_id() const { return key_set_id_; } + void set_key_set_id(const std::string& key_set_id) { + key_set_id_ = key_set_id; + } + SimpleEventListener& event_listener() { return event_listener_; } + + private: + std::string content_id_; + CdmSessionId session_id_; + CdmKeySetId key_set_id_; + std::string key_response_; + bool can_persist_ = false; + uint64_t start_of_rental_clock_ = 0u; + CdmEngine* cdm_engine_ = nullptr; + const ConfigTestEnv& config_; + SimpleEventListener event_listener_; + std::unique_ptr renewal_in_flight_; + std::string renewal_message_; + std::string renewal_response_; + + // Generate the license request. + void GenerateKeyRequest(const InitializationData& init_data, + CdmKeyRequest* key_request); + + // Generate a URL for the specified policy. The license request should be sent + // to this url. + std::string MakeUrl(const std::string& server_url, + const std::string& policy_id); + // Fetch the key response from the server. + void GetKeyResponse(const CdmKeyRequest& key_request); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_TEST_LICENSE_HOLDER_H_ diff --git a/libwvdrmengine/cdm/test/integration-test.mk b/libwvdrmengine/cdm/test/integration-test.mk index a5075993..03fdc98c 100644 --- a/libwvdrmengine/cdm/test/integration-test.mk +++ b/libwvdrmengine/cdm/test/integration-test.mk @@ -17,6 +17,7 @@ LOCAL_SRC_FILES := \ ../core/test/config_test_env.cpp \ ../core/test/fake_provisioning_server.cpp \ ../core/test/http_socket.cpp \ + ../core/test/license_holder.cpp \ ../core/test/license_request.cpp \ ../core/test/test_base.cpp \ ../core/test/test_printers.cpp \