diff --git a/libwvdrmengine/build_and_run_all_unit_tests.sh b/libwvdrmengine/build_and_run_all_unit_tests.sh index eaf8ff1c..ecd75ce9 100755 --- a/libwvdrmengine/build_and_run_all_unit_tests.sh +++ b/libwvdrmengine/build_and_run_all_unit_tests.sh @@ -40,6 +40,7 @@ WV_TEST_TARGETS="base64_test \ crypto_session_unittest \ device_files_unittest \ distribution_unittest \ + duration_use_case_test \ event_metric_unittest \ file_store_unittest \ file_utils_unittest \ @@ -60,6 +61,7 @@ WV_TEST_TARGETS="base64_test \ odk_test \ policy_engine_constraints_unittest \ policy_engine_unittest \ + policy_integration_test \ request_license_test \ rw_lock_test \ service_certificate_unittest \ diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index 6f9334a2..d94cccee 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -347,11 +347,6 @@ class CdmEngine { protected: friend class CdmEngineFactory; - friend class ParallelCdmTest; - friend class WvCdmEnginePreProvTest; - friend class WvCdmTestBase; - friend class WvGenericCryptoTest; - friend class TestLicenseHolder; CdmEngine(FileSystem* file_system, std::shared_ptr metrics); diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp index 52f068ce..115d760c 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp @@ -48,12 +48,9 @@ const std::string kFakeSessionId = "TotallyARealSession123456789"; } // namespace -class WvCdmEnginePreProvTest : public WvCdmTestBase { +class WvCdmEnginePreProvTest : public WvCdmTestBaseWithEngine { public: - WvCdmEnginePreProvTest() - : dummy_engine_metrics_(new EngineMetrics), - cdm_engine_(&file_system_, dummy_engine_metrics_), - session_opened_(false) {} + WvCdmEnginePreProvTest() : session_opened_(false) {} ~WvCdmEnginePreProvTest() override {} @@ -116,9 +113,6 @@ class WvCdmEnginePreProvTest : public WvCdmTestBase { return true; } - FileSystem file_system_; - shared_ptr dummy_engine_metrics_; - CdmEngine cdm_engine_; bool session_opened_; std::string key_msg_; std::string session_id_; diff --git a/libwvdrmengine/cdm/core/test/duration_use_case_test.cpp b/libwvdrmengine/cdm/core/test/duration_use_case_test.cpp new file mode 100644 index 00000000..53d57956 --- /dev/null +++ b/libwvdrmengine/cdm/core/test/duration_use_case_test.cpp @@ -0,0 +1,1632 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +// These tests perform various end-to-end actions similar to what an application +// would, but in parallel, attempting to create as many collisions in the CDM +// code as possible. + +#include +#include +#include + +#include +#include + +#include "cdm_engine.h" +#include "clock.h" +#include "config_test_env.h" +#include "initialization_data.h" +#include "license_request.h" +#include "log.h" +#include "metrics_collections.h" +#include "odk_structs.h" +#include "oec_device_features.h" +#include "test_base.h" +#include "test_printers.h" +#include "test_sleep.h" +#include "url_request.h" +#include "wv_cdm_constants.h" +#include "wv_cdm_event_listener.h" +#include "wv_cdm_types.h" + +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 +// to doing time consuming work at those events. We fudge the limits so that the +// test is lenient by a little bit of time. For example, when we verify +// that playback is terminated at time STOP, we wait until STOP+kFudge and then +// check. Similarly, when we verify that playback may continue until STOP, we +// wait until STOP-kFudge and then check. License sign and load times are not +// fudged because neither direction is more lenient than the other direction. +constexpr uint64_t kFudge = 1; +// How long we allow for a full license round trip -- including time for +// generating the request, sending the request to the server, processing the +// request at the server, sending a response back. Since this is constant, we +// can decide in advance when the rental window and playback windows will +// expire. This must be smaller than the renewal_recovery_duration for all of +// our test cases below. +constexpr uint64_t kRoundTripTime = 10; + +// A renewal policy, whose values should match the policy on UAT and in +// policies.dat with the same name. +struct RenewalPolicy { + // The policy and content id as seen in the UAT database. + std::string policy_id; + // How long to wait before we request the next renewal. + uint64_t renewal_delay; + // How long we allow for a full renewal round trip -- including time for + // generating the request, sending the request to the server, processing the + // request at the server, sending a response back. Since this is constant, we + // can decide in advance when the rental window and playback windows will + // expire. This should match the renewal recovery duration in the license + // policy. + uint64_t renewal_recovery_duration; +}; + +const RenewalPolicy kShortRenewal = {"CDM_LicenseWithRenewal_renewal", 25, 15}; +const RenewalPolicy kLongRenewal = {"CDM_LicenseWithRenewal_long_renewal", 40, + 15}; +const RenewalPolicy kLDLRenewal = {"CDM_LimitedDurationLicense_renewal", 0, 0}; + +// 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) 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. +class CdmDurationTest : public WvCdmTestBaseWithEngine, + public ::testing::WithParamInterface { + public: + CdmDurationTest(const std::string& content_id) + : content_id_(content_id), + first_load_occurred_(false), + allow_lenience_(false) { + // These are reasonable initial values for most tests. This is an unlimited + // license. Most of the tests below will add some restrictions by changing + // these values, and then verify that the system behaves correctly. + // Each test below will modify these values to match those in license + // policy. + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.soft_enforce_playback_duration = true; + timer_limits_.earliest_playback_start_seconds = 0u; + // A duration of 0 means infinite. + timer_limits_.rental_duration_seconds = 0u; + timer_limits_.total_playback_duration_seconds = 0u; + timer_limits_.initial_renewal_duration_seconds = 0u; + + // This is when we will attempt the first valid playback. + start_of_playback_ = + timer_limits_.earliest_playback_start_seconds + kRoundTripTime; + } + + protected: + // In the setup, we open a session and request a license. These tests are + // parameterized by |can_persist_|. If |can_persist_| is true, then we request + // an offline license, also called a persistent license. Otherwise, we request + // a streaming license. When testing playback, we will perform the same tests + // on both types of license. However, for the persistent license we will load + // the license at the start of each interval and then unload the license at + // the end of each interval. For the non-persistent license, we will only load + // the license at the start of the first interval, and only unload when the + // test is finished. These tests rely on the server having pairs of content: + // one with the given content id, and one whose content id has "_can_persist" + // appended to it. + void SetUp() override { + WvCdmTestBase::SetUp(); + EnsureProvisioned(); + ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id_)); + can_persist_ = GetParam(); + if (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; + } + // All times in the license are relative to the rental clock. + start_of_rental_clock_ = wvcdm::Clock().GetCurrentTime(); + FetchLicense(); + } + + void TearDown() override { cdm_engine_.CloseSession(session_id_); } + + // The end of the playback window. (using system clock) + // This is not useful if the playback duration is 0, which means infinite. In + // that case, this just returns a large number. + uint64_t EndOfPlaybackWindow() { + if (timer_limits_.total_playback_duration_seconds > 0) { + return start_of_playback_ + timer_limits_.total_playback_duration_seconds; + } else { + LOGW("Test is asking for EndOfPlaybackWindow for infinte duration."); + // This should not be used, but if it is, all of our tests should be + // finished in less than a half hour. + constexpr uint64_t kHalfHour = 30 * 60; + return start_of_playback_ + kHalfHour; + } + } + + // The end of the rental window. (using system clock) + // This is not useful if the rental duration is 0. + uint64_t EndOfRentalWindow() { + return timer_limits_.earliest_playback_start_seconds + + timer_limits_.rental_duration_seconds; + } + + // Sleep until the specified time on the rental clock. + void SleepUntil(uint64_t desired_rental_time) { + const uint64_t rental_time = CurrentRentalTime(); + if (desired_rental_time >= rental_time) { + TestSleep::Sleep(desired_rental_time - rental_time); + } else { + LOGW("Test Clock skew sleeping from rental clock time %ld to %ld", + rental_time, desired_rental_time); + } + cdm_engine_.OnTimerEvent(); + } + + uint64_t CurrentRentalTime() { + const uint64_t now = wvcdm::Clock().GetCurrentTime(); + 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& 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 = config_.license_server() + 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(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); + 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); + } + } + + void UnloadLicense() { + if (can_persist_) { + CloseSession(session_id_); + } + } + + // Simulate loading or reloading a license, then verify that we are allowed + // playback from |start| to |stop|. If |cutoff| is not 0, then expect the + // timer to expire at that time. If |cutoff| is 0, expect the timer is not + // set. If you refer to the diagrams in "License Duration and Renewal", this + // tests a cyan bar with a green check mark. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void LoadAndAllowPlayback(uint64_t start, uint64_t stop, uint64_t cutoff) { + SleepUntil(start); + LoadLicense(); + AllowPlayback(start, stop, cutoff); + } + + // Verify that we are allowed playback from |start| to |stop|. If |cutoff| is + // not 0, then expect the timer to expire at that time. If |cutoff| is 0, + // expect the timer is not set. If you refer to the diagrams in "License + // Duration and Renewal", this tests a cyan bar with a green check mark. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void AllowPlayback(uint64_t start, uint64_t stop, uint64_t cutoff) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + SleepUntil(start + kFudge); + Decrypt(); + const uint64_t mid = (start + stop) / 2; + SleepUntil(mid); + Decrypt(); + SleepUntil(stop - kFudge); + Decrypt(); + // TODO: Is there a way to verify that playback will be terminated at + // cutoff? + } + + // Simulate loading or reloading a license, then attempt to play from |start| + // to |cutoff|. Verify that we are allowed playback from |start| to |cutoff|, + // but playback is not allowed after |cutoff|. If you refer to the diagrams in + // "License Duration and Renewal", this tests a cyan bar with a black X. When + // nonzero |start|, and |cutoff| are all system times. + void LoadAndTerminatePlayback(uint64_t start, uint64_t cutoff) { + SleepUntil(start); + LoadLicense(); + TerminatePlayback(start, cutoff, cutoff + kFudge); + } + + // Attempt to play from |start| to |stop|. Verify that we are allowed playback + // from |start| to |cutoff|, but playback not allowed after |cutoff|. If you + // refer to the diagrams in "License Duration and Renewal", this tests a cyan + // bar with a black X. This assumes that |cutoff| is before |stop|. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void TerminatePlayback(uint64_t start, uint64_t cutoff, uint64_t stop) { + ASSERT_LT(start, cutoff); + ASSERT_LT(cutoff, stop); + AllowPlayback(start, cutoff, cutoff); + SleepUntil(stop + kFudge); + FailDecrypt(); + } + + // Verify that we are not allowed playback at the |start| time. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan line + // followed by a black X. The parameter |start| is system time. + void ForbidPlayback(uint64_t start) { + SleepUntil(start + kFudge); + LoadLicense(); + FailDecrypt(); + } + + 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); + Decrypt(session_id_, kKeyId, input, iv, &output, NO_ERROR); + } + + void FailDecrypt() { + CdmResponseType expected_status = NEED_KEY; + // On low end devices, for some tests, we are lenient on playback + // termination. This means that low end devices are not required to fail + // playback. Tests that allow lenience are enumerated in the + // documentation. See the section in "License Duration and Renewal" on + // lenient tests. + 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_) { + expected_status = NO_ERROR; + 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); + Decrypt(session_id_, kKeyId, input, iv, &output, expected_status); + } + + void Decrypt(const CdmSessionId& session_id, const KeyId& key_id, + const std::vector& input, + const std::vector& iv, std::vector* output, + CdmResponseType expected_status) { + 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); + + CdmResponseType status = cdm_engine_.DecryptV16(session_id, params); + ASSERT_EQ(expected_status, status); + } + + CdmSessionId session_id_; + std::string content_id_; + CdmLicenseType license_type_; + CdmKeySetId key_set_id_; + std::string key_response_; + // 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 + // the test. (using system clock) + uint64_t start_of_playback_; + // If this is true, then we the licenses have already been loaded once. We do + // 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_; +}; + +/*****************************************************************************/ +// Note on Use Case tests. The test classes below correspond to the use cases +// in the doucment "License Duration and Renewal.". Each diagram in that +// document has a test class below to verify the use case is supported. +// +// In the document, we use realistic rental times in hours or days. In these +// tests, we will use round numbers so that it is easier to read. For example, +// instead of a seven day rental duration, we will use a 200 rental duration. +/*****************************************************************************/ + +/*****************************************************************************/ +// Streaming is the simplest use case. The user has three hours to watch the +// movie from the time of the rental. (See above for note on Use Case tests) +class CdmUseCase_Streaming : public CdmDurationTest { + public: + CdmUseCase_Streaming() : CdmDurationTest("CDM_Streaming") { + // Rental duration = 3 hours hard. (use 300 for readability) + // Playback duration = 0 (unlimited) + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 35; + timer_limits_.total_playback_duration_seconds = 0; + } +}; + +// Playback within rental duration. +TEST_P(CdmUseCase_Streaming, Case1) { + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow(), + EndOfRentalWindow()); +} + +// Playback exceeds rental duration. +TEST_P(CdmUseCase_Streaming, Case2) { + // Allow playback within the rental window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within rental duration, last one exceeds rental +// duration. +TEST_P(CdmUseCase_Streaming, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 5, + EndOfRentalWindow()); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 10, start_of_playback_ + 15, + EndOfRentalWindow()); + UnloadLicense(); + LoadAndTerminatePlayback(start_of_playback_ + 20, EndOfRentalWindow()); +} + +// Playback within rental duration, restart exceeds rental duration. +TEST_P(CdmUseCase_Streaming, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 5, + EndOfRentalWindow()); + UnloadLicense(); + ForbidPlayback(EndOfRentalWindow() + 5); +} + +// Initial playback exceeds rental duration. +TEST_P(CdmUseCase_Streaming, Case5) { ForbidPlayback(EndOfRentalWindow() + 5); } + +/*****************************************************************************/ +// Streaming Quick Start. The user must start watching within 30 seconds, and +// then has three hours to finish. (See above for note on Use Case tests) +class CdmUseCase_StreamingQuickStart : public CdmDurationTest { + public: + CdmUseCase_StreamingQuickStart() + : CdmDurationTest("CDM_StreamingQuickStart") { + // Rental duration = 30 seconds, soft -- use 20 to make the test faster. + // Playback duration = 3 hours -- use 40 to make the test faster. + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 20; + timer_limits_.total_playback_duration_seconds = 40; + timer_limits_.soft_enforce_playback_duration = false; + + // A valid start of playback time. + start_of_playback_ = timer_limits_.rental_duration_seconds - 10; + } +}; + +// Playback starts within rental duration, continues within playback duration. +TEST_P(CdmUseCase_StreamingQuickStart, Case1) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_LE(EndOfRentalWindow(), EndOfPlaybackWindow()); + // Allow playback within the playback window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 20, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_P(CdmUseCase_StreamingQuickStart, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_P(CdmUseCase_StreamingQuickStart, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, + EndOfPlaybackWindow()); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 15, start_of_playback_ + 20, + EndOfPlaybackWindow()); + UnloadLicense(); + LoadAndTerminatePlayback(start_of_playback_ + 25, EndOfPlaybackWindow()); +} + +// Initial playback exceeds rental duration. +TEST_P(CdmUseCase_StreamingQuickStart, Case4) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// smaller of the two time limits. The first four cases start on day +// three. (See above for note on Use Case tests) +class CdmUseCase_SevenHardTwoHard_Start3 : public CdmDurationTest { + public: + CdmUseCase_SevenHardTwoHard_Start3() + : CdmDurationTest("CDM_SevenHardTwoHard") { + // Rental duration = 200, hard + // Playback duration = 100, hard + timer_limits_.rental_duration_seconds = 200; // 7 days. + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 100; // 2 days. + timer_limits_.soft_enforce_playback_duration = false; + + start_of_playback_ = 50; // Start is less than rental - playback. + } +}; + +// Playback within playback and rental duration. +TEST_P(CdmUseCase_SevenHardTwoHard_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_P(CdmUseCase_SevenHardTwoHard_Start3, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_P(CdmUseCase_SevenHardTwoHard_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, + EndOfPlaybackWindow()); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, + EndOfPlaybackWindow()); + UnloadLicense(); + AllowLenience(); + LoadAndTerminatePlayback(start_of_playback_ + 50, EndOfPlaybackWindow()); +} + +// Restart exceeds playback duration. +TEST_P(CdmUseCase_SevenHardTwoHard_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + UnloadLicense(); + AllowLenience(); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class CdmUseCase_SevenHardTwoHard_Start6 + : public CdmUseCase_SevenHardTwoHard_Start3 { + public: + CdmUseCase_SevenHardTwoHard_Start6() { + // Start is more than rental - playback = 200 - 100 + start_of_playback_ = 150; + } +}; + +// Playback exceeds rental duration. +TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case5) { + // As seen in the drawing, the playback window is exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one is terminated +// at the end of the rental duration. +TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, + EndOfRentalWindow()); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, + EndOfRentalWindow()); + UnloadLicense(); + // Allow playback that starts within rental window, but terminate at end of + // rental window. + LoadAndTerminatePlayback(start_of_playback_ + 40, EndOfRentalWindow()); +} + +// Playback within playback duration, restart exceeds rental duration. +TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + UnloadLicense(); + // Restart does not work after end of playback window. + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Playback exceeds rental and playback duration. +TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// First playback cannot exceed rental duration. +TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// rental duration time limits. The first four cases start on day three. (See +// above for note on Use Case tests) +class CdmUseCase_SevenHardTwoSoft_Start3 : public CdmDurationTest { + public: + CdmUseCase_SevenHardTwoSoft_Start3() + : CdmDurationTest("CDM_SevenHardTwoSoft") { + // Rental duration = 200, hard + // Playback duration = 100, hard + timer_limits_.rental_duration_seconds = 200; + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 100; + timer_limits_.soft_enforce_playback_duration = true; + + start_of_playback_ = 50; // Start is less than rental - playback. + } +}; + +// Playback within playback and rental duration. +TEST_P(CdmUseCase_SevenHardTwoSoft_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, + EndOfRentalWindow()); +} + +// Playback exceeds playback duration. +TEST_P(CdmUseCase_SevenHardTwoSoft_Start3, Case2) { + // Allow playback within the playback window, and a little after. + // Timer expires at end of rental window. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 15, + EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_P(CdmUseCase_SevenHardTwoSoft_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, + EndOfRentalWindow()); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, + EndOfRentalWindow()); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 50, EndOfPlaybackWindow() + 15, + EndOfRentalWindow()); +} + +// Restart exceeds playback duration. +TEST_P(CdmUseCase_SevenHardTwoSoft_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + UnloadLicense(); + if (!can_persist_) return; // streaming license cannot restart. + AllowLenience(); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class CdmUseCase_SevenHardTwoSoft_Start6 + : public CdmUseCase_SevenHardTwoSoft_Start3 { + public: + CdmUseCase_SevenHardTwoSoft_Start6() { + // Start is more than rental - playback = 200 - 100 + start_of_playback_ = 150; + } +}; + +// Playback exceeds rental duration. +TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case5) { + // As seen in the drawing, the playback window is exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental duration. +TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, + EndOfRentalWindow()); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, + EndOfRentalWindow()); + UnloadLicense(); + LoadAndTerminatePlayback(start_of_playback_ + 40, EndOfRentalWindow()); +} + +// Playback within playback duration, restart exceeds rental duration. +TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + UnloadLicense(); + // Restart does not work after end of playback window. + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Playback exceeds rental and playback duration. +TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// First playback cannot exceed rental duration. +TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// playback duration. The first four cases start on day three. (See above for +// note on Use Case tests) +class CdmUseCase_SevenSoftTwoHard_Start3 : public CdmDurationTest { + public: + CdmUseCase_SevenSoftTwoHard_Start3() + : CdmDurationTest("CDM_SevenSoftTwoHard") { + // Rental duration = 200, hard + // Playback duration = 100, hard + timer_limits_.rental_duration_seconds = 200; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.total_playback_duration_seconds = 100; + timer_limits_.soft_enforce_playback_duration = false; + + start_of_playback_ = 50; // Start is less than rental - playback. + } +}; + +// Playback within playback and rental duration. +TEST_P(CdmUseCase_SevenSoftTwoHard_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_P(CdmUseCase_SevenSoftTwoHard_Start3, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_P(CdmUseCase_SevenSoftTwoHard_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, + EndOfPlaybackWindow()); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, + EndOfPlaybackWindow()); + UnloadLicense(); + AllowLenience(); + LoadAndTerminatePlayback(start_of_playback_ + 50, EndOfPlaybackWindow()); +} + +// Restart exceeds playback duration. +TEST_P(CdmUseCase_SevenSoftTwoHard_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + UnloadLicense(); + AllowLenience(); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class CdmUseCase_SevenSoftTwoHard_Start6 + : public CdmUseCase_SevenSoftTwoHard_Start3 { + public: + CdmUseCase_SevenSoftTwoHard_Start6() { + // Start is more than rental - playback = 200 - 100 + start_of_playback_ = 150; + } +}; + +// Playback exceeds rental duration. +TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case5) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback to continue beyond the rental window, but not beyond the + // playback window. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental duration. +TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, + EndOfPlaybackWindow()); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, + EndOfPlaybackWindow()); + UnloadLicense(); + AllowLenience(); + LoadAndTerminatePlayback(start_of_playback_ + 40, EndOfPlaybackWindow()); +} + +// Restart exceeds rental duration, playback exceeds playback duration. +TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfPlaybackWindow()); + UnloadLicense(); + AllowLenience(); + LoadAndTerminatePlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow()); +} + +// Playback exceeds rental and playback duration. +TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// First playback cannot exceed rental duration. +TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is not cutoff, +// but restarts are not allowed after playback duration. The first four cases +// start on day three. (See above for note on Use Case tests) +class CdmUseCase_SevenSoftTwoSoft_Start3 : public CdmDurationTest { + public: + CdmUseCase_SevenSoftTwoSoft_Start3() + : CdmDurationTest("CDM_SevenSoftTwoSoft") { + // Rental duration = 200, hard + // Playback duration = 100, hard + timer_limits_.rental_duration_seconds = 200; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.total_playback_duration_seconds = 100; + timer_limits_.soft_enforce_playback_duration = true; + + start_of_playback_ = 50; // Start is less than rental - playback. + } +}; + +// Playback within playback and rental duration. +TEST_P(CdmUseCase_SevenSoftTwoSoft_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, 0); +} + +// Playback exceeds playback duration. +TEST_P(CdmUseCase_SevenSoftTwoSoft_Start3, Case2) { + // Allow playback within the playback window, and beyond. No timer limit. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 10, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_P(CdmUseCase_SevenSoftTwoSoft_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, 0); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, 0); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 50, EndOfPlaybackWindow() + 15, 0); +} + +// Restart exceeds playback duration. +TEST_P(CdmUseCase_SevenSoftTwoSoft_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); + UnloadLicense(); + if (!can_persist_) return; // streaming license cannot restart. + AllowLenience(); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class CdmUseCase_SevenSoftTwoSoft_Start6 + : public CdmUseCase_SevenSoftTwoSoft_Start3 { + public: + CdmUseCase_SevenSoftTwoSoft_Start6() { + // Start is more than rental - playback = 200 - 100 + start_of_playback_ = 150; + } +}; + +// Playback exceeds rental duration. +TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case5) { + // As seen in the drawing, the playback window exceeds the rental window. + // We should be able to play a little bit after the rental window. + // We'll use 10 as "a little bit". + const uint64_t end_play = EndOfRentalWindow() + 10; + // For this case, we are playing after the end of the playback window, too. + EXPECT_GT(EndOfPlaybackWindow(), end_play); + // Allow playback past the rental window, but within the playback window. + LoadAndAllowPlayback(start_of_playback_, end_play, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental and playback duration. +TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, 0); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, 0); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 40, EndOfPlaybackWindow() + 10, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); + UnloadLicense(); + // Allow playback to start after end of rental window, and continue after + // playback window. + LoadAndAllowPlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow() + 10, 0); + UnloadLicense(); + // But forbid restart after playback window. + if (!can_persist_) return; // streaming license cannot restart. + ForbidPlayback(EndOfPlaybackWindow() + 20); +} + +// Playback exceeds rental and playback duration. +TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case8) { + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 20, 0); +} + +// First playback cannot exceed rental duration. +TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_Streaming, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_StreamingQuickStart, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoHard_Start3, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoHard_Start6, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoSoft_Start3, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoSoft_Start6, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoHard_Start3, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoHard_Start6, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoSoft_Start3, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoSoft_Start6, + ::testing::Values(false, true)); + +class RenewalTest : public CdmDurationTest { + protected: + RenewalTest(std::string content_id) : CdmDurationTest(content_id) { + current_cutoff_ = 0; + } + + // 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()) { + uint64_t now = CurrentRentalTime(); + ASSERT_LT(now, cutoff); + SleepUntil(now + 1); + } + } + // Sleep until a renewal request event is generated. If one does not occur + // in a reasonable amount of time, an error happens. + void SleepUntilRenewalNeeded() { + constexpr uint64_t reasonable_time = 5; + SleepUntilRenewalNeeded(CurrentRentalTime() + reasonable_time); + } + + 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(renewal_policy.policy_id); + renewal_in_flight_.reset(new UrlRequest(url)); + ASSERT_TRUE(renewal_in_flight_->is_connected()); + renewal_in_flight_->PostRequest(request.message); + } + + void LoadRenewal(uint64_t time_of_load, const RenewalPolicy& renewal_policy) { + ASSERT_NE(renewal_in_flight_, nullptr); + std::string http_response; + // TODO(fredgc): Tune this. 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); + + 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_)); + ComputeCutoff(time_of_load, renewal_policy); + } + + // Compute the cutoff value for the renewal timer. + void ComputeCutoff(uint64_t time_of_load, + const RenewalPolicy& renewal_policy) { + const uint64_t renewal_duration = + renewal_policy.renewal_delay + renewal_policy.renewal_recovery_duration; + if (renewal_duration > 0) { + current_cutoff_ = time_of_load + renewal_duration; + } else { + current_cutoff_ = 0; + } + } + + // Verify that a renewal can be processed and playback may continue from + // |start| to |stop|, with a renewal loaded at |load_time|. If you refer to + // the diagrams in "License Duration and Renewal", this tests a cyan bar with + // a green check mark. + void RenewAndContinue(uint64_t start, uint64_t load_time, uint64_t stop, + const RenewalPolicy& renewal_policy) { + EXPECT_LE(start, load_time); + EXPECT_LT(load_time, stop); + if (start < load_time) AllowPlayback(start, load_time, current_cutoff_); + LoadRenewal(load_time, renewal_policy); + AllowPlayback(load_time, stop, current_cutoff_); + } + + // Verify that a renewal can be processed and attempt to play from |start| to + // after |cutoff|, with a renewal loaded at |load_time|. Verify that we are + // allowed playback from |start| to |cutoff|, but playback not allowed after + // |cutoff|. If you refer to the diagrams in "License Duration and Renewal", + // this tests a cyan bar with a black X. + void RenewAndTerminate(uint64_t start, uint64_t load_time, uint64_t cutoff, + const RenewalPolicy& renewal_policy) { + RenewAndTerminate(start, load_time, cutoff, + cutoff + renewal_policy.renewal_recovery_duration, + renewal_policy); + } + + // Verify that a renewal can be processed and attempt to play from |start| to + // |stop|, with a renewal loaded at |load_time|. Verify that we are allowed + // playback from |start| to |cutoff|, but playback not allowed after + // |cutoff|. If you refer to the diagrams in "License Duration and Renewal", + // this tests a cyan bar with a black X. This assumes that |cutoff| is between + // |start| and |stop|. + void RenewAndTerminate(uint64_t start, uint64_t load_time, uint64_t cutoff, + uint64_t stop, const RenewalPolicy& renewal_policy) { + ASSERT_LT(start, load_time); + ASSERT_LT(load_time, cutoff); + ASSERT_LE(cutoff, stop); + RenewAndContinue(start, load_time, cutoff, renewal_policy); + // Renewals need some extra fudge because there are extra messages sent and + // received. + SleepUntil(stop + 3 * kFudge); + FailDecrypt(); + } + + // Verify that a license and renewal can be reloaded at time |start|, and then + // playback may continue until |stop|.If you refer to the diagrams in "License + // Duration and Renewal", this tests a cyan bar with a green check mark. This + // is different from the previous functions, because the renewal is loaded + // before the first playback. + void ReloadAndAllowPlayback(uint64_t start, uint64_t stop, + const RenewalPolicy& renewal_policy) { + ASSERT_LT(start, stop); + SleepUntil(start); + // The CDM Core code will automatically load the last renewal when a + // license is reloaded. + LoadLicense(); + ComputeCutoff(start, renewal_policy); + AllowPlayback(start, stop, current_cutoff_); + } + + std::unique_ptr renewal_in_flight_; + std::string renewal_message_; + // When we expect the current renewal timer to expire. + uint64_t current_cutoff_; +}; + +// License with Renewal, limited by rental duration. (See above for note on Use +// Case tests). These tests are parameterized by can_persist = true or false. +class CdmUseCase_LicenseWithRenewal : public RenewalTest { + public: + CdmUseCase_LicenseWithRenewal() : RenewalTest("CDM_LicenseWithRenewal") { + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 360u; + initial_policy_.renewal_delay = 25u; + initial_policy_.renewal_recovery_duration = 15u; + timer_limits_.initial_renewal_duration_seconds = + initial_policy_.renewal_delay + + initial_policy_.renewal_recovery_duration; + } + + void SetUp() override { + RenewalTest::SetUp(); + const uint64_t next_renewal = + start_of_playback_ + initial_policy_.renewal_delay; + // Allow playback within the initial renewal window. + SleepUntil(start_of_playback_); + LoadLicense(); + // Play until just before we expect a renewal to be generated. + current_cutoff_ = next_renewal + initial_policy_.renewal_recovery_duration; + AllowPlayback(start_of_playback_, next_renewal, current_cutoff_); + } + + protected: + RenewalPolicy initial_policy_; +}; + +// Playback within rental duration and renewal duration. +TEST_P(CdmUseCase_LicenseWithRenewal, Case1) { + for (int i = 0; i < 4; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } +} + +// Playback within rental duration, last playback attempt exceeds renewal +// duration. +TEST_P(CdmUseCase_LicenseWithRenewal, Case2) { + for (int i = 0; i < 4; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + // We attempt to continue playing beyond the renewal cutoff. + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t cutoff = load_time + kShortRenewal.renewal_delay + + kShortRenewal.renewal_recovery_duration; + RenewAndTerminate(start, load_time, cutoff, kShortRenewal); +} + +// Playback interrupted by late renewal. +TEST_P(CdmUseCase_LicenseWithRenewal, Case3) { + for (int i = 0; i < 2; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + // We attempt to continue playing beyond the renewal cutoff. + // Playback should fail at the renewal cutoff. + { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t cutoff = load_time + kShortRenewal.renewal_delay + + kShortRenewal.renewal_recovery_duration; + RenewAndTerminate(start, load_time, cutoff, kShortRenewal); + } + // We are late requesting the renewal. + TestSleep::Sleep(kRoundTripTime); + // But after we request the renewal, we should be able to start playing again. + { + RequestRenewal(kShortRenewal); + const uint64_t load_time = CurrentRentalTime() + kRoundTripTime; + // We cannot start playback again until the renewal has loaded. + const uint64_t start = load_time; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } +} + +// Playback & restart within playback duration and renewal duration. Note: this +// simulates reloading a persistent license, and then reloading the last +// renewal. +TEST_P(CdmUseCase_LicenseWithRenewal, Case4) { + for (int i = 0; i < 2; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + UnloadLicense(); + if (can_persist_) { + // Reload license after timer expired. + const uint64_t reload_time = current_cutoff_ + 20; + ReloadAndAllowPlayback( + reload_time, reload_time + kShortRenewal.renewal_delay, kShortRenewal); + // Now we should be able to continue. + for (int i = 0; i < 2; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + } +} + +// Playback within renewal duration, but exceeds playback duration. +TEST_P(CdmUseCase_LicenseWithRenewal, Case5) { + uint64_t stop = 0; + const uint64_t cutoff = EndOfRentalWindow(); + while (stop <= cutoff) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + stop = load_time + kShortRenewal.renewal_delay; + if (stop >= cutoff) { + RenewAndTerminate(start, load_time, cutoff, kShortRenewal); + } else { + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + } +} + +// Renewal duration may be different for different renewals. +// Cutoff at end of rental window. +TEST_P(CdmUseCase_LicenseWithRenewal, Case6) { + for (int i = 0; i < 2; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + // Change renewal duration, and playing until the end of the rental window. + uint64_t stop = 0; + const uint64_t cutoff = EndOfRentalWindow(); + while (stop <= cutoff) { + SleepUntilRenewalNeeded(); + RequestRenewal(kLongRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + stop = load_time + kLongRenewal.renewal_delay; + if (stop >= cutoff) { + RenewAndTerminate(start, load_time, cutoff, kLongRenewal); + } else { + RenewAndContinue(start, load_time, stop, kLongRenewal); + } + } +} + +// Renewal duration may be different for different renewals. +// Cutoff when renewals stop coming. This is a mix of Case2 and Case6. +TEST_P(CdmUseCase_LicenseWithRenewal, Case7) { + for (int i = 0; i < 2; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + // Change renewal duration, and playing until the end of the rental window. + for (int i = 0; i < 2; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kLongRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kLongRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kLongRenewal); + } + // We attempt to continue playing beyond the renewal cutoff. + SleepUntilRenewalNeeded(); + RequestRenewal(kLongRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t cutoff = load_time + kLongRenewal.renewal_delay + + kLongRenewal.renewal_recovery_duration; + RenewAndTerminate(start, load_time, cutoff, kLongRenewal); +} + +// License with Renewal, limited by playback duration. (See above for note on +// Use Case tests). These are similar to the tests above, except the playback +// duration is limited instead of the rental duration. These tests are +// parameterized by can_persist = true or false. +class CdmUseCase_LicenseWithRenewalPlayback : public RenewalTest { + public: + CdmUseCase_LicenseWithRenewalPlayback() + : RenewalTest("CDM_LicenseWithRenewal_playback") { + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 360u; + initial_policy_.renewal_delay = 25u; + initial_policy_.renewal_recovery_duration = 15u; + timer_limits_.initial_renewal_duration_seconds = + initial_policy_.renewal_delay + + initial_policy_.renewal_recovery_duration; + } + + void SetUp() override { + RenewalTest::SetUp(); + uint64_t next_renewal = start_of_playback_ + initial_policy_.renewal_delay; + // Allow playback within the initial renewal window. + SleepUntil(start_of_playback_); + LoadLicense(); + // Play until just before we expect a renewal to be generated. + current_cutoff_ = next_renewal + initial_policy_.renewal_recovery_duration; + AllowPlayback(start_of_playback_, next_renewal, current_cutoff_); + } + + protected: + RenewalPolicy initial_policy_; +}; + +// Playback within rental duration and renewal duration. +TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case1) { + for (int i = 0; i < 4; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } +} + +// Playback within rental duration, last playback attempt exceeds renewal +// duration. +TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case2) { + for (int i = 0; i < 4; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + // We attempt to continue playing beyond the renewal cutoff. + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t cutoff = load_time + kShortRenewal.renewal_delay + + kShortRenewal.renewal_recovery_duration; + RenewAndTerminate(start, load_time, cutoff, kShortRenewal); +} + +// Playback interrupted by late renewal. +TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case3) { + for (int i = 0; i < 2; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + // We attempt to continue playing beyond the renewal cutoff. + // Playback should fail at the renewal cutoff. + { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t cutoff = load_time + kShortRenewal.renewal_delay + + kShortRenewal.renewal_recovery_duration; + RenewAndTerminate(start, load_time, cutoff, kShortRenewal); + } + // We are late requesting the renewal. + TestSleep::Sleep(kRoundTripTime); + // But after we request the renewal, we should be able to start playing again. + { + RequestRenewal(kShortRenewal); + const uint64_t load_time = CurrentRentalTime() + kRoundTripTime; + // We cannot start playback again until the renewal has loaded. + const uint64_t start = load_time; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } +} + +// Playback & restart within playback duration and renewal duration. Note: this +// simulates reloading a persistent license, and then reloading the last +// renewal. +TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case4) { + for (int i = 0; i < 2; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + UnloadLicense(); + if (can_persist_) { + // Reload license after timer expired. + const uint64_t reload_time = current_cutoff_ + 20; + ReloadAndAllowPlayback( + reload_time, reload_time + kShortRenewal.renewal_delay, kShortRenewal); + // Now we should be able to continue. + for (int i = 0; i < 2; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + } +} + +// Playback within renewal duration, but exceeds playback duration. +TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case5) { + uint64_t stop = 0; + const uint64_t cutoff = EndOfPlaybackWindow(); + while (stop <= cutoff) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + stop = load_time + kShortRenewal.renewal_delay; + if (stop >= cutoff) { + RenewAndTerminate(start, load_time, cutoff, kShortRenewal); + } else { + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + } +} + +// Renewal duration may be different for different renewals. +// Cutoff at end of rental window. +TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case6) { + for (int i = 0; i < 2; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + // Change renewal duration, and playing until the end of the rental window. + uint64_t stop = 0; + const uint64_t cutoff = EndOfPlaybackWindow(); + while (stop <= cutoff) { + SleepUntilRenewalNeeded(); + RequestRenewal(kLongRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + stop = load_time + kLongRenewal.renewal_delay; + if (stop >= cutoff) { + RenewAndTerminate(start, load_time, cutoff, kLongRenewal); + } else { + RenewAndContinue(start, load_time, stop, kLongRenewal); + } + } +} + +// Renewal duration may be different for different renewals. +// Cutoff when renewals stop coming. This is a mix of Case2 and Case6. +TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case7) { + for (int i = 0; i < 2; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kShortRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kShortRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kShortRenewal); + } + // Change renewal duration, and playing until the end of the rental window. + for (int i = 0; i < 2; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kLongRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kLongRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kLongRenewal); + } + // We attempt to continue playing beyond the renewal cutoff. + SleepUntilRenewalNeeded(); + RequestRenewal(kLongRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t cutoff = load_time + kLongRenewal.renewal_delay + + kLongRenewal.renewal_recovery_duration; + RenewAndTerminate(start, load_time, cutoff, kLongRenewal); +} + +// Limited Duration License. (See above for notes on Use Case tests). The user +// has 15 minutes to begin watching the movie. If a renewal is not received, +// playback is terminated after 30 seconds. If a renewal is received, playback +// may continue for two hours from playback start. +class CdmUseCase_LimitedDurationLicense : public RenewalTest { + public: + CdmUseCase_LimitedDurationLicense() + : RenewalTest("CDM_LimitedDurationLicense") { + renewal_delay_ = 5u; + renewal_recovery_ = 55; + + // Pick a start of playback that is within the rental window, but so that + // the initial renewal window is after rental window. + start_of_playback_ = 100; + + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 120; // 15 minutes. + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 300; // --> 12 hours. + timer_limits_.initial_renewal_duration_seconds = + renewal_delay_ + renewal_recovery_; + + // Load the renewal just before the cutoff: + renewal_load_time_ = + start_of_playback_ + renewal_delay_ + renewal_recovery_ - 1; + } + uint64_t renewal_delay_; + uint64_t renewal_load_time_; + uint64_t renewal_recovery_; +}; + +// Playback started within rental window and continues. +TEST_P(CdmUseCase_LimitedDurationLicense, Case1) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime, + current_cutoff_); + SleepUntilRenewalNeeded(); + RequestRenewal(kLDLRenewal); + // Continue playback for one hour (use 100 to condense test). + const uint64_t end_of_hour = renewal_load_time_ + 100; + const uint64_t start = CurrentRentalTime(); + RenewAndContinue(start, renewal_load_time_, end_of_hour, kLDLRenewal); +} + +// Playback started after rental duration should fail. +TEST_P(CdmUseCase_LimitedDurationLicense, Case2) { + start_of_playback_ = EndOfRentalWindow() + 1; + ForbidPlayback(start_of_playback_); +} + +// Playback started within rental window but renewal not received. +TEST_P(CdmUseCase_LimitedDurationLicense, Case3) { + // Allow playback within the initial renewal window. + const uint64_t cutoff = + start_of_playback_ + renewal_delay_ + renewal_recovery_; + LoadAndTerminatePlayback(start_of_playback_, cutoff); +} + +// Playback started within rental window, renewal is received, and playback +// continues. +TEST_P(CdmUseCase_LimitedDurationLicense, Case4) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime, + current_cutoff_); + SleepUntilRenewalNeeded(); + RequestRenewal(kLDLRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t cutoff = EndOfPlaybackWindow(); + RenewAndTerminate(start, renewal_load_time_, cutoff, kLDLRenewal); +} + +// Playback may be restarted. +TEST_P(CdmUseCase_LimitedDurationLicense, Case5) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime, + current_cutoff_); + SleepUntilRenewalNeeded(); + RequestRenewal(kLDLRenewal); + // Continue playback for one hour (use 100 to condense test). + const uint64_t end_of_hour = renewal_load_time_ + 100; + const uint64_t start = CurrentRentalTime(); + RenewAndContinue(start, renewal_load_time_, end_of_hour, kLDLRenewal); + UnloadLicense(); + // Simulate reloading the license, and then reloading the renewal, and then + // restarting playback. That is allowed, and playback shall be terminated at + // the end of the original playback window. + const uint64_t reload_time = end_of_hour + 100; + ReloadAndAllowPlayback(reload_time, EndOfPlaybackWindow(), kLDLRenewal); + // But not beyond playback window. + SleepUntil(EndOfPlaybackWindow() + 3 * kFudge); + FailDecrypt(); +} + +// All duration tests are parameterized by can_persist = true or false. +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_LicenseWithRenewal, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_LicenseWithRenewalPlayback, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_LimitedDurationLicense, + ::testing::Values(false, true)); +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/generic_crypto_unittest.cpp b/libwvdrmengine/cdm/core/test/generic_crypto_unittest.cpp index 7e796b11..26b9f306 100644 --- a/libwvdrmengine/cdm/core/test/generic_crypto_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/generic_crypto_unittest.cpp @@ -29,12 +29,9 @@ namespace wvcdm { -class WvGenericCryptoTest : public WvCdmTestBase { +class WvGenericCryptoTest : public WvCdmTestBaseWithEngine { public: - WvGenericCryptoTest() - : dummy_engine_metrics_(new metrics::EngineMetrics), - cdm_engine_(&file_system_, dummy_engine_metrics_), - holder_(&cdm_engine_) {} + WvGenericCryptoTest() : holder_(&cdm_engine_) {} void SetUp() override { WvCdmTestBase::SetUp(); @@ -74,9 +71,6 @@ class WvGenericCryptoTest : public WvCdmTestBase { } protected: - FileSystem file_system_; - std::shared_ptr dummy_engine_metrics_; - CdmEngine cdm_engine_; TestLicenseHolder holder_; KeyId ency_id_; diff --git a/libwvdrmengine/cdm/core/test/policy_integration_test.cpp b/libwvdrmengine/cdm/core/test/policy_integration_test.cpp new file mode 100644 index 00000000..a198860a --- /dev/null +++ b/libwvdrmengine/cdm/core/test/policy_integration_test.cpp @@ -0,0 +1,195 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +// These tests perform various end-to-end actions similar to what an application +// would do. They verify that policies specified on UAT are honored on the +// device. + +#include +#include + +#include +#include + +#include "cdm_engine.h" +#include "clock.h" +#include "config_test_env.h" +#include "initialization_data.h" +#include "license_request.h" +#include "log.h" +#include "metrics_collections.h" +#include "test_base.h" +#include "test_printers.h" +#include "test_sleep.h" +#include "url_request.h" +#include "wv_cdm_constants.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +namespace { +constexpr int kHttpOk = 200; +const std::string kCencMimeType = "cenc"; +} // namespace + +// Core Policy Integration Test +class CorePIGTest : public WvCdmTestBaseWithEngine { + protected: + void SetUp() override { + WvCdmTestBase::SetUp(); + EnsureProvisioned(); + } + + void OpenSession(CdmSessionId* session_id) { + CdmResponseType status = cdm_engine_.OpenSession( + config_.key_system(), nullptr, nullptr, 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)); + } + + // Create a license request for the given content_id and requesting the + // specified license_type. + void GenerateKeyRequest(const CdmSessionId& session_id, + const std::string& content_id, + CdmKeyRequest* key_request, + CdmLicenseType license_type) { + 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(); + 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); + } + + // Send the request to the server and get the response. + void GetKeyResponse(const CdmKeyRequest& key_request, + std::string* key_response) { + const std::string url = config_.license_server() + config_.client_auth(); + 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); + + LicenseRequest license_request; + license_request.GetDrmMessage(http_response, *key_response); + } + + // Load the license response into the specified session. Verify it has the + // correct license type (either streaming or offline). + void AddKey(const CdmSessionId& session_id, const std::string& key_response, + CdmLicenseType expected_license_type, CdmKeySetId* key_set_id) { + CdmLicenseType license_type; + CdmResponseType status = + cdm_engine_.AddKey(session_id, key_response, &license_type, key_set_id); + ASSERT_EQ(KEY_ADDED, status); + ASSERT_EQ(expected_license_type, license_type); + } + + // Reload the license response into the specified session. + void RestoreKey(const CdmSessionId& session_id, + const CdmKeySetId& key_set_id) { + CdmResponseType status = cdm_engine_.RestoreKey(session_id, key_set_id); + ASSERT_EQ(KEY_ADDED, status); + } + + // Use the key to decrypt. + void Decrypt(const CdmSessionId& session_id, const KeyId& 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); + Decrypt(session_id, key_id, input, iv, &output, NO_ERROR); + } + + // Try to use the key to decrypt, but expect the key has expired. + void FailDecrypt(const CdmSessionId& session_id, const KeyId& 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); + Decrypt(session_id, key_id, input, iv, &output, NEED_KEY); + } + + void Decrypt(const CdmSessionId& session_id, const KeyId& key_id, + const std::vector& input, + const std::vector& iv, std::vector* output, + CdmResponseType expected_status) { + 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); + + CdmResponseType status = cdm_engine_.DecryptV16(session_id, params); + ASSERT_EQ(expected_status, status); + } +}; + +// An offline license with nonce not required. +TEST_F(CorePIGTest, OfflineNoNonce) { + const std::string content_id = "2015_tears"; + const KeyId key_id = "0000000000000000"; + + const CdmLicenseType license_type = kLicenseTypeOffline; + CdmSessionId session_id; + ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); + CdmKeyRequest key_request; + ASSERT_NO_FATAL_FAILURE( + GenerateKeyRequest(session_id, content_id, &key_request, license_type)); + std::string key_response; + ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request, &key_response)); + CdmKeySetId key_set_id; + ASSERT_NO_FATAL_FAILURE( + AddKey(session_id, key_response, license_type, &key_set_id)); + ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id)); + ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); + ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); + ASSERT_NO_FATAL_FAILURE(RestoreKey(session_id, key_set_id)); + ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id)); + ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); +} + +// An offline license with nonce and provider session token. +TEST_F(CorePIGTest, OfflineWithPST) { + const std::string content_id = "offline_clip2"; + const KeyId key_id = + "\x32\x60\xF3\x9E\x12\xCC\xF6\x53\x52\x99\x90\x16\x8A\x35\x83\xFF"; + const CdmLicenseType license_type = kLicenseTypeOffline; + CdmSessionId session_id; + ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); + CdmKeyRequest key_request; + ASSERT_NO_FATAL_FAILURE( + GenerateKeyRequest(session_id, content_id, &key_request, license_type)); + std::string key_response; + ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request, &key_response)); + CdmKeySetId key_set_id; + ASSERT_NO_FATAL_FAILURE( + AddKey(session_id, key_response, license_type, &key_set_id)); + ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id)); + ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); + ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); + ASSERT_NO_FATAL_FAILURE(RestoreKey(session_id, key_set_id)); + ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id)); + ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); +} + +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/test_base.cpp b/libwvdrmengine/cdm/core/test/test_base.cpp index f225e24e..36957ebc 100644 --- a/libwvdrmengine/cdm/core/test/test_base.cpp +++ b/libwvdrmengine/cdm/core/test/test_base.cpp @@ -290,8 +290,8 @@ void WvCdmTestBase::Provision() { if (config_.provisioning_server() == "fake") { LOGD("Using fake provisioning server."); - CdmEngine cdm_engine(&file_system, - std::shared_ptr(new EngineMetrics)); + TestCdmEngine cdm_engine(&file_system, + std::shared_ptr(new EngineMetrics)); FakeProvisioningServer server; CdmResponseType result = cdm_engine.GetProvisioningRequest( cert_type, cert_authority, server.service_certificate(), kLevelDefault, @@ -309,8 +309,8 @@ void WvCdmTestBase::Provision() { EXPECT_EQ(NO_ERROR, result); } else { // TODO(fredgc): provision for different SPOIDs. - CdmEngine cdm_engine(&file_system, - std::shared_ptr(new EngineMetrics)); + TestCdmEngine cdm_engine(&file_system, + std::shared_ptr(new EngineMetrics)); CdmResponseType result = cdm_engine.GetProvisioningRequest( cert_type, cert_authority, config_.provisioning_service_certificate(), @@ -387,8 +387,8 @@ void WvCdmTestBase::Provision() { void WvCdmTestBase::EnsureProvisioned() { CdmSessionId session_id; FileSystem file_system; - CdmEngine cdm_engine(&file_system, - std::shared_ptr(new EngineMetrics)); + TestCdmEngine cdm_engine(&file_system, + std::shared_ptr(new EngineMetrics)); CdmResponseType status = cdm_engine.OpenSession(config_.key_system(), nullptr, nullptr, &session_id); if (status == NEED_PROVISIONING) { diff --git a/libwvdrmengine/cdm/core/test/test_base.h b/libwvdrmengine/cdm/core/test/test_base.h index e1553ee5..4293bd97 100644 --- a/libwvdrmengine/cdm/core/test/test_base.h +++ b/libwvdrmengine/cdm/core/test/test_base.h @@ -76,6 +76,27 @@ class WvCdmTestBase : public ::testing::Test { bool binary_provisioning_; }; +// This just makes the constructor public so that we can create one with dummy +// metrics and file system. +class TestCdmEngine : public CdmEngine { + public: + TestCdmEngine(FileSystem* file_system, + std::shared_ptr metrics) + : CdmEngine(file_system, metrics) {} +}; + +class WvCdmTestBaseWithEngine : public WvCdmTestBase { + public: + WvCdmTestBaseWithEngine() + : dummy_engine_metrics_(new metrics::EngineMetrics()), + cdm_engine_(&file_system_, dummy_engine_metrics_) {} + + protected: + FileSystem file_system_; + shared_ptr dummy_engine_metrics_; + TestCdmEngine cdm_engine_; +}; + class TestCryptoSession : public CryptoSession { public: explicit TestCryptoSession(metrics::CryptoMetrics* crypto_metrics); diff --git a/libwvdrmengine/cdm/test/Android.mk b/libwvdrmengine/cdm/test/Android.mk index f66b16bc..734e1dd8 100644 --- a/libwvdrmengine/cdm/test/Android.mk +++ b/libwvdrmengine/cdm/test/Android.mk @@ -67,6 +67,11 @@ test_name := distribution_unittest test_src_dir := ../metrics/test include $(LOCAL_PATH)/unit-test.mk +test_name := duration_use_case_test +test_src_dir := ../core/test +test_main := ../core/test/test_main.cpp +include $(LOCAL_PATH)/integration-test.mk + test_name := event_metric_unittest test_src_dir := ../metrics/test include $(LOCAL_PATH)/unit-test.mk @@ -116,6 +121,11 @@ test_src_dir := ../core/test test_main := ../core/test/test_main.cpp include $(LOCAL_PATH)/integration-test.mk +test_name := policy_integration_test +test_src_dir := ../core/test +test_main := ../core/test/test_main.cpp +include $(LOCAL_PATH)/integration-test.mk + test_name := request_license_test test_src_dir := . test_main := ../core/test/test_main.cpp diff --git a/libwvdrmengine/run_all_unit_tests.sh b/libwvdrmengine/run_all_unit_tests.sh index 82e68e8d..263937d2 100755 --- a/libwvdrmengine/run_all_unit_tests.sh +++ b/libwvdrmengine/run_all_unit_tests.sh @@ -97,6 +97,9 @@ adb_shell_run request_license_test $PROVISIONING_ARG # cdm_extended_duration_test takes >30 minutes to run. # adb_shell_run cdm_extended_duration_test +# duration_use_case_test takes a very long time to run. +# adb_shell_run duration_use_case_test + # cdm_feature_test to be run with modified/mock oemcrypto # adb_shell_run cdm_feature_test @@ -127,6 +130,7 @@ adb_shell_run license_unittest adb_shell_run odk_test adb_shell_run policy_engine_constraints_unittest adb_shell_run policy_engine_unittest +adb_shell_run policy_integration_test adb_shell_run rw_lock_test adb_shell_run service_certificate_unittest adb_shell_run timer_unittest