/* Copyright 2019 Google LLC. This file and proprietary * source code may only be used and distributed under the Widevine * License Agreement. */ #include "OEMCryptoCENCCommon.h" #include "gtest/gtest.h" #include "odk.h" #include "odk_structs.h" #include "odk_structs_priv.h" #include "odk_test_helper.h" namespace { // The rental clock starts when the request is signed. If any test fails // because a time is off by exactly 1000, that is a strong indication that we // confused rental and system clocks. The system clock should only be used // internally on the device, because it might not be synchronized from one // device to another. Rental clock is used in the license message from the // server. constexpr uint64_t kRentalClockStart = 1000u; // The renewal grace period is used by the server and the CDM layer to allow // time between when the renewal is requested and when playback is cutoff if the // renewal is not loaded. constexpr uint64_t kGracePeriod = 5u; constexpr uint32_t kExtraPayloadSize = 128u; constexpr uint32_t kSystemTime = 20u; namespace wvodk_test { TEST(OdkTimerBasicTest, ParseLicenseTimerSet) { // playback timer is successfully started ::wvodk_test::ODK_LicenseResponseParams params; ODK_SetDefaultLicenseResponseParams(¶ms, ODK_MAJOR_VERSION); params.parsed_license.renewal_delay_base = OEMCrypto_License_Load; params.parsed_license.timer_limits.soft_enforce_rental_duration = false; params.parsed_license.timer_limits.soft_enforce_playback_duration = false; params.parsed_license.timer_limits.earliest_playback_start_seconds = 10; params.parsed_license.timer_limits.total_playback_duration_seconds = 0; params.parsed_license.timer_limits.rental_duration_seconds = 10; params.parsed_license.timer_limits.initial_renewal_duration_seconds = 0; OEMCryptoResult result = ODK_InitializeClockValues(¶ms.clock_values, kSystemTime); EXPECT_EQ(OEMCrypto_SUCCESS, result); params.clock_values.time_of_license_request_signed = 5; params.clock_values.status = kActive; uint8_t* buf = nullptr; uint32_t buf_size = 0; ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, &buf_size); result = ODK_ParseLicense( buf, buf_size + kExtraPayloadSize, buf_size, params.initial_license_load, params.usage_entry_present, kSystemTime, &(params.timer_limits), &(params.clock_values), &(params.core_message.nonce_values), &(params.parsed_license), nullptr); EXPECT_EQ(ODK_SET_TIMER, result); delete[] buf; } TEST(OdkTimerBasicTest, ParseLicenseTimerDisabled) { // playback timer is successfully started ::wvodk_test::ODK_LicenseResponseParams params; ODK_SetDefaultLicenseResponseParams(¶ms, ODK_MAJOR_VERSION); params.parsed_license.renewal_delay_base = OEMCrypto_License_Load; params.parsed_license.timer_limits.soft_enforce_rental_duration = true; params.parsed_license.timer_limits.earliest_playback_start_seconds = 3; params.parsed_license.timer_limits.total_playback_duration_seconds = 0; params.parsed_license.timer_limits.initial_renewal_duration_seconds = 0; params.clock_values.time_of_first_decrypt = 10; params.clock_values.time_of_license_request_signed = 5; params.clock_values.status = kActive; uint8_t* buf = nullptr; uint32_t buf_size = 0; ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, &buf_size); OEMCryptoResult result = ODK_ParseLicense( buf, buf_size + kExtraPayloadSize, buf_size, params.initial_license_load, params.usage_entry_present, kSystemTime, &(params.timer_limits), &(params.clock_values), &(params.core_message.nonce_values), &(params.parsed_license), nullptr); EXPECT_EQ(ODK_DISABLE_TIMER, result); delete[] buf; } TEST(OdkTimerBasicTest, ParseRenewalTimerExpired) { // playback timer is successfully started ::wvodk_test::ODK_LicenseResponseParams params; ODK_SetDefaultLicenseResponseParams(¶ms, ODK_MAJOR_VERSION); params.parsed_license.renewal_delay_base = OEMCrypto_License_Load; params.parsed_license.timer_limits.rental_duration_seconds = 5; params.parsed_license.timer_limits.earliest_playback_start_seconds = 3; OEMCryptoResult result = ODK_InitializeClockValues(¶ms.clock_values, kSystemTime); EXPECT_EQ(OEMCrypto_SUCCESS, result); params.clock_values.time_of_license_request_signed = 5; uint8_t* buf = nullptr; uint32_t buf_size = 0; ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, &buf_size); result = ODK_ParseLicense( buf, buf_size + kExtraPayloadSize, buf_size, params.initial_license_load, params.usage_entry_present, kSystemTime, &(params.timer_limits), &(params.clock_values), &(params.core_message.nonce_values), &(params.parsed_license), nullptr); EXPECT_EQ(ODK_TIMER_EXPIRED, result); delete[] buf; } } // namespace wvodk_test TEST(OdkTimerBasicTest, NullTest) { // Assert that nullptr does not cause a core dump. ODK_InitializeClockValues(nullptr, 0u); ODK_ReloadClockValues(nullptr, 0u, 0u, 0u, kActive, 0u); ODK_AttemptFirstPlayback(0u, nullptr, nullptr, nullptr); ODK_UpdateLastPlaybackTime(0, nullptr, nullptr); ASSERT_TRUE(true); } TEST(OdkTimerBasicTest, Init) { // Verify that basic initialization sets all of the fields. ODK_ClockValues clock_values; memset(&clock_values, 0, sizeof(clock_values)); uint64_t time = 42; ODK_InitializeClockValues(&clock_values, time); EXPECT_EQ(clock_values.time_of_license_request_signed, time); EXPECT_EQ(clock_values.time_of_first_decrypt, 0u); EXPECT_EQ(clock_values.time_of_last_decrypt, 0u); EXPECT_EQ(clock_values.time_when_timer_expires, 0u); EXPECT_EQ(clock_values.timer_status, ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); EXPECT_EQ(clock_values.status, kUnused); } TEST(OdkTimerBasicTest, Reload) { // Verify that reloading clock values uses the same values // for fields that can be saved, and sets others to 0. ODK_ClockValues clock_values; memset(&clock_values, 0, sizeof(clock_values)); uint64_t time = 42u; uint64_t lic_signed = 1u; uint64_t first_decrypt = 2u; uint64_t last_decrypt = 3u; enum OEMCrypto_Usage_Entry_Status status = kInactiveUsed; ODK_ReloadClockValues(&clock_values, lic_signed, first_decrypt, last_decrypt, status, time); EXPECT_EQ(clock_values.time_of_license_request_signed, lic_signed); EXPECT_EQ(clock_values.time_of_first_decrypt, first_decrypt); EXPECT_EQ(clock_values.time_of_last_decrypt, last_decrypt); EXPECT_EQ(clock_values.time_when_timer_expires, 0u); EXPECT_EQ(clock_values.timer_status, ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); EXPECT_EQ(clock_values.status, status); } // All of the following test cases are derived from this base class. It // simulates loading a license, attempting playback, and reloading a license. class ODKTimerTest : public ::testing::Test { public: ODKTimerTest() { // 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 ODK library behaves correctly. 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_ = GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); } protected: void SetUp() override { ::testing::Test::SetUp(); // Start rental clock at kRentalClockStart. This happens when the license // request is signed. ODK_InitializeClockValues(&clock_values_, kRentalClockStart); EXPECT_EQ(clock_values_.time_of_license_request_signed, kRentalClockStart); } // Simulate loading or reloading a license in a new session. An offline // license should have several of the clock_value's fields saved into the // usage table. When it is reloaded those values should be reloaded. From // these fields, the ODK function can tell if this is the first playback for // the license, or just the first playback for this session. The key fields // that should be saved are the status, and the times for license signed, and // first and last playback times. void ReloadLicense(uint64_t system_time) { ODK_ClockValues old_clock_values = clock_values_; // First clear out the old clock values. memset(&clock_values_, 0, sizeof(clock_values_)); // When the session is opened, the clock values are initialized. ODK_InitializeClockValues(&clock_values_, 0); // When the usage entry is reloaded, the clock values are reloaded. ODK_ReloadClockValues(&clock_values_, old_clock_values.time_of_license_request_signed, old_clock_values.time_of_first_decrypt, old_clock_values.time_of_last_decrypt, old_clock_values.status, system_time); EXPECT_EQ(clock_values_.timer_status, ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); // These shall not change: EXPECT_EQ(clock_values_.time_of_license_request_signed, kRentalClockStart); EXPECT_EQ(clock_values_.time_of_first_decrypt, old_clock_values.time_of_first_decrypt); EXPECT_EQ(clock_values_.time_of_last_decrypt, old_clock_values.time_of_last_decrypt); // ODK_ParseLicense sets the new timer state. clock_values_.timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; } // 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) { ReloadLicense(start); 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); uint64_t timer_value; const OEMCryptoResult result = ODK_AttemptFirstPlayback( start, &timer_limits_, &clock_values_, &timer_value); // After first playback, the license is active. EXPECT_EQ(clock_values_.status, kActive); EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); if (cutoff > 0) { // If we expect the timer to be set. EXPECT_EQ(result, ODK_SET_TIMER); EXPECT_EQ(timer_value, cutoff - start); } else { EXPECT_EQ(result, ODK_DISABLE_TIMER); } CheckClockValues(start); const uint64_t mid = (start + stop) / 2; EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), OEMCrypto_SUCCESS); CheckClockValues(mid); EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), OEMCrypto_SUCCESS); CheckClockValues(stop); } // 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) { ReloadLicense(start); TerminatePlayback(start, cutoff, cutoff + 10); } // 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); EXPECT_EQ( ODK_UpdateLastPlaybackTime(cutoff + 1, &timer_limits_, &clock_values_), ODK_TIMER_EXPIRED); CheckClockValues(cutoff); const uint64_t mid = (cutoff + stop) / 2; EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), ODK_TIMER_EXPIRED); // times do not change if playback was not allowed. CheckClockValues(cutoff); EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), ODK_TIMER_EXPIRED); CheckClockValues(cutoff); } // 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) { ReloadLicense(start); ODK_ClockValues old_clock_values = clock_values_; uint64_t timer_value; EXPECT_EQ(ODK_AttemptFirstPlayback(start, &timer_limits_, &clock_values_, &timer_value), ODK_TIMER_EXPIRED); // These should not have changed. In particular, if the license was unused // before, it should reamin unused. EXPECT_EQ(clock_values_.time_of_license_request_signed, old_clock_values.time_of_license_request_signed); EXPECT_EQ(clock_values_.time_of_first_decrypt, old_clock_values.time_of_first_decrypt); EXPECT_EQ(clock_values_.time_of_last_decrypt, old_clock_values.time_of_last_decrypt); EXPECT_EQ(clock_values_.status, old_clock_values.status); } // Verify that the clock values are correct. void CheckClockValues(uint64_t time_of_last_decrypt) { EXPECT_EQ(clock_values_.time_of_license_request_signed, kRentalClockStart); EXPECT_EQ(clock_values_.time_of_first_decrypt, start_of_playback_); EXPECT_EQ(clock_values_.time_of_last_decrypt, time_of_last_decrypt); EXPECT_EQ(clock_values_.status, kActive); } // Convert from rental time to system time. By "system time", we mean // OEMCrypto's montonic clock. The spec does not specify a starting time for // this clock. uint64_t GetSystemTime(uint64_t rental_clock) { return kRentalClockStart + rental_clock; } // The end of the playback window. (using system clock) // This is not useful if the playback duration is 0. uint64_t EndOfPlaybackWindow() { return start_of_playback_ + timer_limits_.total_playback_duration_seconds; } // The end of the rental window. (using system clock) // This is not useful if the rental duration is 0. uint64_t EndOfRentalWindow() { return GetSystemTime(timer_limits_.earliest_playback_start_seconds) + timer_limits_.rental_duration_seconds; } ODK_TimerLimits timer_limits_; ODK_ClockValues clock_values_; // 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_; }; TEST_F(ODKTimerTest, SimplePlayback) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); } // This tests that we are not allowed to start playback before the rental window // opens. TEST_F(ODKTimerTest, EarlyTest) { timer_limits_.earliest_playback_start_seconds = 100u; // This is earlier than we are allowed. const uint64_t bad_start_time = GetSystemTime(timer_limits_.earliest_playback_start_seconds - 10); // An attempt to start playback before the timer starts is an error. // We use the TIMER_EXPIRED error to mean both early or late. ForbidPlayback(bad_start_time); // And times were not updated: EXPECT_EQ(clock_values_.status, kUnused); // This is when we will attempt the first valid playback. start_of_playback_ = GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); } // This runs the same test as above, but explicitly gives the ODK library a null // pointer for the timer value. A null pointer is allowed for OEMCrypto // implementations that do not manage their own timer. TEST_F(ODKTimerTest, NullTimer) { timer_limits_.earliest_playback_start_seconds = 100u; // This is the earlier, invalid start time. const uint64_t bad_start_time = GetSystemTime(timer_limits_.earliest_playback_start_seconds - 10); timer_limits_.rental_duration_seconds = 300; timer_limits_.soft_enforce_rental_duration = false; ReloadLicense(GetSystemTime(bad_start_time)); // If OEMCrypto passes in a null pointer, then the ODK should allow this. uint64_t* timer_value_pointer = nullptr; EXPECT_EQ(ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, &clock_values_, timer_value_pointer), ODK_TIMER_EXPIRED); start_of_playback_ = GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); EXPECT_EQ(ODK_AttemptFirstPlayback(start_of_playback_, &timer_limits_, &clock_values_, timer_value_pointer), ODK_SET_TIMER); CheckClockValues(start_of_playback_); } /*****************************************************************************/ // 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 700 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 ODKUseCase_Streaming : public ODKTimerTest { public: ODKUseCase_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 = 300; timer_limits_.total_playback_duration_seconds = 0; } }; // Playback within rental duration. TEST_F(ODKUseCase_Streaming, Case1) { // Allow playback within the rental window. LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow(), EndOfRentalWindow()); } // Playback exceeds rental duration. TEST_F(ODKUseCase_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_F(ODKUseCase_Streaming, Case3) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, EndOfRentalWindow()); LoadAndAllowPlayback(start_of_playback_ + 110, start_of_playback_ + 120, EndOfRentalWindow()); LoadAndTerminatePlayback(start_of_playback_ + 200, EndOfRentalWindow()); } // Playback within rental duration, restart exceeds rental duration. TEST_F(ODKUseCase_Streaming, Case4) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, EndOfRentalWindow()); ForbidPlayback(EndOfRentalWindow() + 10); } // Initial playback exceeds rental duration. TEST_F(ODKUseCase_Streaming, Case5) { ForbidPlayback(EndOfRentalWindow() + 10); } /*****************************************************************************/ // 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 ODKUseCase_StreamingQuickStart : public ODKTimerTest { public: ODKUseCase_StreamingQuickStart() { // Rental duration = 30 seconds, soft. // Playback duration = 3 hours (use 300 for readability) timer_limits_.soft_enforce_rental_duration = true; timer_limits_.rental_duration_seconds = 30; timer_limits_.total_playback_duration_seconds = 300; timer_limits_.soft_enforce_playback_duration = false; // A valid start of playback time. start_of_playback_ = GetSystemTime(timer_limits_.rental_duration_seconds - 10); } }; // Playback starts within rental duration, continues within playback duration. TEST_F(ODKUseCase_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_ + 100, EndOfPlaybackWindow()); } // Playback exceeds playback duration. TEST_F(ODKUseCase_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_F(ODKUseCase_StreamingQuickStart, Case3) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, EndOfPlaybackWindow()); LoadAndAllowPlayback(start_of_playback_ + 110, start_of_playback_ + 120, EndOfPlaybackWindow()); LoadAndTerminatePlayback(start_of_playback_ + 200, EndOfPlaybackWindow()); } // Initial playback exceeds rental duration. TEST_F(ODKUseCase_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 ODKUseCase_SevenHardTwoHard_Start3 : public ODKTimerTest { public: ODKUseCase_SevenHardTwoHard_Start3() { // Rental duration = 700, hard // Playback duration = 200, hard timer_limits_.rental_duration_seconds = 700; timer_limits_.soft_enforce_rental_duration = false; timer_limits_.total_playback_duration_seconds = 200; timer_limits_.soft_enforce_playback_duration = false; start_of_playback_ = GetSystemTime(300); } }; // Playback within playback and rental duration. TEST_F(ODKUseCase_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_ + 100, EndOfPlaybackWindow()); } // Playback exceeds playback duration. TEST_F(ODKUseCase_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_F(ODKUseCase_SevenHardTwoHard_Start3, Case3) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, EndOfPlaybackWindow()); LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, EndOfPlaybackWindow()); LoadAndTerminatePlayback(start_of_playback_ + 125, EndOfPlaybackWindow()); } // Restart exceeds playback duration. TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case4) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, EndOfPlaybackWindow()); ForbidPlayback(EndOfPlaybackWindow() + 10); } // The next four cases start on day six. class ODKUseCase_SevenHardTwoHard_Start6 : public ODKUseCase_SevenHardTwoHard_Start3 { public: ODKUseCase_SevenHardTwoHard_Start6() { start_of_playback_ = GetSystemTime(600); } }; // Playback exceeds rental duration. TEST_F(ODKUseCase_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_F(ODKUseCase_SevenHardTwoHard_Start6, Case6) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, EndOfRentalWindow()); LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, EndOfRentalWindow()); // Allow playback that starts within rental window, but terminate at end of // rental window. LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfRentalWindow()); } // Playback within playback duration, restart exceeds rental duration. TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case7) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, EndOfRentalWindow()); // Restart does not work after end of playback window. ForbidPlayback(EndOfRentalWindow() + 10); } // Playback exceeds rental and playback duration. TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case8) { LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); } // First playback cannot exceed rental duration. TEST_F(ODKUseCase_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 ODKUseCase_SevenHardTwoSoft_Start3 : public ODKTimerTest { public: ODKUseCase_SevenHardTwoSoft_Start3() { // Rental duration = 700, hard // Playback duration = 200, hard timer_limits_.rental_duration_seconds = 700; timer_limits_.soft_enforce_rental_duration = false; timer_limits_.total_playback_duration_seconds = 200; timer_limits_.soft_enforce_playback_duration = true; start_of_playback_ = GetSystemTime(300); } }; // Playback within playback and rental duration. TEST_F(ODKUseCase_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_ + 100, EndOfRentalWindow()); } // Playback exceeds playback duration. TEST_F(ODKUseCase_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() + 50, EndOfRentalWindow()); } // Playback with stops/restarts within playback duration, last one exceeds // playback duration. TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case3) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, EndOfRentalWindow()); LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, EndOfRentalWindow()); LoadAndAllowPlayback(start_of_playback_ + 125, EndOfPlaybackWindow() + 50, EndOfRentalWindow()); } // Restart exceeds playback duration. TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case4) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, EndOfRentalWindow()); ForbidPlayback(EndOfPlaybackWindow() + 10); } // The next four cases start on day six. class ODKUseCase_SevenHardTwoSoft_Start6 : public ODKUseCase_SevenHardTwoSoft_Start3 { public: ODKUseCase_SevenHardTwoSoft_Start6() { start_of_playback_ = GetSystemTime(600); } }; // Playback exceeds rental duration. TEST_F(ODKUseCase_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_F(ODKUseCase_SevenHardTwoSoft_Start6, Case6) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, EndOfRentalWindow()); LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, EndOfRentalWindow()); LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfRentalWindow()); } // Playback within playback duration, restart exceeds rental duration. TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case7) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, EndOfRentalWindow()); // Restart does not work after end of playback window. ForbidPlayback(EndOfRentalWindow() + 10); } // Playback exceeds rental and playback duration. TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case8) { LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); } // First playback cannot exceed rental duration. TEST_F(ODKUseCase_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 ODKUseCase_SevenSoftTwoHard_Start3 : public ODKTimerTest { public: ODKUseCase_SevenSoftTwoHard_Start3() { // Rental duration = 700, hard // Playback duration = 300, hard timer_limits_.rental_duration_seconds = 700; timer_limits_.soft_enforce_rental_duration = true; timer_limits_.total_playback_duration_seconds = 200; timer_limits_.soft_enforce_playback_duration = false; start_of_playback_ = GetSystemTime(300); } }; // Playback within playback and rental duration. TEST_F(ODKUseCase_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_ + 100, EndOfPlaybackWindow()); } // Playback exceeds playback duration. TEST_F(ODKUseCase_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_F(ODKUseCase_SevenSoftTwoHard_Start3, Case3) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, EndOfPlaybackWindow()); LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, EndOfPlaybackWindow()); LoadAndTerminatePlayback(start_of_playback_ + 125, EndOfPlaybackWindow()); } // Restart exceeds playback duration. TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case4) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, EndOfPlaybackWindow()); ForbidPlayback(EndOfPlaybackWindow() + 10); } // The next four cases start on day six. class ODKUseCase_SevenSoftTwoHard_Start6 : public ODKUseCase_SevenSoftTwoHard_Start3 { public: ODKUseCase_SevenSoftTwoHard_Start6() { start_of_playback_ = GetSystemTime(600); } }; // Playback exceeds rental duration. TEST_F(ODKUseCase_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_F(ODKUseCase_SevenSoftTwoHard_Start6, Case6) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, EndOfPlaybackWindow()); LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, EndOfPlaybackWindow()); LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfPlaybackWindow()); } // Restart exceeds rental duration, playback exceeds playback duration. TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case7) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, EndOfPlaybackWindow()); LoadAndTerminatePlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow()); } // Playback exceeds rental and playback duration. TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case8) { LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); } // First playback cannot exceed rental duration. TEST_F(ODKUseCase_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 ODKUseCase_SevenSoftTwoSoft_Start3 : public ODKTimerTest { public: ODKUseCase_SevenSoftTwoSoft_Start3() { // Rental duration = 700, hard // Playback duration = 200, hard timer_limits_.rental_duration_seconds = 700; timer_limits_.soft_enforce_rental_duration = true; timer_limits_.total_playback_duration_seconds = 200; timer_limits_.soft_enforce_playback_duration = true; start_of_playback_ = GetSystemTime(300); } }; // Playback within playback and rental duration. TEST_F(ODKUseCase_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_ + 100, 0); } // Playback exceeds playback duration. TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case2) { // Allow playback within the playback window, and beyond. No timer limit. LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 50, 0); } // Playback with stops/restarts within playback duration, last one exceeds // playback duration. TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case3) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, 0); LoadAndAllowPlayback(start_of_playback_ + 125, EndOfPlaybackWindow() + 50, 0); } // Restart exceeds playback duration. TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case4) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); ForbidPlayback(EndOfPlaybackWindow() + 10); } // The next four cases start on day six. class ODKUseCase_SevenSoftTwoSoft_Start6 : public ODKUseCase_SevenSoftTwoSoft_Start3 { public: ODKUseCase_SevenSoftTwoSoft_Start6() { start_of_playback_ = GetSystemTime(600); } }; // Playback exceeds rental duration. TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case5) { // As seen in the drawing, the playback window exceeds the rental window. EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow() + 25); // Allow playback past the rental window, but within the playback window. LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow() + 25, 0); } // Playback with stops/restarts within playback duration, last one exceeds // rental and playback duration. TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case6) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, 0); LoadAndAllowPlayback(start_of_playback_ + 100, EndOfPlaybackWindow() + 50, 0); } // Playback with stops/restarts within playback duration, last one exceeds // playback duration. TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case7) { LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); // Allow playback to start after end of rental window, and continue after // playback window. LoadAndAllowPlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow() + 10, 0); // But forbid restart after playback window. ForbidPlayback(EndOfPlaybackWindow() + 20); } // Playback exceeds rental and playback duration. TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case8) { LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 100, 0); } // First playback cannot exceed rental duration. TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case9) { ForbidPlayback(EndOfRentalWindow() + 10); } class RenewalTest : public ODKTimerTest { protected: // Verify that a renewal can be processed and playback may continue 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. void RenewAndContinue(uint64_t start, uint64_t stop, uint64_t cutoff, uint64_t renewal_duration_seconds) { uint64_t timer_value; RenewAndContinue(start, stop, cutoff, renewal_duration_seconds, &timer_value); } // Verify that a renewal can be processed and playback may continue 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. // This does the work of the function above, except it allows the caller to // explicitly set the timer_value_pointer. The timer_value_pointer can be a // nullptr. void RenewAndContinue(uint64_t start, uint64_t stop, uint64_t cutoff, uint64_t renewal_duration_seconds, uint64_t* timer_value_pointer) { ASSERT_LT(start, stop); if (cutoff > 0) ASSERT_LE(stop, cutoff); // We'll fake instantaneous renewal requests. Flight time not important. clock_values_.time_of_renewal_request = start; const OEMCryptoResult result = ODK_ComputeRenewalDuration( &timer_limits_, &clock_values_, start, renewal_duration_seconds, timer_value_pointer); // After first playback, the license is active. EXPECT_EQ(clock_values_.status, kActive); EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); if (cutoff > 0) { // If we expect the timer to be set. EXPECT_EQ(result, ODK_SET_TIMER); if (timer_value_pointer != nullptr) EXPECT_EQ(*timer_value_pointer, cutoff - start); } else { EXPECT_EQ(result, ODK_DISABLE_TIMER); } EXPECT_EQ(ODK_UpdateLastPlaybackTime(start, &timer_limits_, &clock_values_), OEMCrypto_SUCCESS); CheckClockValues(start); const uint64_t mid = (start + stop) / 2; EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), OEMCrypto_SUCCESS); CheckClockValues(mid); EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), OEMCrypto_SUCCESS); CheckClockValues(stop); } // Verify that a renewal can be processed and attempt to play from |start| to // after |cutoff|. 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 cutoff, uint64_t renewal_duration_seconds) { RenewAndTerminate(start, cutoff, cutoff + 100, renewal_duration_seconds); } // Verify that a renewal can be processed and 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 between |start| and |stop|. void RenewAndTerminate(uint64_t start, uint64_t cutoff, uint64_t stop, uint64_t renewal_duration_seconds) { ASSERT_LT(start, cutoff); ASSERT_LT(cutoff, stop); RenewAndContinue(start, cutoff, cutoff, renewal_duration_seconds); EXPECT_EQ( ODK_UpdateLastPlaybackTime(cutoff + 1, &timer_limits_, &clock_values_), ODK_TIMER_EXPIRED); CheckClockValues(cutoff); const uint64_t mid = (cutoff + stop) / 2; EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), ODK_TIMER_EXPIRED); CheckClockValues( cutoff); // times do not change if playback was not allowed. EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), ODK_TIMER_EXPIRED); CheckClockValues(cutoff); } // Verify that a renewal can be processed and playback may start 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. This is different from the previous functions, // because the renewal is loaded before the first playback. void RenewAndStart(uint64_t start, uint64_t stop, uint64_t cutoff, uint64_t renewal_duration_seconds) { ASSERT_LT(start, stop); if (cutoff > 0) ASSERT_LE(stop, cutoff); // We'll fake instantaneous renewal requests. Flight time not important. clock_values_.time_of_renewal_request = start; uint64_t timer_value; const OEMCryptoResult result = ODK_ComputeRenewalDuration(&timer_limits_, &clock_values_, start, renewal_duration_seconds, &timer_value); EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); if (cutoff > 0) { // If we expect the timer to be set. EXPECT_EQ(result, ODK_SET_TIMER); EXPECT_EQ(timer_value, cutoff - start); } else { EXPECT_EQ(result, ODK_DISABLE_TIMER); } AllowPlayback(start, stop, cutoff); } }; // License with Renewal, limited by playback duration. (See above for note on // Use Case tests) These tests are parameterized. If the parameter is 0, we // limit the playback duration. If the parameter is 1, we limit the rental // duration. The behavior is basically the same. class ODKUseCase_LicenseWithRenewal : public RenewalTest, public ::testing::WithParamInterface { public: ODKUseCase_LicenseWithRenewal() { // Either Playback or rental duration = 2 days hard if (GetParam() == 0) { timer_limits_.soft_enforce_rental_duration = false; timer_limits_.rental_duration_seconds = 2000; } else { timer_limits_.soft_enforce_playback_duration = false; timer_limits_.total_playback_duration_seconds = 2000; } timer_limits_.initial_renewal_duration_seconds = 50; } void SetUp() override { RenewalTest::SetUp(); renewal_interval_ = timer_limits_.initial_renewal_duration_seconds - kGracePeriod; uint64_t next_renewal = start_of_playback_ + renewal_interval_; // Allow playback within the initial renewal window. LoadAndAllowPlayback(start_of_playback_, next_renewal, next_renewal + kGracePeriod); } uint64_t playback_end_restriction() { if (GetParam() == 0) { return EndOfRentalWindow(); } else { return EndOfPlaybackWindow(); } } protected: // How long to wait before we load the next renewal. i.e. cutoff - // kGracePeriod. This is because the renewal_duration includes the grace // period. uint64_t renewal_interval_; }; // Playback within rental duration and renewal duration. TEST_P(ODKUseCase_LicenseWithRenewal, Case1) { uint64_t next_renewal = start_of_playback_ + renewal_interval_; for (int i = 0; i < 4; i++) { const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndContinue( current_renewal, // start: when renewal is loaded. next_renewal, // stop: expect play allowed. next_renewal + kGracePeriod, // cutoff: when timer expires. timer_limits_.initial_renewal_duration_seconds); } } // Playback within rental duration, last renewal_interval_ exceeds renewal // duration. TEST_P(ODKUseCase_LicenseWithRenewal, Case2) { uint64_t next_renewal = start_of_playback_ + renewal_interval_; for (int i = 0; i < 4; i++) { const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndContinue( current_renewal, // start: when renewal is loaded. next_renewal, // stop: expect play allowed. next_renewal + kGracePeriod, // cutoff: when timer expires. timer_limits_.initial_renewal_duration_seconds); } // We attempt to continue playing beyond the renewal renewal_interval_. const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndTerminate(current_renewal, // start: when renewal is loaded. next_renewal + kGracePeriod, // cutoff: when timer expires. timer_limits_.initial_renewal_duration_seconds); } // Playback interrupted by late renewal. TEST_P(ODKUseCase_LicenseWithRenewal, Case3) { uint64_t next_renewal = start_of_playback_ + renewal_interval_; for (int i = 0; i < 4; i++) { const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndContinue( current_renewal, // start: when renewal is loaded. next_renewal, // stop: expect play allowed. next_renewal + kGracePeriod, // cutoff: when timer expires. timer_limits_.initial_renewal_duration_seconds); } // We attempt to continue playing beyond the renewal renewal_interval_. const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndTerminate(current_renewal, // start: when renewal is loaded. next_renewal + kGracePeriod, // stop: when timer expires. timer_limits_.initial_renewal_duration_seconds); const uint64_t late_renewal = next_renewal + kGracePeriod + 10; next_renewal = late_renewal + renewal_interval_; RenewAndContinue(late_renewal, // start: when renewal is loaded. next_renewal, // stop: expect play allowed. next_renewal + kGracePeriod, // cutoff: when timer expires. timer_limits_.initial_renewal_duration_seconds); } // Playback & restart within playback duration and renewal duration. Note: this // simulates reloading a persistent license, and then reloading the last // renewal. TEST_P(ODKUseCase_LicenseWithRenewal, Case4) { uint64_t next_renewal = start_of_playback_ + renewal_interval_; for (int i = 0; i < 2; i++) { const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndContinue( current_renewal, // start: when renewal is loaded. next_renewal, // stop: expect play allowed. next_renewal + kGracePeriod, // cutoff: when timer expires. timer_limits_.initial_renewal_duration_seconds); } // Reload license after timer expired. uint64_t reload_time = next_renewal + kGracePeriod + 100; next_renewal = reload_time + renewal_interval_; ReloadLicense(reload_time); RenewAndStart(reload_time, next_renewal, next_renewal + kGracePeriod, timer_limits_.initial_renewal_duration_seconds); for (int i = 0; i < 2; i++) { const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndContinue( current_renewal, // start: when renewal is loaded. next_renewal, // stop: expect play allowed. next_renewal + kGracePeriod, // cutoff: when timer expires. timer_limits_.initial_renewal_duration_seconds); } } // Playback within renewal duration, but exceeds playback duration. TEST_P(ODKUseCase_LicenseWithRenewal, Case5) { uint64_t next_renewal = start_of_playback_ + renewal_interval_; do { const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndContinue( current_renewal, // start: when renewal is loaded. next_renewal, // stop: expect play allowed. next_renewal + kGracePeriod, // cutoff: when timer expires. timer_limits_.initial_renewal_duration_seconds); } while ((next_renewal + renewal_interval_ + kGracePeriod) < playback_end_restriction()); // Attempt playing beyond the playback window. const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndTerminate(current_renewal, // start: when renewal is loaded. playback_end_restriction(), // stop: when timer expires. timer_limits_.initial_renewal_duration_seconds); } // Renewal duration increases over time. TEST_P(ODKUseCase_LicenseWithRenewal, Case6) { uint64_t next_renewal = start_of_playback_ + renewal_interval_; do { const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndContinue( current_renewal, // start: when renewal is loaded. next_renewal, // stop: expect play allowed. next_renewal + kGracePeriod, // cutoff: when timer expires. renewal_interval_ + kGracePeriod); // Renewal_Interval_ increases with each renewal. renewal_interval_ += 100; } while (next_renewal + renewal_interval_ + kGracePeriod < playback_end_restriction()); // Attempt playing beyond the playback window: const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndTerminate(current_renewal, // start: when renewal is loaded. playback_end_restriction(), // cutoff: when timer expires. renewal_interval_ + kGracePeriod); } // Increasing renewal duration, playback exceeds last renewal. // This is a mix between case 2 and case 6. TEST_P(ODKUseCase_LicenseWithRenewal, Case7) { uint64_t next_renewal = start_of_playback_ + renewal_interval_; for (int i = 0; i < 4; i++) { const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndContinue( current_renewal, // start: when renewal is loaded. next_renewal, // stop: expect play allowed. next_renewal + kGracePeriod, // cutoff: when timer expires. renewal_interval_ + kGracePeriod); // Renewal_Interval_ increases with each renewal. renewal_interval_ += 100; } const uint64_t current_renewal = next_renewal; next_renewal = current_renewal + renewal_interval_; RenewAndTerminate(current_renewal, // start: when renewal is loaded. next_renewal + kGracePeriod, // cutoff: when timer expires. renewal_interval_ + kGracePeriod); } // This is just like Case1, except we use a null pointer for the timer values in // RenewAndContinue. It is not shown in the use case document. TEST_P(ODKUseCase_LicenseWithRenewal, NullPointerTest) { uint64_t next_renewal = start_of_playback_ + renewal_interval_; const uint64_t start = next_renewal; const uint64_t stop = start + renewal_interval_; const uint64_t cutoff = stop + kGracePeriod; const uint64_t renewal_duration_seconds = timer_limits_.initial_renewal_duration_seconds; uint64_t* timer_value_pointer = nullptr; RenewAndContinue(start, stop, cutoff, renewal_duration_seconds, timer_value_pointer); } INSTANTIATE_TEST_SUITE_P(RestrictRenewal, ODKUseCase_LicenseWithRenewal, ::testing::Values(0, 1)); // 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 ODKUseCase_LimitedDurationLicense : public RenewalTest { public: ODKUseCase_LimitedDurationLicense() { renewal_delay_ = 30u; time_of_renewal_ = start_of_playback_ + renewal_delay_; timer_limits_.soft_enforce_rental_duration = true; timer_limits_.rental_duration_seconds = 150; // 15 minutes. timer_limits_.soft_enforce_playback_duration = false; timer_limits_.total_playback_duration_seconds = 2000; // Two hours. timer_limits_.initial_renewal_duration_seconds = renewal_delay_ + kGracePeriod; } uint64_t renewal_delay_; uint64_t time_of_renewal_; }; // Playback started within rental window and continues. TEST_F(ODKUseCase_LimitedDurationLicense, Case1) { // Allow playback within the initial renewal window. LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, time_of_renewal_ + kGracePeriod); const uint64_t renewal_duration = 2000; // two hour renewal. const uint64_t play_for_one_hour = GetSystemTime(1000); RenewAndContinue(time_of_renewal_, // start: when renewal is loaded. play_for_one_hour, // stop: expect play allowed. EndOfPlaybackWindow(), // cutoff: when timer expires. renewal_duration); } // Playback started after rental duration. TEST_F(ODKUseCase_LimitedDurationLicense, Case2) { start_of_playback_ = EndOfRentalWindow() + 1; ForbidPlayback(start_of_playback_); } // Playback started within rental window but renewal not received. TEST_F(ODKUseCase_LimitedDurationLicense, Case3) { // Allow playback within the initial renewal window. LoadAndTerminatePlayback(start_of_playback_, time_of_renewal_ + kGracePeriod); } // Playback started within rental window, renewal is received, and playback // continues. TEST_F(ODKUseCase_LimitedDurationLicense, Case4) { // Allow playback within the initial renewal window. LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, time_of_renewal_ + kGracePeriod); const uint64_t renewal_duration = 2000; // two hour renewal. RenewAndTerminate(time_of_renewal_, // start: when renewal is loaded. EndOfPlaybackWindow(), // stop: when timer expires. renewal_duration); } // Playback may be restarted. TEST_F(ODKUseCase_LimitedDurationLicense, Case5) { // Allow playback within the initial renewal window. LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, time_of_renewal_ + kGracePeriod); const uint64_t renewal_duration = 2000; // two hour renewal. const uint64_t play_for_one_hour = GetSystemTime(1000); RenewAndContinue(time_of_renewal_, // start: when renewal is loaded. play_for_one_hour, // stop: expect play allowed. EndOfPlaybackWindow(), // cutoff: when timer expires. renewal_duration); const uint64_t reload_time = play_for_one_hour + 100; ReloadLicense(reload_time); // 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. RenewAndStart(reload_time, EndOfPlaybackWindow(), EndOfPlaybackWindow(), renewal_duration); // But not one second more. EXPECT_EQ(ODK_UpdateLastPlaybackTime(EndOfPlaybackWindow() + 1, &timer_limits_, &clock_values_), ODK_TIMER_EXPIRED); CheckClockValues(EndOfPlaybackWindow()); } // Verify that the backwards compatible function, ODK_InitializeV15Values, can // set up the clock values and timer limits. TEST_F(RenewalTest, V15Test) { const uint32_t key_duration = 25; ODK_NonceValues nonce_values; memset(&nonce_values, 0, sizeof(nonce_values)); const uint64_t license_loaded = GetSystemTime(10); EXPECT_EQ( ODK_InitializeV15Values(&timer_limits_, &clock_values_, &nonce_values, key_duration, license_loaded), OEMCrypto_SUCCESS); const uint64_t later_on = GetSystemTime(200); TerminatePlayback(license_loaded, license_loaded + key_duration, later_on); const uint32_t new_key_duration = 100; RenewAndTerminate(later_on, later_on + new_key_duration, new_key_duration); } } // namespace