diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 45a9f9ee..e8b9a23a 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -7,6 +7,7 @@ #include "crypto_session.h" +#include #include #include @@ -1745,11 +1746,11 @@ CdmResponseType CryptoSession::GenerateUsageReport( static_cast(pst_report.pst_length())); LOGV("OEMCrypto_PST_Report.padding: %d\n", static_cast(pst_report.padding())); - LOGV("OEMCrypto_PST_Report.seconds_since_license_received: %ld\n", + LOGV("OEMCrypto_PST_Report.seconds_since_license_received: %" PRId64 "\n", pst_report.seconds_since_license_received()); - LOGV("OEMCrypto_PST_Report.seconds_since_first_decrypt: %ld\n", + LOGV("OEMCrypto_PST_Report.seconds_since_first_decrypt: %" PRId64 "\n", pst_report.seconds_since_first_decrypt()); - LOGV("OEMCrypto_PST_Report.seconds_since_last_decrypt: %ld\n", + LOGV("OEMCrypto_PST_Report.seconds_since_last_decrypt: %" PRId64 "\n", pst_report.seconds_since_last_decrypt()); LOGV("OEMCrypto_PST_Report: %s\n", b2a_hex(*usage_report).c_str()); diff --git a/libwvdrmengine/cdm/core/src/initialization_data.cpp b/libwvdrmengine/cdm/core/src/initialization_data.cpp index fbacf1a8..a969d67e 100644 --- a/libwvdrmengine/cdm/core/src/initialization_data.cpp +++ b/libwvdrmengine/cdm/core/src/initialization_data.cpp @@ -558,13 +558,16 @@ bool InitializationData::ConstructWidevineInitData( // Now format as Widevine init data protobuf WidevinePsshData cenc_header; - // TODO(rfrias): The algorithm is a deprecated field, but proto changes - // have not yet been pushed to production. Set until then. + // TODO(rfrias): The algorithm and provider are deprecated fields, but proto + // changes have not yet been pushed to production. Set until then. + CORE_UTIL_IGNORE_DEPRECATED cenc_header.set_algorithm(WidevinePsshData_Algorithm_AESCTR); + cenc_header.set_provider(provider); + CORE_UTIL_RESTORE_WARNINGS + for (size_t i = 0; i < key_ids.size(); ++i) { cenc_header.add_key_ids(key_ids[i]); } - cenc_header.set_provider(provider); cenc_header.set_content_id(content_id); if (method == kHlsMethodAes128) cenc_header.set_protection_scheme(kFourCcCbc1); diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp index 270a52b6..edcd8e8e 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp @@ -225,7 +225,9 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest { int status_code = url_request.GetStatusCode(response); if (expect_success) - EXPECT_EQ(kHttpOk, status_code) << "Error response: " << response; + EXPECT_EQ(kHttpOk, status_code) + << "Error response from " << server_url << ":\n" + << response; if (status_code != kHttpOk) { return ""; @@ -419,14 +421,14 @@ TEST_F(WvCdmEngineTest, LicenseRenewalSpecifiedServer) { } } -// This test generates a renewal and then requests the renewal from the same -// server from which we requested the original license. -TEST_F(WvCdmEngineTest, LicenseRenewalSameServer) { +// This test generates a renewal and then requests it from the server specified +// by the current test configuration. +TEST_F(WvCdmEngineTest, LicenseRenewal) { GenerateKeyRequest(binary_key_id(), kCencMimeType); VerifyNewKeyResponse(config_.license_server(), config_.client_auth()); GenerateRenewalRequest(); - VerifyRenewalKeyResponse(config_.license_server(), config_.client_auth()); + VerifyRenewalKeyResponse(config_.renewal_server(), config_.client_auth()); } TEST_F(WvCdmEngineTest, ParseDecryptHashStringTest) { diff --git a/libwvdrmengine/cdm/core/test/config_test_env.cpp b/libwvdrmengine/cdm/core/test/config_test_env.cpp index e2d46ba2..10dc6b6c 100644 --- a/libwvdrmengine/cdm/core/test/config_test_env.cpp +++ b/libwvdrmengine/cdm/core/test/config_test_env.cpp @@ -309,25 +309,13 @@ ConfigTestEnv::ConfigTestEnv(ServerConfigurationId server_id, bool streaming, } } -ConfigTestEnv& ConfigTestEnv::operator=(const ConfigTestEnv& other) { - this->server_id_ = other.server_id_; - this->client_auth_ = other.client_auth_; - this->key_id_ = other.key_id_; - this->key_system_ = other.key_system_; - this->license_server_ = other.license_server_; - this->provisioning_server_ = other.provisioning_server_; - this->license_service_certificate_ = other.license_service_certificate_; - this->provisioning_service_certificate_ = - other.provisioning_service_certificate_; - return *this; -} - void ConfigTestEnv::Init(ServerConfigurationId server_id) { this->server_id_ = server_id; client_auth_ = license_servers[server_id].client_tag; key_id_ = license_servers[server_id].key_id; key_system_ = kWidevineKeySystem; license_server_ = license_servers[server_id].license_server_url; + renewal_server_.clear(); provisioning_server_ = license_servers[server_id].provisioning_server_url; license_service_certificate_ = a2bs_hex(license_servers[server_id].license_service_certificate); diff --git a/libwvdrmengine/cdm/core/test/config_test_env.h b/libwvdrmengine/cdm/core/test/config_test_env.h index e3247df3..5e40446b 100644 --- a/libwvdrmengine/cdm/core/test/config_test_env.h +++ b/libwvdrmengine/cdm/core/test/config_test_env.h @@ -61,9 +61,12 @@ class ConfigTestEnv { ConfigTestEnv(ServerConfigurationId server_id, bool streaming); ConfigTestEnv(ServerConfigurationId server_id, bool streaming, bool renew, bool release); - // Allow copy and assign. Performance is not an issue in test initialization. - ConfigTestEnv(const ConfigTestEnv &other) { *this = other; }; - ConfigTestEnv& operator=(const ConfigTestEnv &other); + // Allow copy, assign, and move. Performance is not an issue in test + // initialization. + ConfigTestEnv(const ConfigTestEnv&) = default; + ConfigTestEnv(ConfigTestEnv&&) = default; + ConfigTestEnv& operator=(const ConfigTestEnv&) = default; + ConfigTestEnv& operator=(ConfigTestEnv&&) = default; ~ConfigTestEnv() {}; @@ -72,6 +75,12 @@ class ConfigTestEnv { const KeyId& key_id() const { return key_id_; } const CdmKeySystem& key_system() const { return key_system_; } const std::string& license_server() const { return license_server_; } + const std::string& renewal_server() const { + if (!renewal_server_.empty()) + return renewal_server_; + else + return license_server_; + } const std::string& provisioning_server() const { return provisioning_server_; } @@ -93,6 +102,9 @@ class ConfigTestEnv { void set_license_server(const std::string& license_server) { license_server_.assign(license_server); } + void set_renewal_server(const std::string& renewal_server) { + renewal_server_.assign(renewal_server); + } void set_license_service_certificate( const std::string& license_service_certificate) { license_service_certificate_.assign(license_service_certificate); @@ -113,6 +125,7 @@ class ConfigTestEnv { KeyId key_id_; CdmKeySystem key_system_; std::string license_server_; + std::string renewal_server_; std::string provisioning_server_; std::string license_service_certificate_; std::string provisioning_service_certificate_; diff --git a/libwvdrmengine/cdm/core/test/duration_use_case_test.cpp b/libwvdrmengine/cdm/core/test/duration_use_case_test.cpp index 53d57956..0fd4f519 100644 --- a/libwvdrmengine/cdm/core/test/duration_use_case_test.cpp +++ b/libwvdrmengine/cdm/core/test/duration_use_case_test.cpp @@ -6,6 +6,8 @@ // would, but in parallel, attempting to create as many collisions in the CDM // code as possible. +#include + #include #include #include @@ -45,7 +47,7 @@ const std::string kCencMimeType = "cenc"; // check. Similarly, when we verify that playback may continue until STOP, we // wait until STOP-kFudge and then check. License sign and load times are not // fudged because neither direction is more lenient than the other direction. -constexpr uint64_t kFudge = 1; +constexpr uint64_t kFudge = 2; // How long we allow for a full license round trip -- including time for // generating the request, sending the request to the server, processing the // request at the server, sending a response back. Since this is constant, we @@ -53,6 +55,9 @@ constexpr uint64_t kFudge = 1; // expire. This must be smaller than the renewal_recovery_duration for all of // our test cases below. constexpr uint64_t kRoundTripTime = 10; +// How long we use for a playback duration for many tests. This value should be +// short, but it should also be larger than our fudge value above. +constexpr uint64_t kPlayDuration = 4 + kFudge; // A renewal policy, whose values should match the policy on UAT and in // policies.dat with the same name. @@ -70,10 +75,13 @@ struct RenewalPolicy { uint64_t renewal_recovery_duration; }; -const RenewalPolicy kShortRenewal = {"CDM_LicenseWithRenewal_renewal", 25, 15}; -const RenewalPolicy kLongRenewal = {"CDM_LicenseWithRenewal_long_renewal", 40, - 15}; +const RenewalPolicy kShortRenewal = {"CDM_LicenseWithRenewal_renewal", 15, 10}; +const RenewalPolicy kLongRenewal = {"CDM_LicenseWithRenewal_long_renewal", 30, + 10}; const RenewalPolicy kLDLRenewal = {"CDM_LimitedDurationLicense_renewal", 0, 0}; +const RenewalPolicy kInfiniteRenewal = {"CDM_InfiniteRenewal_renewal", 0, 0}; +const RenewalPolicy kLicenseDurationWithRenewal = { + "CDM_LicenseDurationWithRenewal_renewal", 10, 0}; // Key ID in all duration tests. const KeyId kKeyId = "Duration_Key===="; @@ -156,7 +164,20 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, FetchLicense(); } - void TearDown() override { cdm_engine_.CloseSession(session_id_); } + void TearDown() override { + cdm_engine_.CloseSession(session_id_); + // Log the time used in this test suite. When this comment was written, + // these tests took over three hours. If we want to improve that, we need to + // track these times. + static uint64_t first_time = start_of_rental_clock_; + uint64_t delta = wvcdm::Clock().GetCurrentTime() - first_time; + uint64_t sec = delta % 60; + delta = delta / 60; + uint64_t min = delta % 60; + uint64_t hour = delta / 60; + LOGD("Time used in test suite so far: %" PRIu64 ":%02" PRIu64 ":%02" PRIu64, + hour, min, sec); + } // The end of the playback window. (using system clock) // This is not useful if the playback duration is 0, which means infinite. In @@ -186,7 +207,8 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, if (desired_rental_time >= rental_time) { TestSleep::Sleep(desired_rental_time - rental_time); } else { - LOGW("Test Clock skew sleeping from rental clock time %ld to %ld", + LOGW("Test Clock skew sleeping from rental clock time %" PRIu64 + " to %" PRIu64, rental_time, desired_rental_time); } cdm_engine_.OnTimerEvent(); @@ -233,13 +255,14 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, } // Append the content/policy id for the test to the URL. - std::string MakeUrl(const std::string& policy_id) { + std::string MakeUrl(const std::string& server_url, + const std::string& policy_id) { // For these tests, we want to specify the policy, but the UAT server only // allows us to set the content id as the video_id. So each policy is // matched to a single license with the same name. The local license // server, on the other hand, wants to see the policy id in the url. So we // have to guess which format to use based on the name of the server. - const std::string path = config_.license_server() + config_.client_auth(); + const std::string path = server_url + config_.client_auth(); std::string video_query; if (path.find("proxy.uat") != std::string::npos) { // This is uat or uat-nightly. Set the video_id. @@ -256,7 +279,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, void GetKeyResponse(const CdmKeyRequest& key_request) { // The content id matches the policy id used on UAT. - const std::string url = MakeUrl(content_id_); + const std::string url = MakeUrl(config_.license_server(), content_id_); UrlRequest url_request(url); ASSERT_TRUE(url_request.is_connected()); @@ -293,34 +316,27 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, } // 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) { + // playback from |start| to |stop|. If you refer to the diagrams in "License + // Duration and Renewal", this tests a cyan bar with a green check mark. Both + // |start| and |stop| are system times. + void LoadAndAllowPlayback(uint64_t start, uint64_t stop) { SleepUntil(start); LoadLicense(); - AllowPlayback(start, stop, cutoff); + AllowPlayback(start, stop); } - // 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) { + // Verify that we are allowed playback from |start| to |stop|. If you refer to + // the diagrams in "License Duration and Renewal", this tests a cyan bar with + // a green check mark. Both |start| and |stop| are all system times. + void AllowPlayback(uint64_t start, uint64_t stop) { ASSERT_LT(start, stop); - if (cutoff > 0) ASSERT_LE(stop, cutoff); - SleepUntil(start + kFudge); + SleepUntil(start); Decrypt(); const uint64_t mid = (start + stop) / 2; SleepUntil(mid); Decrypt(); SleepUntil(stop - kFudge); Decrypt(); - // TODO: Is there a way to verify that playback will be terminated at - // cutoff? } // Simulate loading or reloading a license, then attempt to play from |start| @@ -331,19 +347,18 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, void LoadAndTerminatePlayback(uint64_t start, uint64_t cutoff) { SleepUntil(start); LoadLicense(); - TerminatePlayback(start, cutoff, cutoff + kFudge); + TerminatePlayback(start, cutoff); } - // 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) { + // Attempt to play from |start| to |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. This assumes that |cutoff| is before + // |stop|. Both |start| and |cutoff| are system times. + void TerminatePlayback(uint64_t start, uint64_t cutoff) { ASSERT_LT(start, cutoff); - ASSERT_LT(cutoff, stop); - AllowPlayback(start, cutoff, cutoff); - SleepUntil(stop + kFudge); + AllowPlayback(start, cutoff); + SleepUntil(cutoff + kFudge); FailDecrypt(); } @@ -363,34 +378,54 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, const std::vector input(buffer_size, 0); std::vector output(buffer_size, 0); const std::vector iv(KEY_IV_SIZE, 0); - Decrypt(session_id_, kKeyId, input, iv, &output, NO_ERROR); + const uint64_t now = CurrentRentalTime(); + EXPECT_EQ(NO_ERROR, Decrypt(session_id_, kKeyId, input, iv, &output)) + << "Failed to decrypt when rental clock = " << now + << ", and playback clock = " + << ((now < start_of_playback_) ? 0 : (now - start_of_playback_)); } void FailDecrypt() { - CdmResponseType expected_status = NEED_KEY; - // On low end devices, for some tests, we are lenient on playback - // termination. This means that low end devices are not required to fail - // playback. Tests that allow lenience are enumerated in the - // documentation. See the section in "License Duration and Renewal" on - // lenient tests. + bool allow_success = false; + // We will be lenient for some tests if it is a low end device without a + // usage table or if it is a pre-v16 devices. We are only leinient + // for an offline license. Being lenient means we allow playback, even + // though the policy requests playback cutoff. Tests that allow lenience + // are enumerated in the documentation. See the section in "License + // Duration and Renewal" on lenient tests. These tests call the function + // AllowLenience to signal this case. const bool low_end_device = (wvoec::global_features.api_version < wvoec::kCoreMessagesAPI || !wvoec::global_features.usage_table); if (allow_lenience_ && low_end_device && can_persist_) { - expected_status = NO_ERROR; - allow_lenience_ = false; // Only allow lenience once per test. + allow_success = true; } + allow_lenience_ = false; // Only allow lenience once per test. + constexpr size_t buffer_size = 500; const std::vector input(buffer_size, 0); std::vector output(buffer_size, 0); const std::vector iv(KEY_IV_SIZE, 0); - Decrypt(session_id_, kKeyId, input, iv, &output, expected_status); + const uint64_t now = CurrentRentalTime(); + CdmResponseType status = Decrypt(session_id_, kKeyId, input, iv, &output); + // We always allow failure. that's what we usually expect. + if (status == NEED_KEY) return; + // No other error code is allowed: either NO_ERROR or NEED_KEY. + ASSERT_EQ(NO_ERROR, status) + << "Failed to decrypt with unexpected error code when rental clock = " + << now << ", and playback clock = " + << (now < start_of_playback_ ? 0 : now - start_of_playback_); + // However, for those cases decided above, we also allow success. + EXPECT_TRUE(allow_success) + << "Device was unexpectedly lenient when rental clock = " << now + << ", and playback clock = " + << (now < start_of_playback_ ? 0 : now - start_of_playback_); } - void Decrypt(const CdmSessionId& session_id, const KeyId& key_id, - const std::vector& input, - const std::vector& iv, std::vector* output, - CdmResponseType expected_status) { + CdmResponseType Decrypt(const CdmSessionId& session_id, const KeyId& key_id, + const std::vector& input, + const std::vector& iv, + std::vector* output) { CdmDecryptionParametersV16 params(key_id); params.is_secure = false; CdmDecryptionSample sample(input.data(), output->data(), 0, input.size(), @@ -399,8 +434,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, sample.subsamples.push_back(subsample); params.samples.push_back(sample); - CdmResponseType status = cdm_engine_.DecryptV16(session_id, params); - ASSERT_EQ(expected_status, status); + return cdm_engine_.DecryptV16(session_id, params); } CdmSessionId session_id_; @@ -427,7 +461,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, /*****************************************************************************/ // 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 +// in the document "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 @@ -444,7 +478,7 @@ class CdmUseCase_Streaming : public CdmDurationTest { // Rental duration = 3 hours hard. (use 300 for readability) // Playback duration = 0 (unlimited) timer_limits_.soft_enforce_rental_duration = false; - timer_limits_.rental_duration_seconds = 35; + timer_limits_.rental_duration_seconds = 40; timer_limits_.total_playback_duration_seconds = 0; } }; @@ -452,8 +486,7 @@ class CdmUseCase_Streaming : public CdmDurationTest { // Playback within rental duration. TEST_P(CdmUseCase_Streaming, Case1) { // Allow playback within the rental window. - LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow(), - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow()); } // Playback exceeds rental duration. @@ -465,25 +498,26 @@ TEST_P(CdmUseCase_Streaming, Case2) { // Playback with stops/restarts within rental duration, last one exceeds rental // duration. TEST_P(CdmUseCase_Streaming, Case3) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 5, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 10, start_of_playback_ + 15, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); - LoadAndTerminatePlayback(start_of_playback_ + 20, EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfRentalWindow()); } // Playback within rental duration, restart exceeds rental duration. TEST_P(CdmUseCase_Streaming, Case4) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 5, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - ForbidPlayback(EndOfRentalWindow() + 5); + ForbidPlayback(EndOfRentalWindow() + kFudge); } // Initial playback exceeds rental duration. -TEST_P(CdmUseCase_Streaming, Case5) { ForbidPlayback(EndOfRentalWindow() + 5); } +TEST_P(CdmUseCase_Streaming, Case5) { + ForbidPlayback(EndOfRentalWindow() + kFudge); +} /*****************************************************************************/ // Streaming Quick Start. The user must start watching within 30 seconds, and @@ -500,7 +534,7 @@ class CdmUseCase_StreamingQuickStart : public CdmDurationTest { timer_limits_.soft_enforce_playback_duration = false; // A valid start of playback time. - start_of_playback_ = timer_limits_.rental_duration_seconds - 10; + start_of_playback_ = timer_limits_.rental_duration_seconds - kPlayDuration; } }; @@ -508,9 +542,9 @@ class CdmUseCase_StreamingQuickStart : public CdmDurationTest { TEST_P(CdmUseCase_StreamingQuickStart, Case1) { // As seen in the drawing, the playback window exceeds the rental window. EXPECT_LE(EndOfRentalWindow(), EndOfPlaybackWindow()); - // Allow playback within the playback window. - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 20, - EndOfPlaybackWindow()); + // Allow playback within the playback window, even if it is after the rental + // window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow() + kFudge); } // Playback exceeds playback duration. @@ -520,91 +554,107 @@ TEST_P(CdmUseCase_StreamingQuickStart, Case2) { } // Playback with stops/restarts within playback duration, last one exceeds -// playback duration. +// playback duration. This also tests that playback may be restarted after the +// rental window has closed, as long as the playback window is still open. TEST_P(CdmUseCase_StreamingQuickStart, Case3) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_, + start_of_playback_ + 2 * kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 15, start_of_playback_ + 20, - EndOfPlaybackWindow()); + // Check that we can restart the playback after the rental window has stopped. + const uint64_t after_rental = EndOfRentalWindow() + kPlayDuration; + LoadAndAllowPlayback(after_rental, after_rental + kPlayDuration); UnloadLicense(); - LoadAndTerminatePlayback(start_of_playback_ + 25, EndOfPlaybackWindow()); + LoadAndTerminatePlayback(EndOfPlaybackWindow() - kPlayDuration, + EndOfPlaybackWindow()); } // Initial playback exceeds rental duration. TEST_P(CdmUseCase_StreamingQuickStart, Case4) { - ForbidPlayback(EndOfRentalWindow() + 10); + ForbidPlayback(EndOfRentalWindow() + kFudge); } +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. This class sets up the +// times to match the diagrams in the document "License Duration and +// Renewal". There are four sets of classes below this separate out +// hard/soft for enforcement of rental and playback duration. +class CdmUseCase_SevenTwo : public CdmDurationTest { + public: + CdmUseCase_SevenTwo(const std::string& content_id) + : CdmDurationTest(content_id) { + // Rental Duration should be 7 days. In the diagram. + timer_limits_.rental_duration_seconds = seven_days_; + timer_limits_.total_playback_duration_seconds = two_days_; + } + + // Playback window is entirely contained in rental window. In diagram this is + // on day 3. + void StartDay3() { start_of_playback_ = day3_; } + + // Playback overlays end of rental window. In diagram, this is on day 6. + void StartDay6() { start_of_playback_ = day6_; } + static constexpr uint64_t seven_days_ = 100; + static constexpr uint64_t two_days_ = 50; + static constexpr uint64_t day3_ = 20; + static constexpr uint64_t day6_ = 70; +}; + /*****************************************************************************/ // Seven Day / Two Day. The user must start watching within 7 days. Once // started, the user has two days to finish the video. Playback is cutoff by the // smaller of the two time limits. The first four cases start on day // three. (See above for note on Use Case tests) -class CdmUseCase_SevenHardTwoHard_Start3 : public CdmDurationTest { +class CdmUseCase_SevenHardTwoHard : public CdmUseCase_SevenTwo { public: - CdmUseCase_SevenHardTwoHard_Start3() - : CdmDurationTest("CDM_SevenHardTwoHard") { - // Rental duration = 200, hard - // Playback duration = 100, hard - timer_limits_.rental_duration_seconds = 200; // 7 days. + CdmUseCase_SevenHardTwoHard() : CdmUseCase_SevenTwo("CDM_SevenHardTwoHard") { timer_limits_.soft_enforce_rental_duration = false; - timer_limits_.total_playback_duration_seconds = 100; // 2 days. timer_limits_.soft_enforce_playback_duration = false; - - start_of_playback_ = 50; // Start is less than rental - playback. } }; // Playback within playback and rental duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start3, Case1) { +TEST_P(CdmUseCase_SevenHardTwoHard, Case1) { + StartDay3(); // As seen in the drawing, the playback window is within the rental window. EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); // Allow playback within the rental window. - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); } // Playback exceeds playback duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start3, Case2) { +TEST_P(CdmUseCase_SevenHardTwoHard, Case2) { + StartDay3(); // Allow playback within the playback window, but not beyond. LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); } // Playback with stops/restarts within playback duration, last one exceeds // playback duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start3, Case3) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfPlaybackWindow()); +TEST_P(CdmUseCase_SevenHardTwoHard, Case3) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, - EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); AllowLenience(); - LoadAndTerminatePlayback(start_of_playback_ + 50, EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow()); } // Restart exceeds playback duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start3, Case4) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, - EndOfPlaybackWindow()); +TEST_P(CdmUseCase_SevenHardTwoHard, Case4) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); AllowLenience(); - ForbidPlayback(EndOfPlaybackWindow() + 10); + ForbidPlayback(EndOfPlaybackWindow() + kFudge); } -// The next four cases start on day six. -class CdmUseCase_SevenHardTwoHard_Start6 - : public CdmUseCase_SevenHardTwoHard_Start3 { - public: - CdmUseCase_SevenHardTwoHard_Start6() { - // Start is more than rental - playback = 200 - 100 - start_of_playback_ = 150; - } -}; - // Playback exceeds rental duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case5) { +TEST_P(CdmUseCase_SevenHardTwoHard, Case5) { + StartDay6(); // As seen in the drawing, the playback window is exceeds the rental window. EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); // Allow playback within the rental window. @@ -613,35 +663,38 @@ TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case5) { // Playback with stops/restarts within playback duration, last one is terminated // at the end of the rental duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case6) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfRentalWindow()); +TEST_P(CdmUseCase_SevenHardTwoHard, Case6) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); // Allow playback that starts within rental window, but terminate at end of // rental window. - LoadAndTerminatePlayback(start_of_playback_ + 40, EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfRentalWindow()); } // Playback within playback duration, restart exceeds rental duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case7) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, - EndOfRentalWindow()); +TEST_P(CdmUseCase_SevenHardTwoHard, Case7) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); // Restart does not work after end of playback window. - ForbidPlayback(EndOfRentalWindow() + 10); + ForbidPlayback(EndOfRentalWindow() + kPlayDuration); } // Playback exceeds rental and playback duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case8) { +TEST_P(CdmUseCase_SevenHardTwoHard, Case8) { + StartDay6(); LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); } // First playback cannot exceed rental duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case9) { - ForbidPlayback(EndOfRentalWindow() + 10); +TEST_P(CdmUseCase_SevenHardTwoHard, Case9) { + StartDay6(); + ForbidPlayback(EndOfRentalWindow() + kFudge); } /*****************************************************************************/ @@ -649,73 +702,59 @@ TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case9) { // started, the user has two days to finish the video. Playback is cutoff by the // rental duration time limits. The first four cases start on day three. (See // above for note on Use Case tests) -class CdmUseCase_SevenHardTwoSoft_Start3 : public CdmDurationTest { +class CdmUseCase_SevenHardTwoSoft : public CdmUseCase_SevenTwo { public: - CdmUseCase_SevenHardTwoSoft_Start3() - : CdmDurationTest("CDM_SevenHardTwoSoft") { - // Rental duration = 200, hard - // Playback duration = 100, hard - timer_limits_.rental_duration_seconds = 200; + CdmUseCase_SevenHardTwoSoft() : CdmUseCase_SevenTwo("CDM_SevenHardTwoSoft") { timer_limits_.soft_enforce_rental_duration = false; - timer_limits_.total_playback_duration_seconds = 100; timer_limits_.soft_enforce_playback_duration = true; - - start_of_playback_ = 50; // Start is less than rental - playback. } }; // Playback within playback and rental duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start3, Case1) { +TEST_P(CdmUseCase_SevenHardTwoSoft, Case1) { + StartDay3(); // As seen in the drawing, the playback window is within the rental window. EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); // Allow playback within the rental window. - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); } // Playback exceeds playback duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start3, Case2) { +TEST_P(CdmUseCase_SevenHardTwoSoft, Case2) { + StartDay3(); // Allow playback within the playback window, and a little after. // Timer expires at end of rental window. - LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 15, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_, + EndOfPlaybackWindow() + kPlayDuration); } // Playback with stops/restarts within playback duration, last one exceeds // playback duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start3, Case3) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfRentalWindow()); +TEST_P(CdmUseCase_SevenHardTwoSoft, Case3) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 50, EndOfPlaybackWindow() + 15, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow() + kPlayDuration); } // Restart exceeds playback duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start3, Case4) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, - EndOfRentalWindow()); +TEST_P(CdmUseCase_SevenHardTwoSoft, Case4) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, + start_of_playback_ + 4 * kPlayDuration); UnloadLicense(); if (!can_persist_) return; // streaming license cannot restart. AllowLenience(); - ForbidPlayback(EndOfPlaybackWindow() + 10); + ForbidPlayback(EndOfPlaybackWindow() + kFudge); } -// The next four cases start on day six. -class CdmUseCase_SevenHardTwoSoft_Start6 - : public CdmUseCase_SevenHardTwoSoft_Start3 { - public: - CdmUseCase_SevenHardTwoSoft_Start6() { - // Start is more than rental - playback = 200 - 100 - start_of_playback_ = 150; - } -}; - // Playback exceeds rental duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case5) { +TEST_P(CdmUseCase_SevenHardTwoSoft, Case5) { + StartDay6(); // As seen in the drawing, the playback window is exceeds the rental window. EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); // Allow playback within the rental window. @@ -724,33 +763,36 @@ TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case5) { // Playback with stops/restarts within playback duration, last one exceeds // rental duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case6) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfRentalWindow()); +TEST_P(CdmUseCase_SevenHardTwoSoft, Case6) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); - LoadAndTerminatePlayback(start_of_playback_ + 40, EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfRentalWindow()); } // Playback within playback duration, restart exceeds rental duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case7) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, - EndOfRentalWindow()); +TEST_P(CdmUseCase_SevenHardTwoSoft, Case7) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); // Restart does not work after end of playback window. - ForbidPlayback(EndOfRentalWindow() + 10); + ForbidPlayback(EndOfRentalWindow() + kPlayDuration); } // Playback exceeds rental and playback duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case8) { +TEST_P(CdmUseCase_SevenHardTwoSoft, Case8) { + StartDay6(); LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); } // First playback cannot exceed rental duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case9) { - ForbidPlayback(EndOfRentalWindow() + 10); +TEST_P(CdmUseCase_SevenHardTwoSoft, Case9) { + StartDay6(); + ForbidPlayback(EndOfRentalWindow() + kFudge); } /*****************************************************************************/ @@ -758,70 +800,57 @@ TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case9) { // started, the user has two days to finish the video. Playback is cutoff by the // playback duration. The first four cases start on day three. (See above for // note on Use Case tests) -class CdmUseCase_SevenSoftTwoHard_Start3 : public CdmDurationTest { +class CdmUseCase_SevenSoftTwoHard : public CdmUseCase_SevenTwo { public: - CdmUseCase_SevenSoftTwoHard_Start3() - : CdmDurationTest("CDM_SevenSoftTwoHard") { - // Rental duration = 200, hard - // Playback duration = 100, hard - timer_limits_.rental_duration_seconds = 200; + CdmUseCase_SevenSoftTwoHard() : CdmUseCase_SevenTwo("CDM_SevenSoftTwoHard") { timer_limits_.soft_enforce_rental_duration = true; - timer_limits_.total_playback_duration_seconds = 100; timer_limits_.soft_enforce_playback_duration = false; - - start_of_playback_ = 50; // Start is less than rental - playback. } }; // Playback within playback and rental duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start3, Case1) { +TEST_P(CdmUseCase_SevenSoftTwoHard, Case1) { + StartDay3(); // As seen in the drawing, the playback window is within the rental window. EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); // Allow playback within the rental window. - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); } // Playback exceeds playback duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start3, Case2) { +TEST_P(CdmUseCase_SevenSoftTwoHard, Case2) { + StartDay3(); // Allow playback within the playback window, but not beyond. LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); } // Playback with stops/restarts within playback duration, last one exceeds // playback duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start3, Case3) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfPlaybackWindow()); +TEST_P(CdmUseCase_SevenSoftTwoHard, Case3) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, - EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); AllowLenience(); - LoadAndTerminatePlayback(start_of_playback_ + 50, EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow()); } // Restart exceeds playback duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start3, Case4) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, - EndOfPlaybackWindow()); +TEST_P(CdmUseCase_SevenSoftTwoHard, Case4) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, + start_of_playback_ + 4 * kPlayDuration); UnloadLicense(); AllowLenience(); - ForbidPlayback(EndOfPlaybackWindow() + 10); + ForbidPlayback(EndOfPlaybackWindow() + kFudge); } -// The next four cases start on day six. -class CdmUseCase_SevenSoftTwoHard_Start6 - : public CdmUseCase_SevenSoftTwoHard_Start3 { - public: - CdmUseCase_SevenSoftTwoHard_Start6() { - // Start is more than rental - playback = 200 - 100 - start_of_playback_ = 150; - } -}; - // Playback exceeds rental duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case5) { +TEST_P(CdmUseCase_SevenSoftTwoHard, Case5) { + StartDay6(); // 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 @@ -831,34 +860,38 @@ TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case5) { // Playback with stops/restarts within playback duration, last one exceeds // rental duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case6) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfPlaybackWindow()); +TEST_P(CdmUseCase_SevenSoftTwoHard, Case6) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, - EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); AllowLenience(); - LoadAndTerminatePlayback(start_of_playback_ + 40, EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow()); } // Restart exceeds rental duration, playback exceeds playback duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case7) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, - EndOfPlaybackWindow()); +TEST_P(CdmUseCase_SevenSoftTwoHard, Case7) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); AllowLenience(); - LoadAndTerminatePlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow()); + LoadAndTerminatePlayback(EndOfRentalWindow() + kPlayDuration, + EndOfPlaybackWindow()); } // Playback exceeds rental and playback duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case8) { +TEST_P(CdmUseCase_SevenSoftTwoHard, Case8) { + StartDay6(); LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); } // First playback cannot exceed rental duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case9) { - ForbidPlayback(EndOfRentalWindow() + 10); +TEST_P(CdmUseCase_SevenSoftTwoHard, Case9) { + StartDay6(); + ForbidPlayback(EndOfRentalWindow() + kFudge); } /*****************************************************************************/ @@ -866,129 +899,120 @@ TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case9) { // started, the user has two days to finish the video. Playback is not cutoff, // but restarts are not allowed after playback duration. The first four cases // start on day three. (See above for note on Use Case tests) -class CdmUseCase_SevenSoftTwoSoft_Start3 : public CdmDurationTest { +class CdmUseCase_SevenSoftTwoSoft : public CdmUseCase_SevenTwo { public: - CdmUseCase_SevenSoftTwoSoft_Start3() - : CdmDurationTest("CDM_SevenSoftTwoSoft") { - // Rental duration = 200, hard - // Playback duration = 100, hard - timer_limits_.rental_duration_seconds = 200; + CdmUseCase_SevenSoftTwoSoft() : CdmUseCase_SevenTwo("CDM_SevenSoftTwoSoft") { timer_limits_.soft_enforce_rental_duration = true; - timer_limits_.total_playback_duration_seconds = 100; timer_limits_.soft_enforce_playback_duration = true; - - start_of_playback_ = 50; // Start is less than rental - playback. } }; // Playback within playback and rental duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start3, Case1) { +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case1) { + StartDay3(); // As seen in the drawing, the playback window is within the rental window. EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); // Allow playback within the rental window. - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, 0); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); } // Playback exceeds playback duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start3, Case2) { +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case2) { + StartDay3(); // Allow playback within the playback window, and beyond. No timer limit. - LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 10, 0); + LoadAndAllowPlayback(start_of_playback_, + EndOfPlaybackWindow() + kPlayDuration); } // Playback with stops/restarts within playback duration, last one exceeds // playback duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start3, Case3) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, 0); +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case3) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, 0); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 50, EndOfPlaybackWindow() + 15, 0); + LoadAndAllowPlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow() + kPlayDuration); } // Restart exceeds playback duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start3, Case4) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case4) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, + start_of_playback_ + 4 * kPlayDuration); UnloadLicense(); if (!can_persist_) return; // streaming license cannot restart. AllowLenience(); - ForbidPlayback(EndOfPlaybackWindow() + 10); + ForbidPlayback(EndOfPlaybackWindow() + kFudge); } -// The next four cases start on day six. -class CdmUseCase_SevenSoftTwoSoft_Start6 - : public CdmUseCase_SevenSoftTwoSoft_Start3 { - public: - CdmUseCase_SevenSoftTwoSoft_Start6() { - // Start is more than rental - playback = 200 - 100 - start_of_playback_ = 150; - } -}; - // Playback exceeds rental duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case5) { +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case5) { + StartDay6(); // As seen in the drawing, the playback window exceeds the rental window. // We should be able to play a little bit after the rental window. - // We'll use 10 as "a little bit". - const uint64_t end_play = EndOfRentalWindow() + 10; + const uint64_t end_play = EndOfRentalWindow() + kPlayDuration; // For this case, we are playing after the end of the playback window, too. EXPECT_GT(EndOfPlaybackWindow(), end_play); // Allow playback past the rental window, but within the playback window. - LoadAndAllowPlayback(start_of_playback_, end_play, 0); + LoadAndAllowPlayback(start_of_playback_, end_play); } // Playback with stops/restarts within playback duration, last one exceeds // rental and playback duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case6) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, 0); +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case6) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, 0); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 40, EndOfPlaybackWindow() + 10, 0); + LoadAndAllowPlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow() + kPlayDuration); } // Playback with stops/restarts within playback duration, last one exceeds // playback duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case7) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case7) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); // Allow playback to start after end of rental window, and continue after // playback window. - LoadAndAllowPlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow() + 10, 0); + LoadAndAllowPlayback(EndOfRentalWindow() + kPlayDuration, + EndOfPlaybackWindow() + kPlayDuration); UnloadLicense(); // But forbid restart after playback window. if (!can_persist_) return; // streaming license cannot restart. - ForbidPlayback(EndOfPlaybackWindow() + 20); + ForbidPlayback(EndOfPlaybackWindow() + 2 * kPlayDuration); } // Playback exceeds rental and playback duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case8) { - LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 20, 0); +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case8) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, + EndOfPlaybackWindow() + 2 * kPlayDuration); } // First playback cannot exceed rental duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case9) { - ForbidPlayback(EndOfRentalWindow() + 10); +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case9) { + StartDay6(); + ForbidPlayback(EndOfRentalWindow() + kFudge); } INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_Streaming, ::testing::Values(false, true)); INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_StreamingQuickStart, ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoHard_Start3, +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoHard, ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoHard_Start6, +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoSoft, ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoSoft_Start3, +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoHard, ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoSoft_Start6, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoHard_Start3, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoHard_Start6, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoSoft_Start3, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoSoft_Start6, +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoSoft, ::testing::Values(false, true)); class RenewalTest : public CdmDurationTest { @@ -1019,7 +1043,8 @@ class RenewalTest : public CdmDurationTest { const CdmResponseType result = cdm_engine_.GenerateRenewalRequest(session_id_, &request); ASSERT_EQ(KEY_MESSAGE, result); - const std::string url = MakeUrl(renewal_policy.policy_id); + const std::string url = + MakeUrl(config_.renewal_server(), renewal_policy.policy_id); renewal_in_flight_.reset(new UrlRequest(url)); ASSERT_TRUE(renewal_in_flight_->is_connected()); renewal_in_flight_->PostRequest(request.message); @@ -1064,9 +1089,9 @@ class RenewalTest : public CdmDurationTest { const RenewalPolicy& renewal_policy) { EXPECT_LE(start, load_time); EXPECT_LT(load_time, stop); - if (start < load_time) AllowPlayback(start, load_time, current_cutoff_); + if (start < load_time) AllowPlayback(start, load_time); LoadRenewal(load_time, renewal_policy); - AllowPlayback(load_time, stop, current_cutoff_); + AllowPlayback(load_time, stop); } // Verify that a renewal can be processed and attempt to play from |start| to @@ -1112,7 +1137,7 @@ class RenewalTest : public CdmDurationTest { // license is reloaded. LoadLicense(); ComputeCutoff(start, renewal_policy); - AllowPlayback(start, stop, current_cutoff_); + AllowPlayback(start, stop); } std::unique_ptr renewal_in_flight_; @@ -1127,9 +1152,9 @@ class CdmUseCase_LicenseWithRenewal : public RenewalTest { public: CdmUseCase_LicenseWithRenewal() : RenewalTest("CDM_LicenseWithRenewal") { timer_limits_.soft_enforce_rental_duration = false; - timer_limits_.rental_duration_seconds = 360u; - initial_policy_.renewal_delay = 25u; - initial_policy_.renewal_recovery_duration = 15u; + timer_limits_.rental_duration_seconds = 180u; + initial_policy_.renewal_delay = 15u; + initial_policy_.renewal_recovery_duration = 10u; timer_limits_.initial_renewal_duration_seconds = initial_policy_.renewal_delay + initial_policy_.renewal_recovery_duration; @@ -1144,7 +1169,7 @@ class CdmUseCase_LicenseWithRenewal : public RenewalTest { LoadLicense(); // Play until just before we expect a renewal to be generated. current_cutoff_ = next_renewal + initial_policy_.renewal_recovery_duration; - AllowPlayback(start_of_playback_, next_renewal, current_cutoff_); + AllowPlayback(start_of_playback_, next_renewal); } protected: @@ -1252,7 +1277,7 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case4) { TEST_P(CdmUseCase_LicenseWithRenewal, Case5) { uint64_t stop = 0; const uint64_t cutoff = EndOfRentalWindow(); - while (stop <= cutoff) { + while (stop < cutoff) { SleepUntilRenewalNeeded(); RequestRenewal(kShortRenewal); const uint64_t start = CurrentRentalTime(); @@ -1261,6 +1286,10 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case5) { if (stop >= cutoff) { RenewAndTerminate(start, load_time, cutoff, kShortRenewal); } else { + if (stop + kRoundTripTime >= cutoff) { + // If the next load time intersects the cutoff, back off a little. + stop -= kRoundTripTime; + } RenewAndContinue(start, load_time, stop, kShortRenewal); } } @@ -1280,7 +1309,7 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case6) { // Change renewal duration, and playing until the end of the rental window. uint64_t stop = 0; const uint64_t cutoff = EndOfRentalWindow(); - while (stop <= cutoff) { + while (stop < cutoff) { SleepUntilRenewalNeeded(); RequestRenewal(kLongRenewal); const uint64_t start = CurrentRentalTime(); @@ -1289,6 +1318,10 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case6) { if (stop >= cutoff) { RenewAndTerminate(start, load_time, cutoff, kLongRenewal); } else { + if (stop + kRoundTripTime >= cutoff) { + // If the next load time intersects the cutoff, back off a little. + stop -= kRoundTripTime; + } RenewAndContinue(start, load_time, stop, kLongRenewal); } } @@ -1305,15 +1338,6 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case7) { const uint64_t stop = load_time + kShortRenewal.renewal_delay; RenewAndContinue(start, load_time, stop, kShortRenewal); } - // Change renewal duration, and playing until the end of the rental window. - for (int i = 0; i < 2; i++) { - SleepUntilRenewalNeeded(); - RequestRenewal(kLongRenewal); - const uint64_t start = CurrentRentalTime(); - const uint64_t load_time = start + kRoundTripTime; - const uint64_t stop = load_time + kLongRenewal.renewal_delay; - RenewAndContinue(start, load_time, stop, kLongRenewal); - } // We attempt to continue playing beyond the renewal cutoff. SleepUntilRenewalNeeded(); RequestRenewal(kLongRenewal); @@ -1333,9 +1357,9 @@ class CdmUseCase_LicenseWithRenewalPlayback : public RenewalTest { CdmUseCase_LicenseWithRenewalPlayback() : RenewalTest("CDM_LicenseWithRenewal_playback") { timer_limits_.soft_enforce_rental_duration = false; - timer_limits_.total_playback_duration_seconds = 360u; - initial_policy_.renewal_delay = 25u; - initial_policy_.renewal_recovery_duration = 15u; + timer_limits_.total_playback_duration_seconds = 180u; + initial_policy_.renewal_delay = 15u; + initial_policy_.renewal_recovery_duration = 10u; timer_limits_.initial_renewal_duration_seconds = initial_policy_.renewal_delay + initial_policy_.renewal_recovery_duration; @@ -1349,7 +1373,7 @@ class CdmUseCase_LicenseWithRenewalPlayback : public RenewalTest { LoadLicense(); // Play until just before we expect a renewal to be generated. current_cutoff_ = next_renewal + initial_policy_.renewal_recovery_duration; - AllowPlayback(start_of_playback_, next_renewal, current_cutoff_); + AllowPlayback(start_of_playback_, next_renewal); } protected: @@ -1457,7 +1481,7 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case4) { TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case5) { uint64_t stop = 0; const uint64_t cutoff = EndOfPlaybackWindow(); - while (stop <= cutoff) { + while (stop < cutoff) { SleepUntilRenewalNeeded(); RequestRenewal(kShortRenewal); const uint64_t start = CurrentRentalTime(); @@ -1466,6 +1490,10 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case5) { if (stop >= cutoff) { RenewAndTerminate(start, load_time, cutoff, kShortRenewal); } else { + if (stop + kRoundTripTime >= cutoff) { + // If the next load time intersects the cutoff, back off a little. + stop -= kRoundTripTime; + } RenewAndContinue(start, load_time, stop, kShortRenewal); } } @@ -1485,7 +1513,7 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case6) { // Change renewal duration, and playing until the end of the rental window. uint64_t stop = 0; const uint64_t cutoff = EndOfPlaybackWindow(); - while (stop <= cutoff) { + while (stop < cutoff) { SleepUntilRenewalNeeded(); RequestRenewal(kLongRenewal); const uint64_t start = CurrentRentalTime(); @@ -1494,6 +1522,10 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case6) { if (stop >= cutoff) { RenewAndTerminate(start, load_time, cutoff, kLongRenewal); } else { + if (stop + kRoundTripTime >= cutoff) { + // If the next load time intersects the cutoff, back off a little. + stop -= kRoundTripTime; + } RenewAndContinue(start, load_time, stop, kLongRenewal); } } @@ -1510,15 +1542,6 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case7) { const uint64_t stop = load_time + kShortRenewal.renewal_delay; RenewAndContinue(start, load_time, stop, kShortRenewal); } - // Change renewal duration, and playing until the end of the rental window. - for (int i = 0; i < 2; i++) { - SleepUntilRenewalNeeded(); - RequestRenewal(kLongRenewal); - const uint64_t start = CurrentRentalTime(); - const uint64_t load_time = start + kRoundTripTime; - const uint64_t stop = load_time + kLongRenewal.renewal_delay; - RenewAndContinue(start, load_time, stop, kLongRenewal); - } // We attempt to continue playing beyond the renewal cutoff. SleepUntilRenewalNeeded(); RequestRenewal(kLongRenewal); @@ -1538,16 +1561,16 @@ class CdmUseCase_LimitedDurationLicense : public RenewalTest { CdmUseCase_LimitedDurationLicense() : RenewalTest("CDM_LimitedDurationLicense") { renewal_delay_ = 5u; - renewal_recovery_ = 55; + renewal_recovery_ = 15; // Pick a start of playback that is within the rental window, but so that // the initial renewal window is after rental window. - start_of_playback_ = 100; + start_of_playback_ = 10; timer_limits_.soft_enforce_rental_duration = true; - timer_limits_.rental_duration_seconds = 120; // 15 minutes. + timer_limits_.rental_duration_seconds = 15; // 15 minutes. timer_limits_.soft_enforce_playback_duration = false; - timer_limits_.total_playback_duration_seconds = 300; // --> 12 hours. + timer_limits_.total_playback_duration_seconds = 60; // --> 12 hours. timer_limits_.initial_renewal_duration_seconds = renewal_delay_ + renewal_recovery_; @@ -1563,14 +1586,16 @@ class CdmUseCase_LimitedDurationLicense : public RenewalTest { // Playback started within rental window and continues. TEST_P(CdmUseCase_LimitedDurationLicense, Case1) { // Allow playback within the initial renewal window. - LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime, - current_cutoff_); + LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime); SleepUntilRenewalNeeded(); RequestRenewal(kLDLRenewal); - // Continue playback for one hour (use 100 to condense test). - const uint64_t end_of_hour = renewal_load_time_ + 100; + // Continue playback until after renewal cutoff. + const uint64_t end_of_play = renewal_load_time_ + kPlayDuration; + const uint64_t cutoff = + start_of_playback_ + renewal_delay_ + renewal_recovery_; + ASSERT_GT(end_of_play, cutoff); const uint64_t start = CurrentRentalTime(); - RenewAndContinue(start, renewal_load_time_, end_of_hour, kLDLRenewal); + RenewAndContinue(start, renewal_load_time_, end_of_play, kLDLRenewal); } // Playback started after rental duration should fail. @@ -1591,8 +1616,7 @@ TEST_P(CdmUseCase_LimitedDurationLicense, Case3) { // continues. TEST_P(CdmUseCase_LimitedDurationLicense, Case4) { // Allow playback within the initial renewal window. - LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime, - current_cutoff_); + LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime); SleepUntilRenewalNeeded(); RequestRenewal(kLDLRenewal); const uint64_t start = CurrentRentalTime(); @@ -1603,25 +1627,179 @@ TEST_P(CdmUseCase_LimitedDurationLicense, Case4) { // Playback may be restarted. TEST_P(CdmUseCase_LimitedDurationLicense, Case5) { // Allow playback within the initial renewal window. - LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime, - current_cutoff_); + LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime); SleepUntilRenewalNeeded(); RequestRenewal(kLDLRenewal); // Continue playback for one hour (use 100 to condense test). - const uint64_t end_of_hour = renewal_load_time_ + 100; + const uint64_t end_of_play = renewal_load_time_ + kPlayDuration; const uint64_t start = CurrentRentalTime(); - RenewAndContinue(start, renewal_load_time_, end_of_hour, kLDLRenewal); + RenewAndContinue(start, renewal_load_time_, end_of_play, kLDLRenewal); UnloadLicense(); // Simulate reloading the license, and then reloading the renewal, and then // restarting playback. That is allowed, and playback shall be terminated at // the end of the original playback window. - const uint64_t reload_time = end_of_hour + 100; + const uint64_t reload_time = end_of_play + kPlayDuration; ReloadAndAllowPlayback(reload_time, EndOfPlaybackWindow(), kLDLRenewal); // But not beyond playback window. SleepUntil(EndOfPlaybackWindow() + 3 * kFudge); FailDecrypt(); } +/*****************************************************************************/ +// The unlimited streaming case has no limits on license use. This use case is +// not in the documentation because it is not recommended. +class CdmUseCase_UnlimitedStreaming : public CdmDurationTest { + public: + CdmUseCase_UnlimitedStreaming() : CdmDurationTest("CDM_UnlimitedStreaming") { + timer_limits_.rental_duration_seconds = 0; + timer_limits_.total_playback_duration_seconds = 0; + } +}; + +// Playback within rental duration. +TEST_P(CdmUseCase_UnlimitedStreaming, Case1) { + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); +} + +// Playback with stops/restarts within rental duration, last one exceeds rental +// duration. +TEST_P(CdmUseCase_UnlimitedStreaming, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); +} + +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_UnlimitedStreaming, + ::testing::Values(false, true)); + +/*****************************************************************************/ +// Some content providers set the license duration instead of the rental +// duration. The test behavior should be the same on the device. This use case +// is not in the documentation because it is not recommended. +class CdmUseCase_LicenseDuration : public CdmDurationTest { + public: + CdmUseCase_LicenseDuration() : CdmDurationTest("CDM_LicenseDuration") { + // Rental duration = 3 hours hard. (use 300 for readability) + // Playback duration = 0 (unlimited) + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 40u; + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 40u; + } +}; + +// Playback within rental duration. +TEST_P(CdmUseCase_LicenseDuration, Case1) { + // Allow playback within the playback window. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback exceeds rental duration. +TEST_P(CdmUseCase_LicenseDuration, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within rental duration, last one exceeds +// playback duration. +TEST_P(CdmUseCase_LicenseDuration, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); + UnloadLicense(); + AllowLenience(); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow()); +} + +// Playback within rental duration, restart exceeds playback duration. +TEST_P(CdmUseCase_LicenseDuration, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); + UnloadLicense(); + ForbidPlayback(EndOfPlaybackWindow() + kFudge); +} + +// Initial playback exceeds rental duration. +TEST_P(CdmUseCase_LicenseDuration, Case5) { + ForbidPlayback(EndOfRentalWindow() + kFudge); +} + +// This use case is not documented because we don't recommend having a renewal +// with an infinite duration. However, some content providers use this policy, +// so we want to verify that we support it correctly. +class CdmUseCase_InfiniteRenewal : public RenewalTest { + public: + CdmUseCase_InfiniteRenewal() : RenewalTest("CDM_InfiniteRenewal") { + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 50u; + timer_limits_.total_playback_duration_seconds = 0; + } +}; + +// The renewal interval is infinite. We never need to load the renewal. +TEST_P(CdmUseCase_InfiniteRenewal, NoRenewal) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); + SleepUntil(EndOfRentalWindow() + kPlayDuration); + FailDecrypt(); +} + +// If we do load the renewal, the playback window still cuts off playback. +TEST_P(CdmUseCase_InfiniteRenewal, WithRenewal) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); + RequestRenewal(kInfiniteRenewal); + const uint64_t start = start_of_playback_ + 2 * kPlayDuration; + const uint64_t load_time = start + kRoundTripTime; + const uint64_t cutoff = EndOfRentalWindow(); + RenewAndTerminate(start, load_time, cutoff, kInfiniteRenewal); +} + +// If we load the renewal before playback starts, then the rental window still +// cuts off playback. +TEST_P(CdmUseCase_InfiniteRenewal, RenewBeforePlayback) { + LoadLicense(); + RequestRenewal(kInfiniteRenewal); + LoadRenewal(start_of_playback_, kInfiniteRenewal); + AllowPlayback(start_of_playback_, start_of_playback_ + 2 * kPlayDuration); + SleepUntil(EndOfRentalWindow() + kPlayDuration); + FailDecrypt(); +} + +// This use case is not documented because we don't recommend specifying the +// license duration instead of rental or playback duration. However, some +// content providers use this policy, so we want to verify that we support it +// correctly. +class CdmUseCase_LicenseDurationWithRenewal : public RenewalTest { + public: + CdmUseCase_LicenseDurationWithRenewal() + : RenewalTest("CDM_LicenseDurationWithRenewal") { + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 30u; + timer_limits_.total_playback_duration_seconds = 0; + } +}; + +// If we do load the renewal, we may continue playback past original window. +TEST_P(CdmUseCase_LicenseDurationWithRenewal, WithRenewal) { + LoadAndAllowPlayback( + start_of_playback_, + start_of_playback_ + kLicenseDurationWithRenewal.renewal_delay); + // Play through four renewals, which should be past the rental window. + for (int i = 0; i < 4; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kLicenseDurationWithRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kLicenseDurationWithRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kLicenseDurationWithRenewal); + } + // Expect that renewals did let us play past the rental window. + EXPECT_GT(CurrentRentalTime(), EndOfRentalWindow() + kPlayDuration); +} + +/***********************************************************************/ // All duration tests are parameterized by can_persist = true or false. INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_LicenseWithRenewal, ::testing::Values(false, true)); @@ -1629,4 +1807,11 @@ INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_LicenseWithRenewalPlayback, ::testing::Values(false, true)); INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_LimitedDurationLicense, ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_InfiniteRenewal, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_LicenseDuration, + ::testing::Values(false, true)); +INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_LicenseDurationWithRenewal, + ::testing::Values(false, true)); + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp index 95356fd3..717a00bc 100644 --- a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp @@ -716,8 +716,13 @@ TEST_P(HlsConstructionTest, InitData) { if (param.success_) { WidevinePsshData cenc_header; EXPECT_TRUE(cenc_header.ParseFromString(value)); + + CORE_UTIL_IGNORE_DEPRECATED EXPECT_EQ(video_widevine::WidevinePsshData_Algorithm_AESCTR, cenc_header.algorithm()); + EXPECT_EQ(param.provider_, cenc_header.provider()); + CORE_UTIL_RESTORE_WARNINGS + for (size_t i = 0; i < param.key_ids_.size(); ++i) { bool key_id_found = false; if (param.key_ids_[i].size() != 32) continue; @@ -729,7 +734,6 @@ TEST_P(HlsConstructionTest, InitData) { } EXPECT_TRUE(key_id_found); } - EXPECT_EQ(param.provider_, cenc_header.provider()); std::vector param_content_id_vec(Base64Decode(param.content_id_)); EXPECT_EQ( std::string(param_content_id_vec.begin(), param_content_id_vec.end()), @@ -822,6 +826,8 @@ TEST_P(HlsParseTest, Parse) { WidevinePsshData cenc_header; EXPECT_TRUE(cenc_header.ParseFromString(init_data.data())); + + CORE_UTIL_IGNORE_DEPRECATED EXPECT_EQ(video_widevine::WidevinePsshData_Algorithm_AESCTR, cenc_header.algorithm()); if (param.key_.compare(kJsonProvider) == 0) { @@ -831,6 +837,7 @@ TEST_P(HlsParseTest, Parse) { } else if (param.key_.compare(kJsonKeyIds) == 0) { EXPECT_EQ(param.value_, b2a_hex(cenc_header.key_ids(0))); } + CORE_UTIL_RESTORE_WARNINGS EXPECT_EQ(kHlsIvHexValue, b2a_hex(init_data.hls_iv())); } else { diff --git a/libwvdrmengine/cdm/core/test/policy_integration_test.cpp b/libwvdrmengine/cdm/core/test/policy_integration_test.cpp index a198860a..b057f650 100644 --- a/libwvdrmengine/cdm/core/test/policy_integration_test.cpp +++ b/libwvdrmengine/cdm/core/test/policy_integration_test.cpp @@ -19,6 +19,7 @@ #include "license_request.h" #include "log.h" #include "metrics_collections.h" +#include "oec_device_features.h" #include "test_base.h" #include "test_printers.h" #include "test_sleep.h" @@ -115,22 +116,30 @@ class CorePIGTest : public WvCdmTestBaseWithEngine { const std::vector input(buffer_size, 0); std::vector output(buffer_size, 0); const std::vector iv(KEY_IV_SIZE, 0); - Decrypt(session_id, key_id, input, iv, &output, NO_ERROR); + ASSERT_EQ(NO_ERROR, Decrypt(session_id, key_id, input, iv, &output)); } // Try to use the key to decrypt, but expect the key has expired. - void FailDecrypt(const CdmSessionId& session_id, const KeyId& key_id) { + void FailDecrypt(const CdmSessionId& session_id, const KeyId& key_id, + CdmResponseType expected_status) { constexpr size_t buffer_size = 500; const std::vector input(buffer_size, 0); std::vector output(buffer_size, 0); const std::vector iv(KEY_IV_SIZE, 0); - Decrypt(session_id, key_id, input, iv, &output, NEED_KEY); + CdmResponseType status = Decrypt(session_id, key_id, input, iv, &output); + // If the server knows we cannot handle the key, it would not have given us + // the key. In that case, the status should indicate no key. + if (status != NEED_KEY) { + // Otherwise, we should have gotten the expected error. + ASSERT_EQ(expected_status, status); + } } - void Decrypt(const CdmSessionId& session_id, const KeyId& key_id, - const std::vector& input, - const std::vector& iv, std::vector* output, - CdmResponseType expected_status) { + // Decrypt or fail to decrypt, with the expected status. + CdmResponseType Decrypt(const CdmSessionId& session_id, const KeyId& key_id, + const std::vector& input, + const std::vector& iv, + std::vector* output) { CdmDecryptionParametersV16 params(key_id); params.is_secure = false; CdmDecryptionSample sample(input.data(), output->data(), 0, input.size(), @@ -138,9 +147,54 @@ class CorePIGTest : public WvCdmTestBaseWithEngine { CdmDecryptionSubsample subsample(0, input.size()); sample.subsamples.push_back(subsample); params.samples.push_back(sample); + return cdm_engine_.DecryptV16(session_id, params); + } + // Use the key to decrypt to a secure buffer. + void DecryptSecure(const CdmSessionId& session_id, const KeyId& key_id) { + ASSERT_TRUE(wvoec::global_features.test_secure_buffers); + constexpr size_t buffer_size = 500; + const std::vector input(buffer_size, 0); + const std::vector iv(KEY_IV_SIZE, 0); + + // To create a secure buffer, we need to know the OEMCrypto session id. + CdmQueryMap query_map; + cdm_engine_.QueryOemCryptoSessionId(session_id, &query_map); + const std::string oec_session_id_string = + query_map[QUERY_KEY_OEMCRYPTO_SESSION_ID]; + uint32_t oec_session_id = std::stoi(oec_session_id_string); + + int secure_buffer_fid; + OEMCrypto_DestBufferDesc output_descriptor; + output_descriptor.type = OEMCrypto_BufferType_Secure; + output_descriptor.buffer.secure.handle_length = buffer_size; + ASSERT_EQ( + OEMCrypto_AllocateSecureBuffer(oec_session_id, buffer_size, + &output_descriptor, &secure_buffer_fid), + OEMCrypto_SUCCESS); + + ASSERT_NE(output_descriptor.buffer.secure.handle, nullptr); + // It is OK if OEMCrypto changes the maximum size, but there must + // still be enough room for our data. + ASSERT_GE(output_descriptor.buffer.secure.handle_length, buffer_size); + output_descriptor.buffer.secure.offset = 0; + + // Now create a sample array for the CDM layer. + CdmDecryptionParametersV16 params(key_id); + params.is_secure = true; + CdmDecryptionSample sample(input.data(), + output_descriptor.buffer.secure.handle, 0, + input.size(), iv); + CdmDecryptionSubsample subsample(0, input.size()); + sample.subsamples.push_back(subsample); + params.samples.push_back(sample); CdmResponseType status = cdm_engine_.DecryptV16(session_id, params); - ASSERT_EQ(expected_status, status); + + // Free the secure buffer before we check the return status. + OEMCrypto_FreeSecureBuffer(oec_session_id, &output_descriptor, + secure_buffer_fid); + + ASSERT_EQ(status, NO_ERROR); } }; @@ -192,4 +246,36 @@ TEST_F(CorePIGTest, OfflineWithPST) { ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); } +// This test verifies that the system can download and install license with a +// key that requires secure buffers. It also verifies that we cannot decrypt to +// a non-secure buffer using this key, but that we can decrypt to a secure +// buffer, if the test harness supports secure buffers. +TEST_F(CorePIGTest, OfflineHWSecureRequired) { + const std::string content_id = "GTS_HW_SECURE_ALL"; + const KeyId key_id = "0000000000000002"; + + const CdmLicenseType license_type = kLicenseTypeOffline; + CdmSessionId session_id; + ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); + CdmKeyRequest key_request; + ASSERT_NO_FATAL_FAILURE( + GenerateKeyRequest(session_id, content_id, &key_request, license_type)); + std::string key_response; + ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request, &key_response)); + CdmKeySetId key_set_id; + ASSERT_NO_FATAL_FAILURE( + AddKey(session_id, key_response, license_type, &key_set_id)); + // First we try to decrypt to a non-secure buffer and verify that it fails. + // TODO(b/164517875): This error code should be something actionable. + ASSERT_NO_FATAL_FAILURE(FailDecrypt(session_id, key_id, DECRYPT_ERROR)); + // Next, if possible, we try to decrypt to a secure buffer, and verify + // success. + if (wvoec::global_features.test_secure_buffers) { + ASSERT_NO_FATAL_FAILURE(DecryptSecure(session_id, key_id)); + } else { + LOGI("Test harness cannot create secure buffers. test skipped."); + } + ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/test_base.cpp b/libwvdrmengine/cdm/core/test/test_base.cpp index 36957ebc..8f5dc172 100644 --- a/libwvdrmengine/cdm/core/test/test_base.cpp +++ b/libwvdrmengine/cdm/core/test/test_base.cpp @@ -93,6 +93,19 @@ void show_menu(const char* prog_name, const std::string& extra_help_text) { << " in the url" << std::endl << std::endl; + std::cout << " --renewal_server_url=" << std::endl; + std::cout << " configure the renewal server url, please include http[s] " + "in the url" + << std::endl + << " If not set, this defaults to be the same url used by the " + "license server." + << std::endl + << " Some tests, such as LicenseRenewalSpecifiedServer, will " + "ignore this setting. " + << std::endl + << " See comments in the code for an explanation." << std::endl + << std::endl; + std::cout << " --provisioning_server_url=" << std::endl; std::cout << " configure the provisioning server url, please include http[s]" @@ -457,6 +470,8 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[], default_config_.set_provisioning_service_certificate(certificate); } else if (arg_prefix == "--license_server_url") { default_config_.set_license_server(arg_value); + } else if (arg_prefix == "--renewal_server_url") { + default_config_.set_renewal_server(arg_value); } else if (arg_prefix == "--provisioning_server_url") { default_config_.set_provisioning_server(arg_value); } else { @@ -480,6 +495,8 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[], << default_config_.provisioning_server() << std::endl; std::cout << "Default License Server: " << default_config_.license_server() << std::endl; + std::cout << "Default Renewal Server: " << default_config_.renewal_server() + << std::endl; std::cout << "Default KeyID: " << default_config_.key_id() << std::endl << std::endl; diff --git a/libwvdrmengine/cdm/core/test/test_main.cpp b/libwvdrmengine/cdm/core/test/test_main.cpp index 1651a565..edf9e306 100644 --- a/libwvdrmengine/cdm/core/test/test_main.cpp +++ b/libwvdrmengine/cdm/core/test/test_main.cpp @@ -11,7 +11,7 @@ #include "test_base.h" int main(int argc, char** argv) { - if (!wvcdm::WvCdmTestBase::Initialize(argc, argv)) return 0; + if (!wvcdm::WvCdmTestBase::Initialize(argc, argv)) return 1; ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/libwvdrmengine/cdm/util/include/log.h b/libwvdrmengine/cdm/util/include/log.h index 28791f35..c64c67a8 100644 --- a/libwvdrmengine/cdm/util/include/log.h +++ b/libwvdrmengine/cdm/util/include/log.h @@ -78,9 +78,7 @@ struct LoggingUidSetter { // unit tests. CORE_UTIL_EXPORT void InitLogging(); -// Only enable format specifier warnings on LP64 systems. There is -// no easy portable method to handle format specifiers for int64_t. -#if (defined(__GNUC__) || defined(__clang__)) && defined(__LP64__) +#ifdef __GNUC__ [[gnu::format(printf, 5, 6)]] CORE_UTIL_EXPORT void Log(const char* file, const char* function, int line, diff --git a/libwvdrmengine/cdm/util/include/util_common.h b/libwvdrmengine/cdm/util/include/util_common.h index 4477ca1c..5e7b1661 100644 --- a/libwvdrmengine/cdm/util/include/util_common.h +++ b/libwvdrmengine/cdm/util/include/util_common.h @@ -11,12 +11,23 @@ # else # define CORE_UTIL_EXPORT __declspec(dllimport) # endif +# define CORE_UTIL_IGNORE_DEPRECATED +# define CORE_UTIL_RESTORE_WARNINGS #else # ifdef CORE_UTIL_IMPLEMENTATION # define CORE_UTIL_EXPORT __attribute__((visibility("default"))) # else # define CORE_UTIL_EXPORT # endif +# ifdef __GNUC__ +# define CORE_UTIL_IGNORE_DEPRECATED \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +# define CORE_UTIL_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") +# else +# define CORE_UTIL_IGNORE_DEPRECATED +# define CORE_UTIL_RESTORE_WARNINGS +# endif #endif #endif // WVCDM_UTIL_UTIL_COMMON_H_ diff --git a/libwvdrmengine/cdm/util/src/string_conversions.cpp b/libwvdrmengine/cdm/util/src/string_conversions.cpp index cd93dbbd..e40e6f7e 100644 --- a/libwvdrmengine/cdm/util/src/string_conversions.cpp +++ b/libwvdrmengine/cdm/util/src/string_conversions.cpp @@ -254,6 +254,8 @@ std::vector Base64SafeDecode(const std::string& b64_input) { std::string HexEncode(const uint8_t* in_buffer, unsigned int size) { static const char kHexChars[] = "0123456789ABCDEF"; if (size == 0) return ""; + constexpr unsigned int kMaxSafeSize = 2048; + if (size > kMaxSafeSize) size = kMaxSafeSize; // Each input byte creates two output hex characters. std::string out_buffer(size * 2, '\0'); diff --git a/libwvdrmengine/cdm/util/test/test_sleep.cpp b/libwvdrmengine/cdm/util/test/test_sleep.cpp index f3f219c8..3557fe95 100644 --- a/libwvdrmengine/cdm/util/test/test_sleep.cpp +++ b/libwvdrmengine/cdm/util/test/test_sleep.cpp @@ -9,6 +9,9 @@ #else # include #endif +#ifdef __APPLE__ +# include +#endif #include #include @@ -73,10 +76,13 @@ bool TestSleep::RollbackSystemTime(int seconds) { file_time.dwHighDateTime = long_time >> 32; if (!FileTimeToSystemTime(&file_time, &time)) return false; if (!SetSystemTime(&time)) return false; +#elif TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE + LOGE("iOS time rollback: cannot set system time."); + return false; #else auto time = std::chrono::system_clock::now(); auto modified_time = time - std::chrono::seconds(seconds); - ; + timespec time_spec; time_spec.tv_sec = std::chrono::duration_cast( modified_time.time_since_epoch()) @@ -140,6 +146,9 @@ bool TestSleep::CanChangeSystemTime() { } LOGE("Win32 time rollback: cannot set system time."); return false; +#elif TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE + LOGE("iOS time rollback: cannot set system time."); + return false; #else // Otherwise, the test needs to be run as root. const uid_t uid = getuid(); diff --git a/libwvdrmengine/oemcrypto/ref/test/oemcrypto_rsa_key_unittest.cpp b/libwvdrmengine/oemcrypto/ref/test/oemcrypto_rsa_key_unittest.cpp new file mode 100644 index 00000000..021a6e51 --- /dev/null +++ b/libwvdrmengine/oemcrypto/ref/test/oemcrypto_rsa_key_unittest.cpp @@ -0,0 +1,406 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation of OEMCrypto APIs +// +#include + +#include + +#include + +#include "OEMCryptoCENC.h" +#include "log.h" +#include "oemcrypto_ref_test_utils.h" +#include "oemcrypto_rsa_key.h" + +namespace wvoec_ref { +constexpr size_t kMessageSize = 4 * 1024; // 4 kB +constexpr size_t kCastMessageSize = 83; // Special max size. + +class OEMCryptoRsaKeyTest : public ::testing::TestWithParam { + public: + void SetUp() override { + // RSA key generation is slow (~2 seconds) compared to the + // operations they perform (<50 ms). Each key type is generated + // once and globally stored in serialized form. + // Caching the instance may result in test failures for + // memory-leak detection. + const RsaFieldSize field_size = GetParam(); + std::lock_guard rsa_key_lock(rsa_key_mutex_); + // Use of a switch case is intentional to cause compiler warnings + // if a new field size is introduced without updating the test. + switch (field_size) { + case kRsa2048Bit: { + if (!rsa_2048_key_data_.empty()) { + key_ = RsaPrivateKey::Load(rsa_2048_key_data_); + } + if (!key_) { + key_ = RsaPrivateKey::New(kRsa2048Bit); + } + if (rsa_2048_key_data_.empty() && key_) { + rsa_2048_key_data_ = key_->Serialize(); + } + } break; + case kRsa3072Bit: { + if (!rsa_3072_key_data_.empty()) { + key_ = RsaPrivateKey::Load(rsa_3072_key_data_); + } + if (!key_) { + key_ = RsaPrivateKey::New(kRsa3072Bit); + } + if (rsa_3072_key_data_.empty() && key_) { + rsa_3072_key_data_ = key_->Serialize(); + } + } break; + case kRsaFieldUnknown: // Suppress compiler warnings + LOGE("RSA test was incorrectly instantiation"); + exit(EXIT_FAILURE); + break; + } + } + + void TearDown() override { key_.reset(); } + + protected: + std::unique_ptr key_; + static std::mutex rsa_key_mutex_; + static std::vector rsa_2048_key_data_; + static std::vector rsa_3072_key_data_; +}; + +std::mutex OEMCryptoRsaKeyTest::rsa_key_mutex_; +std::vector OEMCryptoRsaKeyTest::rsa_2048_key_data_; +std::vector OEMCryptoRsaKeyTest::rsa_3072_key_data_; + +// Basic verification of RSA private key generation. +TEST_P(OEMCryptoRsaKeyTest, KeyProperties) { + ASSERT_TRUE(key_); + const RsaFieldSize expected_field_size = GetParam(); + + EXPECT_EQ(key_->field_size(), expected_field_size); + EXPECT_NE(nullptr, key_->GetRsaKey()); +} + +// Checks that the private key serialization APIs are compatible +// and performing in a manner that is similar to other OEMCrypto methods +// that retrieve data. +TEST_P(OEMCryptoRsaKeyTest, SerializePrivateKey) { + ASSERT_TRUE(key_); + + constexpr size_t kInitialBufferSize = 10; // Definitely too small. + size_t buffer_size = kInitialBufferSize; + std::vector buffer(buffer_size); + + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + key_->Serialize(buffer.data(), &buffer_size)); + EXPECT_GT(buffer_size, kInitialBufferSize); + + buffer.resize(buffer_size); + EXPECT_EQ(OEMCrypto_SUCCESS, key_->Serialize(buffer.data(), &buffer_size)); + buffer.resize(buffer_size); + + const std::vector direct_key_data = key_->Serialize(); + EXPECT_FALSE(direct_key_data.empty()); + ASSERT_EQ(buffer.size(), direct_key_data.size()); + for (size_t i = 0; i < buffer.size(); i++) { + ASSERT_EQ(buffer[i], direct_key_data[i]) << "i = " << std::to_string(i); + } +} + +// Checks that a private key that is serialized can be deserialized and +// reload. Also checks that the serialization of a key produces the +// same data to ensure consistency. +TEST_P(OEMCryptoRsaKeyTest, SerializeAndReloadPrivateKey) { + ASSERT_TRUE(key_); + + const std::vector key_data = key_->Serialize(); + std::unique_ptr loaded_key = RsaPrivateKey::Load(key_data); + ASSERT_TRUE(loaded_key); + + EXPECT_EQ(key_->field_size(), loaded_key->field_size()); + + const std::vector loaded_key_data = loaded_key->Serialize(); + ASSERT_EQ(key_data.size(), loaded_key_data.size()); + for (size_t i = 0; i < key_data.size(); i++) { + ASSERT_EQ(key_data[i], loaded_key_data[i]) << "i = " << std::to_string(i); + } +} + +// Checks that a private key with explicitly indicated schemes include +// the scheme fields in the reserialized key. +TEST_P(OEMCryptoRsaKeyTest, SerializeAndReloadPrivateKeyWithAllowedSchemes) { + ASSERT_TRUE(key_); + + const std::vector raw_key_data = key_->Serialize(); + std::vector key_data = {'S', 'I', 'G', 'N', 0x00, 0x00, 0x00, 0x03}; + key_data.insert(key_data.end(), raw_key_data.begin(), raw_key_data.end()); + + std::unique_ptr explicit_key = RsaPrivateKey::Load(key_data); + ASSERT_TRUE(explicit_key); + EXPECT_EQ(key_->field_size(), explicit_key->field_size()); + EXPECT_EQ(explicit_key->allowed_schemes(), 0x03); + + const std::vector explicit_key_data = explicit_key->Serialize(); + ASSERT_EQ(key_data.size(), explicit_key_data.size()); + ASSERT_EQ(key_data, explicit_key_data); +} + +// Checks that a public key can be created from the private key. +TEST_P(OEMCryptoRsaKeyTest, DerivePublicKey) { + ASSERT_TRUE(key_); + + std::unique_ptr pub_key = key_->MakePublicKey(); + ASSERT_TRUE(pub_key); + + EXPECT_TRUE(key_->IsMatchingPublicKey(*pub_key)); +} + +// Checks that the public key serialization APIs are compatible +// and performing in a manner that is similar to other OEMCrypto methods +// that retrieve data. +TEST_P(OEMCryptoRsaKeyTest, SerializePublicKey) { + ASSERT_TRUE(key_); + + std::unique_ptr pub_key = key_->MakePublicKey(); + ASSERT_TRUE(pub_key); + + constexpr size_t kInitialBufferSize = 10; // Definitely too small. + size_t buffer_size = kInitialBufferSize; + std::vector buffer(buffer_size); + + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + pub_key->Serialize(buffer.data(), &buffer_size)); + EXPECT_GT(buffer_size, kInitialBufferSize); + + buffer.resize(buffer_size); + EXPECT_EQ(OEMCrypto_SUCCESS, pub_key->Serialize(buffer.data(), &buffer_size)); + buffer.resize(buffer_size); + + const std::vector direct_key_data = pub_key->Serialize(); + EXPECT_FALSE(direct_key_data.empty()); + ASSERT_EQ(buffer.size(), direct_key_data.size()); + for (size_t i = 0; i < buffer.size(); i++) { + ASSERT_EQ(buffer[i], direct_key_data[i]) << "i = " << std::to_string(i); + } +} + +// Checks that a public key that is serialized can be deserialized and +// reload. Also checks that the serialization of a key produces the +// same data to ensure consistency. +TEST_P(OEMCryptoRsaKeyTest, SerializeAndReloadPublicKey) { + ASSERT_TRUE(key_); + + std::unique_ptr pub_key = key_->MakePublicKey(); + ASSERT_TRUE(pub_key); + + const std::vector key_data = pub_key->Serialize(); + + std::unique_ptr loaded_key = RsaPublicKey::Load(key_data); + ASSERT_TRUE(loaded_key); + + EXPECT_EQ(pub_key->field_size(), loaded_key->field_size()); + EXPECT_EQ(pub_key->allowed_schemes(), loaded_key->allowed_schemes()); + + const std::vector loaded_key_data = loaded_key->Serialize(); + ASSERT_EQ(key_data.size(), loaded_key_data.size()); + for (size_t i = 0; i < key_data.size(); i++) { + ASSERT_EQ(key_data[i], loaded_key_data[i]) << "i = " << std::to_string(i); + } +} + +// Checks that the RSA signature generating API operates similar to +// existing signature generation functions. +TEST_P(OEMCryptoRsaKeyTest, GenerateSignature) { + ASSERT_TRUE(key_); + + const std::vector message = RandomData(kMessageSize); + ASSERT_FALSE(message.empty()) << "CdmRandom failed"; + + constexpr size_t kInitialBufferSize = 10; // Definitely too small. + size_t signature_size = kInitialBufferSize; + std::vector signature(signature_size); + EXPECT_EQ( + OEMCrypto_ERROR_SHORT_BUFFER, + key_->GenerateSignature(message.data(), message.size(), kRsaPssDefault, + signature.data(), &signature_size)); + EXPECT_GT(signature_size, kInitialBufferSize); + + signature.resize(signature_size); + EXPECT_EQ( + OEMCrypto_SUCCESS, + key_->GenerateSignature(message.data(), message.size(), kRsaPssDefault, + signature.data(), &signature_size)); + signature.resize(signature_size); + + EXPECT_LE(signature_size, key_->SignatureSize()); +} + +// Checks that RSA signatures can be verified by an RSA public key. +TEST_P(OEMCryptoRsaKeyTest, VerifySignature) { + ASSERT_TRUE(key_); + const std::vector message = RandomData(kMessageSize); + ASSERT_FALSE(message.empty()) << "CdmRandom failed"; + + const std::vector signature = key_->GenerateSignature(message); + + std::unique_ptr pub_key = key_->MakePublicKey(); + ASSERT_TRUE(pub_key); + + EXPECT_EQ(OEMCrypto_SUCCESS, pub_key->VerifySignature(message, signature)); + + // Check with different message. + const std::vector message_two = RandomData(kMessageSize); + EXPECT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + pub_key->VerifySignature(message_two, signature)); + + // Check with bad signature. + const std::vector bad_signature = RandomData(signature.size()); + EXPECT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + pub_key->VerifySignature(message, bad_signature)); +} + +// Checks that the special CAST receiver signature scheme works +// to the degree that it is possible to test. +TEST_P(OEMCryptoRsaKeyTest, GenerateAndVerifyRsaSignature) { + ASSERT_TRUE(key_); + // Key must be enabled for PKCS1 Block 1 padding scheme. + // To do so, the key is serialized and the padding scheme is + // added to the key data. + const std::vector key_data = key_->Serialize(); + ASSERT_FALSE(key_data.empty()); + std::vector pkcs_enabled_key_data = { + 'S', 'I', 'G', 'N', 0x00, 0x00, 0x00, kSign_PKCS1_Block1}; + pkcs_enabled_key_data.insert(pkcs_enabled_key_data.end(), key_data.begin(), + key_data.end()); + std::unique_ptr pkcs_enabled_key = + RsaPrivateKey::Load(pkcs_enabled_key_data); + ASSERT_TRUE(pkcs_enabled_key); + + // The actual cast message is a domain specific hash of the message, + // however, random data works for testing purposes. + const std::vector message = RandomData(kCastMessageSize); + + // Generate signature. + constexpr size_t kInitialBufferSize = 10; // Definitely too small. + size_t signature_size = kInitialBufferSize; + std::vector signature(signature_size); + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + pkcs_enabled_key->GenerateSignature(message.data(), message.size(), + kRsaPkcs1Cast, signature.data(), + &signature_size)); + EXPECT_GT(signature_size, kInitialBufferSize); + signature.resize(signature_size); + EXPECT_EQ(OEMCrypto_SUCCESS, + pkcs_enabled_key->GenerateSignature(message.data(), message.size(), + kRsaPkcs1Cast, signature.data(), + &signature_size)); + signature.resize(signature_size); + + EXPECT_LE(signature_size, pkcs_enabled_key->SignatureSize()); + + // Verify signature. + std::unique_ptr pub_key = pkcs_enabled_key->MakePublicKey(); + ASSERT_TRUE(pub_key); + + EXPECT_EQ(OEMCrypto_SUCCESS, + pub_key->VerifySignature(message, signature, kRsaPkcs1Cast)); +} + +// Verifies the session key exchange protocol used by the licensing +// server. +TEST_P(OEMCryptoRsaKeyTest, ShareSessionKey) { + constexpr size_t kSessionKeySize = 16; + ASSERT_TRUE(key_); + std::unique_ptr public_key = key_->MakePublicKey(); + ASSERT_TRUE(public_key); + + // Generate session key. + const std::vector session_key = RandomData(kSessionKeySize); + ASSERT_FALSE(session_key.empty()); + + // Server's perspective. + constexpr size_t kInitialBufferSize = 10; // Definitely too small. + size_t enc_session_key_size = kInitialBufferSize; + std::vector enc_session_key(enc_session_key_size); + OEMCryptoResult result = public_key->EncryptSessionKey( + session_key.data(), session_key.size(), enc_session_key.data(), + &enc_session_key_size); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, result); + enc_session_key.resize(enc_session_key_size); + result = public_key->EncryptSessionKey(session_key.data(), session_key.size(), + enc_session_key.data(), + &enc_session_key_size); + ASSERT_EQ(OEMCrypto_SUCCESS, result); + enc_session_key.resize(enc_session_key_size); + + // Client's perspective. + size_t received_session_key_size = kInitialBufferSize; + std::vector received_session_key(received_session_key_size); + result = key_->DecryptSessionKey( + enc_session_key.data(), enc_session_key.size(), + received_session_key.data(), &received_session_key_size); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, result); + received_session_key.resize(received_session_key_size); + result = key_->DecryptSessionKey( + enc_session_key.data(), enc_session_key.size(), + received_session_key.data(), &received_session_key_size); + ASSERT_EQ(OEMCrypto_SUCCESS, result); + received_session_key.resize(received_session_key_size); + + // Compare keys. + ASSERT_EQ(session_key.size(), received_session_key.size()); + ASSERT_EQ(session_key, received_session_key); +} + +// Verifies the encryption key exchange protocol used by the licensing +// server. +TEST_P(OEMCryptoRsaKeyTest, ShareEncryptionKey) { + constexpr size_t kEncryptionKeySize = 16; + ASSERT_TRUE(key_); + std::unique_ptr public_key = key_->MakePublicKey(); + ASSERT_TRUE(public_key); + + // Generate session key. + const std::vector encryption_key = RandomData(kEncryptionKeySize); + ASSERT_FALSE(encryption_key.empty()); + + // Server's perspective. + constexpr size_t kInitialBufferSize = 10; // Definitely too small. + size_t enc_encryption_key_size = kInitialBufferSize; + std::vector enc_encryption_key(enc_encryption_key_size); + OEMCryptoResult result = public_key->EncryptEncryptionKey( + encryption_key.data(), encryption_key.size(), enc_encryption_key.data(), + &enc_encryption_key_size); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, result); + enc_encryption_key.resize(enc_encryption_key_size); + result = public_key->EncryptEncryptionKey( + encryption_key.data(), encryption_key.size(), enc_encryption_key.data(), + &enc_encryption_key_size); + ASSERT_EQ(OEMCrypto_SUCCESS, result); + enc_encryption_key.resize(enc_encryption_key_size); + + // Client's perspective. + size_t received_encryption_key_size = kInitialBufferSize; + std::vector received_encryption_key(received_encryption_key_size); + result = key_->DecryptEncryptionKey( + enc_encryption_key.data(), enc_encryption_key.size(), + received_encryption_key.data(), &received_encryption_key_size); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, result); + received_encryption_key.resize(received_encryption_key_size); + result = key_->DecryptEncryptionKey( + enc_encryption_key.data(), enc_encryption_key.size(), + received_encryption_key.data(), &received_encryption_key_size); + ASSERT_EQ(OEMCrypto_SUCCESS, result); + received_encryption_key.resize(received_encryption_key_size); + + // Compare keys. + ASSERT_EQ(encryption_key.size(), received_encryption_key.size()); + ASSERT_EQ(encryption_key, received_encryption_key); +} + +INSTANTIATE_TEST_CASE_P(AllFieldSizes, OEMCryptoRsaKeyTest, + ::testing::Values(kRsa2048Bit, kRsa3072Bit)); + +} // namespace wvoec_ref diff --git a/libwvdrmengine/run_all_unit_tests.sh b/libwvdrmengine/run_all_unit_tests.sh index 263937d2..4ce288ab 100755 --- a/libwvdrmengine/run_all_unit_tests.sh +++ b/libwvdrmengine/run_all_unit_tests.sh @@ -106,8 +106,8 @@ adb_shell_run request_license_test $PROVISIONING_ARG # Additional tests adb_shell_run base64_test adb_shell_run buffer_reader_test -adb_shell_run cdm_engine_test adb_shell_run cdm_engine_metrics_decorator_unittest +adb_shell_run cdm_engine_test adb_shell_run cdm_session_unittest adb_shell_run certificate_provisioning_unittest adb_shell_run counter_metric_unittest