From b1f54bbba8173762cb6bf9765d021e45edfe5346 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Sun, 1 Nov 2020 12:28:45 -0800 Subject: [PATCH] Add Duration Use Case integration tests Cherry pick from http://go/wvgerrit/102986, rvc-dev branch of http://go/wvgerrit/105825, rvc-widevine-release of http://go/ag/12561661 This CL adds several integration tests that match the duration use cases. The test classes are designed for the core cdm, but the test cases match those found in oemcrypto/odk/test/odk_timer_test.cpp. See this document for a list of use cases: libwvdrmengine/docs/License_Duration_and_Renewal.pdf Test: Ran the tests against v16 OEMCrypto. Some fail against v15. Bug: 161463952 Change-Id: I7cd424ae241d3897fbd06956e87dd9da0752cb6d --- .../build_and_run_all_unit_tests.sh | 1 + .../cdm/core/test/duration_use_case_test.cpp | 1632 +++++++++++++++++ libwvdrmengine/cdm/test/Android.mk | 5 + libwvdrmengine/run_all_unit_tests.sh | 4 + 4 files changed, 1642 insertions(+) create mode 100644 libwvdrmengine/cdm/core/test/duration_use_case_test.cpp diff --git a/libwvdrmengine/build_and_run_all_unit_tests.sh b/libwvdrmengine/build_and_run_all_unit_tests.sh index c860072a..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 \ 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/test/Android.mk b/libwvdrmengine/cdm/test/Android.mk index 7fac72a9..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 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