From 24e4c33262d3e7e3b338434387166ab40bf846e6 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Sun, 7 Mar 2021 00:03:17 -0800 Subject: [PATCH 1/9] Fix deprecated and printf warnings. [ Merge of http://go/wvgerrit/118703 ] Bug: 182058081 Test: WV unit/integration tests Change-Id: I2d8995b8aab864a2d2f5161d12a473d34e67bad4 --- libwvdrmengine/cdm/core/src/crypto_session.cpp | 7 ++++--- libwvdrmengine/cdm/core/src/initialization_data.cpp | 9 ++++++--- .../cdm/core/test/initialization_data_unittest.cpp | 9 ++++++++- libwvdrmengine/cdm/util/include/log.h | 4 +--- libwvdrmengine/cdm/util/include/util_common.h | 11 +++++++++++ 5 files changed, 30 insertions(+), 10 deletions(-) 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/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/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_ From 50efa73e3468f0a2be07e0526edab13496c39f74 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Sun, 7 Mar 2021 00:16:20 -0800 Subject: [PATCH 2/9] Set renewal server on command line for tests [ Merge of http://go/wvgerrit/110903 ] This CL adds the ability to set the renewal server on the command line, and adds some comments to the build scripts' README file to explain how to test a server rollout. Bug: 173031207 Test: WV unit/integration tests Change-Id: Ibe71e77469c94601627fb85a1ad4654553d3eb1a Change-Id: I7e9bfc873c78e26c0cece113dc8a3d08cd9163db --- .../cdm/core/test/cdm_engine_test.cpp | 12 +++++++----- .../cdm/core/test/config_test_env.cpp | 14 +------------- .../cdm/core/test/config_test_env.h | 19 ++++++++++++++++--- libwvdrmengine/cdm/core/test/test_base.cpp | 17 +++++++++++++++++ 4 files changed, 41 insertions(+), 21 deletions(-) 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/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; From 616a9b38dc1f49b1bc53d44a3dc7b9c986d711b7 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Sun, 7 Mar 2021 00:20:59 -0800 Subject: [PATCH 3/9] Return error when test initialization fails [ Merge of http://go/wvgerrit/116243 ] Currently if a command line argument is not understood, all tests are skipped and the test suite passes. Bug: 182058081 Test: WV unit/integration tests Change-Id: I9725a9ed9446f15f08372e68c7a25dffd99c7cef --- libwvdrmengine/cdm/core/test/test_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(); } From 9a659e31c147bbc5563bf9fe8fd61b3e03ab6412 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Sun, 7 Mar 2021 22:13:47 -0800 Subject: [PATCH 4/9] Restrict a2b_hex to 2000 bytes. [ Merge of http://go/wvgerrit/109144 ] Because it doesn't help anybody when a buffer overflow test chokes the logger. Bug: 182058081 Test: Ran unit tests with verbose logging Change-Id: Ibcb3379b9eb9bdd94a8959b977e8de32ea116859 --- libwvdrmengine/cdm/util/src/string_conversions.cpp | 2 ++ 1 file changed, 2 insertions(+) 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'); From a9f051faac471e306b467692e9baecb3232b21ee Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Sun, 7 Mar 2021 22:21:50 -0800 Subject: [PATCH 5/9] Fix test sleep on iOS. [ Merge of http://go/wvgerrit/117203 ] clock_settime isn't available on iOS (even though settimeofday is). But we can't change the system time on iOS anyway, so this just disallows iOS. Bug: 182058081 Test: WV unit/integration tests Change-Id: I96e5b6634803bd4e6aaf5cc6d64f4441296247d4 --- libwvdrmengine/cdm/util/test/test_sleep.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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(); From 0831e575baa9269d612bbb01036c451a4d72e053 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Sun, 7 Mar 2021 22:37:12 -0800 Subject: [PATCH 6/9] Add Duration Use Case integration tests Cherry pick from http://go/wvgerrit/102986, rvc-dev branch of http://go/wvgerrit/105825, rvc-widevine-release of http://go/ag/12561661 Most of this CL was merged in http://go/ag/12967146 except this correction of ordering in test listing. Test: Ran the tests against v16 OEMCrypto. Some fail against v15. Bug: 161463952 Change-Id: I3fa803a645c745dfce42ad15b5ceec9f28aab630 --- libwvdrmengine/run_all_unit_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 3176f5f66f4670e1e048402c57d84ad64b860566 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Mon, 8 Mar 2021 02:46:10 -0800 Subject: [PATCH 7/9] An integration test for secure buffers This is a squash of several different CLs with changes only affecting policy_integration_test.cpp * An integration test for secure buffers [ Merge of http://go/wvgerrit/113905 ] This extends the previous CL that loads a license that has a key that requires a secure buffer. It now creates a secure buffer and tries to decrypt to it. Bug: 38004627 * Test loading license requiring secure buffer [ Merge of http://go/wvgerrit/113903 ] This adds a policy test to verify we can load a license that requires hardware secure buffers. Bug: 38004627 Test: WV unit/integration tests Change-Id: I1cc0b607ddf5b43fc6b7ba648f3c78d6163e14e9 --- .../cdm/core/test/policy_integration_test.cpp | 102 ++++++++++++++++-- 1 file changed, 94 insertions(+), 8 deletions(-) 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 From 9ab837c78a35ab85fc394c0ddd331646725d4123 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Mon, 8 Mar 2021 10:22:00 -0800 Subject: [PATCH 8/9] Integration test for license duration with renewal This is a squash of several different CLs with chnages only affecting duration_use_case_test.cpp * Integration test for license duration with renewal [ Merge of http://go/wvgerrit/117263 ] Bug: 180067457 * Add test for infinite renewal [ Merge of http://go/wvgerrit/107743 ] This adds a test that verifies an infinite renewal is processed correctly. Bug: 162516965 Bug: 170355696 Bug: 169213621 Bug: 166728158 * Add more time to CdmUseCase_Streaming test [ Merge of http://go/wvgerrit/114146 and http://go/wvgerrit/114147 ] The duration tests CdmUseCase_Streaming.Case3 was flaky on the buildbot for platforms with a real clock because there was only room for 1 second of fudge at the end of playback -- i.e. the rental window ended at 35s, but the last playback was 34s. Bug: 175741647 * Set renewal server on command line for tests [ Merge of http://go/wvgerrit/110903 ] This CL adds the ability to set the renewal server on the command line, and adds some comments to the build scripts' README file to explain how to test a server rollout. Bug: 173031207 * Change duration test fudge from 1 to 2 [ Merge of http://go/wvgerrit/112143 ] Some duration tests are flakey. Let's see if this cleans them up enough. Bug: 175741647 * Correct some grammar [ Merged from http://go/wvgerrit/111824 and http://go/wvgerrit/112063 ] * Add license duration test [ Merge of http://go/wvgerrit/109143 ] This adds a license duration test that behaves the same as a rental duration test. We do not encourage content providers to do this, but it is reasonable that legacy licenses should work. Bug: 172099147 * Shorten duration tests [ Merge of http://go/wvgerrit/108664 ] This CL tweaks some of the times in the duration use case tests so that they take less time to run. These changes the CdmUseCase test time from six and half hours to 3 and a quarter. A 50% savings! Bug: 170746277 * Improve logging and edge cases in duration tests [ Merge of http://go/wvgerrit/108663 ] This cleans up some logging, and handles some edge cases on renewals when the renewal request round trip overlaps the cutoff time. Bug: 170746277 * Remove extra cutoff computations [ Merge of http://go/wvgerrit/106783 ] The duration tests originally tried to keep track of when the timer would have gone off if the test was allowed to continue. This proved impracticle, so the extra parameter has been removed. The tests still closely match the documented use cases. Bug: 169453960 * But not too lenient [ Merge of http://go/wvgerrit/107943 ] Previously, the duration tests were modified to allow playback to continue in some cases. See the documentation or code for a list of these cases. However, the tests had been modified to force playback to continue in these cases. This is not desired: in some cases, v15 devices can restrict playback as requested. This CL changes the tests so that playback restriction is allowed. In other words, we no longer force older devices to fail the test. Bug: 169255315 * Make some integration tests lenient [ Merge of http://go/wvgerrit/106843 ] This allows devices that have OEMCrypto version < v16 or do not support usage tables to continue playback for an offline license after the playback window has expired. Bug: 169582310 Test: duration_use_case_test.cpp * Add Renewal Use Case tests [ Merge of http://go/wvgerrit/105826 and http://go/wvgerrit/103784 ] This CL adds several integration tests that match the duration use cases with renewals. The test classes are designed for the core cdm, but the test cases match those found in oemcrypto/odk/test/odk_timer_test.cpp. Test: tests pass except for documented bugs. Bug: 161463952 Change-Id: Ib4775d48490cf150b89aeb2cc64e01a1428f0ab5 --- .../cdm/core/test/duration_use_case_test.cpp | 841 +++++++++++------- 1 file changed, 513 insertions(+), 328 deletions(-) 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 From 5ad2bd7ba334778253cdda5fb7412012fbbc0f13 Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Thu, 18 Feb 2021 14:33:15 -0800 Subject: [PATCH 9/9] Added unittests for reference RSA implementation. [ Merge of http://go/wvgerrit/115546 ] Included a set of unittests for RSA keys which ensure client-server RSA operations work as expected. Bug: 135283522 Test: oemcrypto_unittests Change-Id: I8363a82403d0780f3074a05c64c804e700c2b779 --- .../ref/test/oemcrypto_rsa_key_unittest.cpp | 406 ++++++++++++++++++ 1 file changed, 406 insertions(+) create mode 100644 libwvdrmengine/oemcrypto/ref/test/oemcrypto_rsa_key_unittest.cpp 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