/* * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary * source code may only be used and distributed under the Widevine Master * License Agreement. */ #include #include "odk.h" using ::testing::tuple; using ::testing::Values; using ::testing::WithParamInterface; namespace { constexpr uint64_t kTolerance = 1; // Allow 1 second of roundoff. } // namespace namespace odk_test { struct ServerExpiry { bool soft_rental; bool soft_playback; }; 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; uint64_t time = 42; ODK_InitializeClockValues(&clock_values, time); EXPECT_EQ(clock_values.time_of_license_signed, time); EXPECT_EQ(clock_values.time_of_first_decrypt, 0); EXPECT_EQ(clock_values.time_of_last_decrypt, 0); EXPECT_EQ(clock_values.time_when_timer_expires, 0); EXPECT_EQ(clock_values.timer_status, 0); 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; 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_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, 0u); EXPECT_EQ(clock_values.status, status); } // ************************************************************************ // Test that we can only start playback within the rental window. This // simulates requesting a license at rental_clock_start_, and a rental window of // 50-150s relative to license request, or 100-200s absolute. class OdkTimerRentalWindow : public ::testing::Test { public: OdkTimerRentalWindow() { rental_clock_start_ = 10000u; rental_window_start_ = 50u; rental_window_duration_ = 100u; } protected: void SetUp() override { ::testing::Test::SetUp(); // Start rental clocks at rental_clock_start_. ODK_InitializeClockValues(&clock_values_, rental_clock_start_); // Simple license values: timer_limits_.soft_expiry = false; timer_limits_.earliest_playback_start_seconds = rental_window_start_; timer_limits_.latest_playback_start_seconds = rental_window_start_ + rental_window_duration_; timer_limits_.initial_playback_duration_seconds = 0; timer_limits_.renewal_playback_duration_seconds = 0; timer_limits_.license_duration_seconds = 0; } // Simulate 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 ReloadClock(uint64_t system_time) { ODK_ClockValues old_clock_values = clock_values_; // First clear out the old clock values. ODK_InitializeClockValues(&clock_values_, 0u); ODK_ReloadClockValues(&clock_values_, old_clock_values.time_of_license_signed, old_clock_values.time_of_first_decrypt, old_clock_values.time_of_last_decrypt, old_clock_values.status, system_time); } uint64_t system_time(uint64_t rental_clock) { return rental_clock_start_ + rental_clock; } ODK_TimerLimits timer_limits_; ODK_ClockValues clock_values_; // The rental clock starts when the request is signed. uint64_t rental_clock_start_; // start of rental window in seconds since rental clock start. uint64_t rental_window_start_; // The "width" of window. uint64_t rental_window_duration_; }; TEST_F(OdkTimerRentalWindow, EarlyTest) { uint64_t timer_value = 0; // An attempt to start playback before the timer starts is an error. // We use the TIMER_EXPIRED error to mean both early or late. const uint64_t bad_start_time = system_time(timer_limits_.earliest_playback_start_seconds - 10); EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, &clock_values_, &timer_value)); const uint64_t good_start_time = system_time(timer_limits_.earliest_playback_start_seconds); EXPECT_EQ(ODK_DISABLE_TIMER, ODK_AttemptFirstPlayback(good_start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(good_start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(good_start_time, clock_values_.time_of_last_decrypt); EXPECT_EQ(0, clock_values_.time_when_timer_expires); } TEST_F(OdkTimerRentalWindow, NullTimer) { // If OEMCrypto passes in a nullpointer, then the ODK should allow this. uint64_t* timer_value_pointer = nullptr; const uint64_t bad_start_time = system_time(timer_limits_.earliest_playback_start_seconds - 10); EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, &clock_values_, timer_value_pointer)); const uint64_t good_start_time = system_time(timer_limits_.earliest_playback_start_seconds); EXPECT_EQ(ODK_DISABLE_TIMER, ODK_AttemptFirstPlayback(good_start_time, &timer_limits_, &clock_values_, timer_value_pointer)); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(good_start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(good_start_time, clock_values_.time_of_last_decrypt); EXPECT_EQ(0, clock_values_.time_when_timer_expires); } // Verify that an attempt to start playback outside the rental window fails. TEST_F(OdkTimerRentalWindow, LateTest) { uint64_t timer_value = 0; // An attempt to start playback before the timer starts is an error. // We use the TIMER_EXPIRED error to mean both early or late. const uint64_t start_time = system_time(201); EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kUnused, clock_values_.status); } // ************************************************************************ // The Hard Rental use case is a strict time limit on playback. class OdkTimerHardTest : public OdkTimerRentalWindow { protected: void SetUp() override { OdkTimerRentalWindow::SetUp(); timer_limits_.soft_expiry = false; // Hard expiry. // License duration is 200. The license starts when it was signed at 50, // so the license is valid from 50-250. The rental window is 100-200 -- as // inherited from the ODKRentalWindow class. timer_limits_.license_duration_seconds = 200u; } }; TEST_F(OdkTimerHardTest, EarlyTest) { uint64_t timer_value = 0; // An attempt to start playback before the timer starts is an error. // We use the TIMER_EXPIRED error to mean both early or late. const uint64_t bad_start_time = system_time(timer_limits_.earliest_playback_start_seconds - 10); EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kUnused, clock_values_.status); // Starting playback within the window should work. const uint64_t start_time = system_time(timer_limits_.earliest_playback_start_seconds); const uint64_t cutoff_time = system_time(timer_limits_.license_duration_seconds); // For a soft_expiry = false, we should set a timer. EXPECT_EQ(ODK_SET_TIMER, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); } TEST_F(OdkTimerHardTest, NormalTest) { uint64_t timer_value = 0; // Starting playback within the window should work. const uint64_t start_time = system_time(timer_limits_.earliest_playback_start_seconds); const uint64_t cutoff_time = system_time(timer_limits_.license_duration_seconds); // For a soft_expiry = false, we should set a timer. EXPECT_EQ(ODK_SET_TIMER, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); // Try to play before the cutoff time is valid. const uint64_t valid_play_time = cutoff_time - 1; // This play time is outside the rental window. We are allowed to continue // playback after the rental window expires as long as the first decrypt is // within the rental window. EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); EXPECT_EQ(OEMCrypto_SUCCESS, ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, &clock_values_)); EXPECT_EQ(kActive, clock_values_.status); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should change. EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); // Try to play after the cutoff time is not valid. const uint64_t late_play_time = cutoff_time + 1; EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, &clock_values_)); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should NOT change because we were not allowed to decrypt. EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); } // This verifies that the rental window only affects the first load for the // license, not the first load for the session. TEST_F(OdkTimerHardTest, Reload) { uint64_t timer_value = 0; // An attempt to start playback before the timer starts is an error. // We use the TIMER_EXPIRED error to mean both early or late. EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(75, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kUnused, clock_values_.status); // Starting playback within the window should work. const uint64_t start_time = system_time(timer_limits_.earliest_playback_start_seconds); const uint64_t cutoff_time = system_time(timer_limits_.license_duration_seconds); // For a soft_expiry = false, we should set a timer. EXPECT_EQ(ODK_SET_TIMER, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); // We can restart playback before the cutoff time. const uint64_t valid_restart_time = cutoff_time - 10; ReloadClock(valid_restart_time); EXPECT_EQ(kActive, clock_values_.status); // This restart is outside the rental window. We are allowed to // restart playback after the rental window expires as long as the first // decrypt for the license is within the rental window. EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_restart_time); EXPECT_EQ(ODK_SET_TIMER, ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(valid_restart_time, clock_values_.time_of_last_decrypt); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); // Try to play before the cutoff time is valid. const uint64_t valid_play_time = cutoff_time - 1; // This play time is outside the rental window. That means we are allowed // to continue playback after the rental window expires as long as the first // decrypt is within the rental window. EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); EXPECT_EQ(OEMCrypto_SUCCESS, ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, &clock_values_)); EXPECT_EQ(kActive, clock_values_.status); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should change. EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); // Try to play after the cutoff time is not valid. const uint64_t late_play_time = cutoff_time + 1; EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, &clock_values_)); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should NOT change because we were not allowed to decrypt. EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); } TEST_F(OdkTimerHardTest, ReloadLate) { uint64_t timer_value = 0; // Starting playback within the window should work. const uint64_t start_time = system_time(timer_limits_.earliest_playback_start_seconds); const uint64_t cutoff_time = system_time(timer_limits_.license_duration_seconds); // For a soft_expiry = false, we should set a timer. EXPECT_EQ(ODK_SET_TIMER, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); // We can not restart playback after the cutoff time. const uint64_t late_restart_time = cutoff_time + 10; ReloadClock(late_restart_time); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); // Calling UpdateLastPlaybackTimer should not be done, but if it were done, // it should also fail. EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, &clock_values_)); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should NOT change because we were not allowed to decrypt. EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); } // ************************************************************************ // The Soft Rental use case is a strict time limit on playback. class OdkTimerSoftTest : public OdkTimerRentalWindow { protected: void SetUp() override { OdkTimerRentalWindow::SetUp(); timer_limits_.soft_expiry = true; // Soft expiry. // License duration is 200. The license starts when it was signed at 50, // so the license is valid from 50-250. The rental window is 100-200 -- as // inherited from the ODKRentalWindow class. timer_limits_.license_duration_seconds = 200u; } }; TEST_F(OdkTimerSoftTest, EarlyTest) { uint64_t timer_value = 0; // An attempt to start playback before the timer starts is an error. // We use the TIMER_EXPIRED error to mean both early or late. const uint64_t bad_start_time = system_time(timer_limits_.earliest_playback_start_seconds - 10); EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, &clock_values_, &timer_value)); // Starting playback within the window should work. const uint64_t start_time = system_time(timer_limits_.earliest_playback_start_seconds); const uint64_t cutoff_time = system_time(timer_limits_.license_duration_seconds); // For a soft_expiry = true, we should not set a timer. EXPECT_EQ(ODK_DISABLE_TIMER, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); EXPECT_EQ(0u, clock_values_.time_when_timer_expires); } TEST_F(OdkTimerSoftTest, NormalTest) { uint64_t timer_value = 0; const uint64_t start_time = system_time(timer_limits_.earliest_playback_start_seconds); const uint64_t cutoff_time = system_time(timer_limits_.license_duration_seconds); // For a soft_expiry = true, we should not set a timer. EXPECT_EQ(ODK_DISABLE_TIMER, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); EXPECT_EQ(0u, clock_values_.time_when_timer_expires); // Try to play before the cutoff time is valid. const uint64_t valid_play_time = cutoff_time - 1; // This play time is outside the rental window. We are allowed to continue // playback after the rental window expires as long as the first decrypt is // within the rental window. EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); EXPECT_EQ(OEMCrypto_SUCCESS, ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, &clock_values_)); EXPECT_EQ(kActive, clock_values_.status); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should change. EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); // Try to play after the cutoff time is still valid for soft expiry. const uint64_t late_play_time = cutoff_time + 1; EXPECT_EQ(OEMCrypto_SUCCESS, ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, &clock_values_)); EXPECT_EQ(kActive, clock_values_.status); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should NOT change because we were not allowed to decrypt. EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); } // This verifies that the rental window only affects the first load for the // license, not the first load for the session. TEST_F(OdkTimerSoftTest, Reload) { uint64_t timer_value = 0; // Starting playback within the window should work. const uint64_t start_time = system_time(timer_limits_.earliest_playback_start_seconds); const uint64_t cutoff_time = system_time(timer_limits_.license_duration_seconds); // For a soft_expiry = true, we should not set a timer. EXPECT_EQ(ODK_DISABLE_TIMER, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); EXPECT_EQ(0, clock_values_.time_when_timer_expires); // We can restart playback before the cutoff time. const uint64_t valid_restart_time = cutoff_time - 10; ReloadClock(valid_restart_time); EXPECT_EQ(kActive, clock_values_.status); // This restart is outside the rental window. We are allowed to // restart playback after the rental window expires as long as the first // decrypt for the license is within the rental window. EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_restart_time); EXPECT_EQ(ODK_DISABLE_TIMER, ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(valid_restart_time, clock_values_.time_of_last_decrypt); EXPECT_EQ(0, clock_values_.time_when_timer_expires); // Try to play before the cutoff time is valid. const uint64_t valid_play_time = cutoff_time - 1; // This play time is outside the rental window. That means we are allowed // to continue playback after the rental window expires as long as the first // decrypt is within the rental window. EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); EXPECT_EQ(OEMCrypto_SUCCESS, ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, &clock_values_)); EXPECT_EQ(kActive, clock_values_.status); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should change. EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); EXPECT_EQ(0u, clock_values_.time_when_timer_expires); // Try to play after the cutoff time is still valid. const uint64_t late_play_time = cutoff_time + 1; EXPECT_EQ(OEMCrypto_SUCCESS, ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, &clock_values_)); EXPECT_EQ(kActive, clock_values_.status); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should change. EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); EXPECT_EQ(0u, clock_values_.time_when_timer_expires); } TEST_F(OdkTimerSoftTest, ReloadLate) { uint64_t timer_value = 0; // Starting playback within the window should work. const uint64_t start_time = system_time(timer_limits_.earliest_playback_start_seconds); const uint64_t cutoff_time = system_time(timer_limits_.license_duration_seconds); // For a soft_expiry = true, we should not set a timer. EXPECT_EQ(ODK_DISABLE_TIMER, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); EXPECT_EQ(0u, clock_values_.time_when_timer_expires); // We can not restart playback after the cutoff time. const uint64_t late_restart_time = cutoff_time + 10; ReloadClock(late_restart_time); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); // Calling UpdateLastPlaybackTimer should not be done, but if it were done, // it should also fail. EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, &clock_values_)); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should NOT change because we were not allowed to decrypt. EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); } // ************************************************************************ // ************************************************************************ // From the server's point of view, there are two flags that decide soft or // hard limits: the rental duration, and the playback duration. From // OEMCrypto's point of view, there is only playback duration. A soft or hard // rental duration is translated into different rental and license durations. // The four test classes below all have a 700 rental window and a 200 playback // duration. We'll use the server description, and then set the OEMCrypto // restraints in the test SetUp() function. Note, it's easier to use the word // "day" but really the rental window is 700 seconds, not 7 days. These tests // have some coverage overlap with the ones above, but it is better to have // these all grouped together to make sure we cover all of the server team's // needs. // ************************************************************************ class Odk7DayTest : public OdkTimerRentalWindow, public WithParamInterface { public: Odk7DayTest() { rental_window_duration_ = 700; rental_window_start_ = 100u; } protected: void SetUp() override { OdkTimerRentalWindow::SetUp(); server_expiry_ = GetParam(); const uint64_t playback_duration = 200; // Beginning of rental window. it is unusual to start it in the future, // but it is a supported use case, so we'll test it here. timer_limits_.earliest_playback_start_seconds = rental_window_start_; // The rental window is 700 long. timer_limits_.latest_playback_start_seconds = timer_limits_.earliest_playback_start_seconds + rental_window_duration_; // Playback duration is 200 starting from first playback. timer_limits_.initial_playback_duration_seconds = playback_duration; if (server_expiry_.soft_rental) { // The license duration limits any restart. For soft rental window, the // server will set this to the rental end + playback duration. // License duration is in seconds since license signed. timer_limits_.license_duration_seconds = timer_limits_.latest_playback_start_seconds + playback_duration; } else { // The license duration limits any restart. For hard rental window, the // server will set this to the rental end. // License duration is in seconds since license signed. timer_limits_.license_duration_seconds = timer_limits_.latest_playback_start_seconds; } timer_limits_.soft_expiry = server_expiry_.soft_playback; } ServerExpiry server_expiry_; }; TEST_P(Odk7DayTest, StartDay3) { uint64_t timer_value = 0; // Starting playback within the window should work. const uint64_t three_days = 300u; const uint64_t start_time = system_time(rental_window_start_ + three_days); const uint64_t cutoff_time = start_time + timer_limits_.initial_playback_duration_seconds; if (server_expiry_.soft_playback) { // If the playback expiry is soft, then the timer is disabled. EXPECT_EQ(ODK_DISABLE_TIMER, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); } else { // If the playback expiry is hard, then the timer should be set. EXPECT_EQ(ODK_SET_TIMER, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); } EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); // Try to play before the cutoff time is valid. const uint64_t valid_play_time = cutoff_time - 50; EXPECT_EQ(OEMCrypto_SUCCESS, ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, &clock_values_)); EXPECT_EQ(kActive, clock_values_.status); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should change. EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); if (!server_expiry_.soft_playback) { EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); } } TEST_P(Odk7DayTest, StartDay3Reload) { uint64_t timer_value = 0; // Starting playback within the window should work. const uint64_t three_days = 300u; const uint64_t start_time = system_time(rental_window_start_ + three_days); const uint64_t cutoff_time = start_time + timer_limits_.initial_playback_duration_seconds; EXPECT_NE(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); // ----------------------------------------------------------------------- // Try to reload and play before the cutoff time is valid. uint64_t valid_restart_time = cutoff_time - 10; ReloadClock(valid_restart_time); EXPECT_EQ(kActive, clock_values_.status); if (server_expiry_.soft_playback) { // If the playback expiry is soft, then the timer is disabled. EXPECT_EQ(ODK_DISABLE_TIMER, ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, &clock_values_, &timer_value)); } else { // If the playback expiry is hard, then the timer should be set. EXPECT_EQ(ODK_SET_TIMER, ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); } // Try to play before the cutoff time is valid. const uint64_t valid_play_time = cutoff_time - 1; EXPECT_EQ(OEMCrypto_SUCCESS, ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, &clock_values_)); EXPECT_EQ(kActive, clock_values_.status); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should change. EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); if (!server_expiry_.soft_playback) { EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); } // Try to play after the cutoff time is valid for soft expiry, but not hard. const uint64_t late_play_time = cutoff_time + 1; if (server_expiry_.soft_playback) { EXPECT_EQ(OEMCrypto_SUCCESS, ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, &clock_values_)); // Last decrypt should change because we were allowed to decrypt. EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); } else { EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, &clock_values_)); // Last decrypt should NOT change because we were not allowed to decrypt. EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); } // First decrypt should NOT change for either soft or hard. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // ----------------------------------------------------------------------- // Try to reload after the cutoff time is not valid for soft or hard // playback expiry. const uint64_t late_restart_time = cutoff_time + 1; ReloadClock(late_restart_time); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Calling UpdateLastPlaybackTimer should not be done, but if it were done, // it should also fail. EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, &clock_values_)); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should not change. if (server_expiry_.soft_playback) { EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); } else { EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); } } TEST_P(Odk7DayTest, StartDay6Reload) { uint64_t timer_value = 0; // Starting playback within the window should work. const uint64_t six_days = 600u; const uint64_t start_time = system_time(rental_window_start_ + six_days); const uint64_t cutoff_time = server_expiry_.soft_rental ? // If the rental expiry is soft, we can continue playing and // reloading for the full playback duration. start_time + timer_limits_.initial_playback_duration_seconds // If the rental expiry is hard, we can reload only within the // rental window. : system_time(timer_limits_.latest_playback_start_seconds); // Let's double check that math: if (server_expiry_.soft_rental) { // If that was not clear, the cutoff = 100 start of window + 600 start time // + 200 playback duration... EXPECT_EQ(system_time(900u), cutoff_time); } else { // ...and for hard rental, the cutoff = 100 start of window + 700 rental // duration. EXPECT_EQ(system_time(800u), cutoff_time); } if (server_expiry_.soft_playback) { // If the playback expiry is soft, then the timer is disabled. EXPECT_EQ(ODK_DISABLE_TIMER, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); } else { // If the playback expiry is hard, then the timer should be set. EXPECT_EQ(ODK_SET_TIMER, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); } EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); // Try to play before the cutoff time is valid. uint64_t valid_play_time = cutoff_time - 50; EXPECT_EQ(OEMCrypto_SUCCESS, ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, &clock_values_)); EXPECT_EQ(kActive, clock_values_.status); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should change. EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); if (!server_expiry_.soft_playback) { EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); } // ----------------------------------------------------------------------- // Try to reload and play before the cutoff time is valid. uint64_t valid_restart_time = cutoff_time - 10; ReloadClock(valid_restart_time); EXPECT_EQ(kActive, clock_values_.status); if (server_expiry_.soft_playback) { // If the playback expiry is soft, then the timer is disabled. EXPECT_EQ(ODK_DISABLE_TIMER, ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, &clock_values_, &timer_value)); } else { // If the playback expiry is hard, then the timer should be set. EXPECT_EQ(ODK_SET_TIMER, ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); } // Try to play before the cutoff time is valid. valid_play_time = cutoff_time - 1; EXPECT_EQ(OEMCrypto_SUCCESS, ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, &clock_values_)); EXPECT_EQ(kActive, clock_values_.status); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should change. EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); if (!server_expiry_.soft_playback) { EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); } // Try to play after the cutoff time is valid for soft expiry, but not hard. const uint64_t late_play_time = cutoff_time + 1; if (server_expiry_.soft_playback) { EXPECT_EQ(OEMCrypto_SUCCESS, ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, &clock_values_)); // Last decrypt should change because we were allowed to decrypt. EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); } else { EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, &clock_values_)); // Last decrypt should NOT change because we were not allowed to decrypt. EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); } // First decrypt should NOT change for either soft or hard. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // ----------------------------------------------------------------------- // Try to reload after the cutoff time is not valid for soft or hard // playback expiry. const uint64_t late_restart_time = cutoff_time + 2; ReloadClock(late_restart_time); EXPECT_EQ(kActive, clock_values_.status); EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Calling UpdateLastPlaybackTimer should not be done, but if it were done, // it should also fail. EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, &clock_values_)); // First decrypt should NOT change. EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); // Last decrypt should not change. if (server_expiry_.soft_playback) { EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); } else { EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); } } // This test explicitly shows the difference between hard and soft rental // expiry. This is not an OEMCrypto concept, but it is used on the serer, and // this test verifies the logic is handled correctly when we translate it into // OEMCrypto concepts. TEST_P(Odk7DayTest, StartDay6ReloadDay7) { uint64_t timer_value = 0; // Starting playback within the window should work. const uint64_t six_days = 600u; const uint64_t seven_days = 700u; const uint64_t start_time = system_time(rental_window_start_ + six_days); EXPECT_NE(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); // Reload time is 1 second after end of rental window. const uint64_t restart_time = system_time(timer_limits_.latest_playback_start_seconds + 1); ReloadClock(restart_time); if (server_expiry_.soft_rental) { if (server_expiry_.soft_playback) { // If the playback expiry is soft, then the timer is disabled. EXPECT_EQ(ODK_DISABLE_TIMER, ODK_AttemptFirstPlayback(restart_time, &timer_limits_, &clock_values_, &timer_value)); } else { const uint64_t cutoff_time = start_time + timer_limits_.initial_playback_duration_seconds; // If the playback expiry is hard, then the timer should be set. EXPECT_EQ(ODK_SET_TIMER, ODK_AttemptFirstPlayback(restart_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); EXPECT_NEAR(cutoff_time - restart_time, timer_value, kTolerance); } } else { // For hard rental expiry, reloading after the rental window fails. EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(restart_time, &timer_limits_, &clock_values_, &timer_value)); } } TEST_P(Odk7DayTest, StartDay8) { uint64_t timer_value = 0; // Starting playback after the rental window should not work. const uint64_t eight_days = 800u; const uint64_t start_time = system_time(rental_window_start_ + eight_days); EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, &timer_value)); EXPECT_EQ(kUnused, clock_values_.status); EXPECT_EQ(0u, clock_values_.time_of_first_decrypt); EXPECT_EQ(0u, clock_values_.time_of_last_decrypt); // Calling UpdateLastPlaybackTimer should not be done, but if it were done, // it should also fail. EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime( start_time, &timer_limits_, &clock_values_)); EXPECT_EQ(kUnused, clock_values_.status); EXPECT_EQ(0u, clock_values_.time_of_first_decrypt); EXPECT_EQ(0u, clock_values_.time_of_last_decrypt); } INSTANTIATE_TEST_CASE_P(OdkSoftHard, Odk7DayTest, Values(ServerExpiry({true, true}), ServerExpiry({true, false}), ServerExpiry({false, true}), ServerExpiry({false, false}))); // ************************************************************************ // ************************************************************************ // TODO(b/140765031): Cover all tests in Use Cases document. // Limited Duration License // 7 day with renewal. // Streaming with renewal // Persistent with renewal } // namespace odk_test