From 4819a26bd4ff7d9736f13e1b69611139f4a5d183 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Thu, 7 Aug 2014 10:45:11 -0700 Subject: [PATCH] Fixes for query information and usage reporting * The Usage APIs return usage reports from either L1 or L3 (if available). * Correction to when usage reports are saved. In addition to other events they are now saved when keys are loaded, usage reports are released and soon after first decryption and periodically (60 seconds) after that, if decryption takes place. * Usage reports now get deleted on an unprovision request. * Policy timer is now started when offline licenses are restored. * Usage session is now released, when a usage response is received. * Usage tests ahev been enabled. * Added CDM extended duration (integration) tests to test usage reporting and querying. These need to be run manually as they take a while (currently half an hour). b/15592374 [ Merge of https://widevine-internal-review.googlesource.com/#/c/10800 from the Widevine CDM repo ] Change-Id: Ia817e03ebbe880e08ba7b4a235ecb82b3ff35fbf --- .../build_and_run_all_unit_tests.sh | 1 + libwvdrmengine/cdm/core/include/cdm_engine.h | 6 +- libwvdrmengine/cdm/core/include/cdm_session.h | 11 +- .../cdm/core/include/crypto_session.h | 2 + .../cdm/core/include/device_files.h | 2 +- libwvdrmengine/cdm/core/include/license.h | 4 +- .../cdm/core/include/string_conversions.h | 1 + libwvdrmengine/cdm/core/src/cdm_engine.cpp | 115 +- libwvdrmengine/cdm/core/src/cdm_session.cpp | 20 +- .../cdm/core/src/crypto_session.cpp | 76 +- libwvdrmengine/cdm/core/src/license.cpp | 34 +- .../cdm/core/src/string_conversions.cpp | 25 +- .../cdm/core/test/cdm_session_unittest.cpp | 240 ++++ .../cdm/core/test/config_test_env.cpp | 2 +- .../cdm/src/wv_content_decryption_module.cpp | 5 +- libwvdrmengine/cdm/test/Android.mk | 4 + .../cdm/test/cdm_extended_duration_test.cpp | 1278 +++++++++++++++++ .../cdm/test/request_license_test.cpp | 60 +- 18 files changed, 1824 insertions(+), 62 deletions(-) create mode 100644 libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp create mode 100644 libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp diff --git a/libwvdrmengine/build_and_run_all_unit_tests.sh b/libwvdrmengine/build_and_run_all_unit_tests.sh index 0ee23c09..ec278f10 100755 --- a/libwvdrmengine/build_and_run_all_unit_tests.sh +++ b/libwvdrmengine/build_and_run_all_unit_tests.sh @@ -49,6 +49,7 @@ adb root && adb wait-for-device remount adb push $OUT/system/bin/oemcrypto_test /system/bin adb push $OUT/system/bin/request_license_test /system/bin +adb push $OUT/system/bin/cdm_extended_duration_test /system/bin adb push $OUT/system/bin/policy_engine_unittest /system/bin adb push $OUT/system/bin/libwvdrmmediacrypto_test /system/bin adb push $OUT/system/bin/libwvdrmdrmplugin_test /system/bin diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index 6d084716..91db0f50 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -17,6 +17,7 @@ namespace wvcdm { class CdmClientPropertySet; class CryptoEngine; +class UsagePropertySet; class WvCdmEventListener; typedef std::map CdmSessionMap; @@ -117,6 +118,8 @@ class CdmEngine { private: // private methods bool ValidateKeySystem(const CdmKeySystem& key_system); + CdmResponseType GetUsageInfo(SecurityLevel requested_security_level, + CdmUsageInfo* usage_info); void OnKeyReleaseEvent(const CdmKeySetId& key_set_id); @@ -132,7 +135,8 @@ class CdmEngine { // usage related variables scoped_ptr usage_session_; - int64_t last_usage_information_update_time; + scoped_ptr usage_property_set_; + int64_t last_usage_information_update_time_; CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine); }; diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index 9aee1060..2fb203f1 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -88,8 +88,10 @@ class CdmSession { virtual CdmResponseType UpdateUsageInformation(); + virtual bool is_initial_usage_update() { return is_initial_usage_update_; } virtual bool is_usage_update_needed() { return is_usage_update_needed_; } - virtual void reset_is_usage_update_needed() { + virtual void reset_usage_flags() { + is_initial_usage_update_ = false; is_usage_update_needed_ = false; } @@ -117,10 +119,13 @@ class CdmSession { bool license_received_; bool is_offline_; bool is_release_; - bool is_usage_update_needed_; + CdmSecurityLevel security_level_; + + // decryption and usage flags bool is_initial_decryption_; bool has_decrypted_recently_; - CdmSecurityLevel security_level_; + bool is_initial_usage_update_; + bool is_usage_update_needed_; // information useful for offline and usage scenarios CdmKeyMessage key_request_; diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index e2f674d1..b67890fa 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -84,6 +84,7 @@ class CryptoSession { // Media data path virtual CdmResponseType Decrypt(const CdmDecryptionParameters& parameters); + // Usage related methods virtual bool UsageInformationSupport(bool* has_support); virtual CdmResponseType UpdateUsageInformation(); virtual CdmResponseType DeactivateUsageInformation( @@ -95,6 +96,7 @@ class CryptoSession { virtual CdmResponseType ReleaseUsageInformation( const std::string& message, const std::string& signature, const std::string& provider_session_token); + virtual CdmResponseType DeleteAllUsageReports(); virtual bool GetHdcpCapabilities(OemCryptoHdcpVersion* current, OemCryptoHdcpVersion* max); diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h index 898cdbd6..8e53fee5 100644 --- a/libwvdrmengine/cdm/core/include/device_files.h +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -97,7 +97,7 @@ class DeviceFiles { FRIEND_TEST(DeviceFilesUsageInfoTest, Store); FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest); FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test); - FRIEND_TEST(WvCdmUsageInfoTest, DISABLED_UsageInfo); + FRIEND_TEST(WvCdmUsageInfoTest, UsageInfo); #endif scoped_ptr file_; diff --git a/libwvdrmengine/cdm/core/include/license.h b/libwvdrmengine/cdm/core/include/license.h index 01013a64..ac4c502b 100644 --- a/libwvdrmengine/cdm/core/include/license.h +++ b/libwvdrmengine/cdm/core/include/license.h @@ -47,8 +47,8 @@ class CdmLicense { const CdmKeyResponse& license_renewal_response, int64_t playback_start_time, int64_t last_playback_time); - virtual bool RestoreUsageLicense(const CdmKeyMessage& license_request, - const CdmKeyResponse& license_response); + virtual bool RestoreLicenseForRelease(const CdmKeyMessage& license_request, + const CdmKeyResponse& license_response); virtual bool HasInitData() { return !stored_init_data_.empty(); } virtual bool IsKeyLoaded(const KeyId& key_id); diff --git a/libwvdrmengine/cdm/core/include/string_conversions.h b/libwvdrmengine/cdm/core/include/string_conversions.h index 3d3ba38f..d0c7ea2f 100644 --- a/libwvdrmengine/cdm/core/include/string_conversions.h +++ b/libwvdrmengine/cdm/core/include/string_conversions.h @@ -22,6 +22,7 @@ std::string HexEncode(const uint8_t* bytes, unsigned size); std::string IntToString(int value); std::string UintToString(unsigned int value); int64_t htonll64(int64_t x); +int64_t ntohll64(int64_t x); }; // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index bde19b07..94051bb7 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -25,13 +25,36 @@ namespace { namespace wvcdm { +class UsagePropertySet : public CdmClientPropertySet { + public: + UsagePropertySet() {} + virtual ~UsagePropertySet() {} + void set_security_level(SecurityLevel security_level) { + if (kLevel3 == security_level) + security_level_ = QUERY_VALUE_SECURITY_LEVEL_L3; + else + security_level_.clear(); + } + virtual const std::string& security_level() const { return security_level_; } + virtual bool use_privacy_mode() const { return false; } + virtual const std::string& service_certificate() const { return empty_; } + virtual bool is_session_sharing_enabled() const { return false; } + virtual uint32_t session_sharing_id() const { return 0; } + virtual void set_session_sharing_id(uint32_t id) { + id; // noop to suppress warning + } + private: + std::string security_level_; + const std::string empty_; +}; + bool CdmEngine::seeded_ = false; CdmEngine::CdmEngine() : cert_provisioning_(NULL), cert_provisioning_requested_security_level_(kLevelDefault), usage_session_(NULL), - last_usage_information_update_time(0) { + last_usage_information_update_time_(0) { Properties::Init(); if (!seeded_) { Clock clock; @@ -269,7 +292,7 @@ CdmResponseType CdmEngine::RestoreKey( LOGI("CdmEngine::RestoreKey"); if (key_set_id.empty()) { - LOGI("CdmEngine::RestoreKey: invalid key set id"); + LOGE("CdmEngine::RestoreKey: invalid key set id"); return KEY_ERROR; } @@ -286,6 +309,9 @@ CdmResponseType CdmEngine::RestoreKey( cert_provisioning_requested_security_level_ = iter->second->GetRequestedSecurityLevel(); } + if (KEY_ADDED != sts) { + LOGE("CdmEngine::RestoreKey: restore offline session error = %d", sts); + } return sts; } @@ -539,11 +565,46 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { LOGE("CdmEngine::Unprovision: unable to delete files"); return UNKNOWN_ERROR; } - return NO_ERROR; + + scoped_ptr crypto_session(new CryptoSession()); + CdmResponseType status = crypto_session->Open( + security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault); + if (NO_ERROR != status) { + LOGE("CdmEngine::Unprovision: error opening crypto session: %d", status); + return UNKNOWN_ERROR; + } + status = crypto_session->DeleteAllUsageReports(); + if (status != NO_ERROR) { + LOGE("CdmEngine::Unprovision: error deleteing usage reports: %d", status); + } + return status; } CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) { - usage_session_.reset(new CdmSession(NULL)); + // Return a random usage report from a random security level + SecurityLevel security_level = ((rand() % 2) == 0) ? kLevelDefault : kLevel3; + CdmResponseType status = GetUsageInfo(security_level, usage_info); + + if (KEY_MESSAGE == status && !usage_info->empty()) + return status; + + security_level = (kLevel3 == security_level) ? kLevelDefault : kLevel3; + status = GetUsageInfo(security_level, usage_info); + if (NEED_PROVISIONING == status) + return NO_ERROR; // Valid scenario that one of the security + // levels has not been provisioned + return status; +} + +CdmResponseType CdmEngine::GetUsageInfo( + SecurityLevel requested_security_level, + CdmUsageInfo* usage_info) { + if (NULL == usage_property_set_.get()) { + usage_property_set_.reset(new UsagePropertySet()); + } + usage_property_set_->set_security_level(requested_security_level); + + usage_session_.reset(new CdmSession(usage_property_set_.get())); CdmResponseType status = usage_session_->Init(); if (NO_ERROR != status) { @@ -573,7 +634,7 @@ CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) { uint32_t index = rand() % license_info.size(); status = usage_session_->RestoreUsageSession(license_info[index].first, - license_info[index].second); + license_info[index].second); if (KEY_ADDED != status) { LOGE("CdmEngine::GetUsageInfo: restore usage session (%u) error %d", index, status); @@ -600,6 +661,7 @@ CdmResponseType CdmEngine::ReleaseUsageInfo( } CdmResponseType status = usage_session_->ReleaseKey(message); + usage_session_.reset(NULL); if (NO_ERROR != status) { LOGE("CdmEngine::ReleaseUsageInfo: release key error: %d", status); } @@ -724,29 +786,44 @@ bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) { void CdmEngine::OnTimerEvent() { Clock clock; uint64_t current_time = clock.GetCurrentTime(); - bool update_usage_information = false; + bool usage_update_period_expired = false; - if (current_time - last_usage_information_update_time > + if (current_time - last_usage_information_update_time_ > kUpdateUsageInformationPeriod) { - update_usage_information = true; - last_usage_information_update_time = current_time; + usage_update_period_expired = true; + last_usage_information_update_time_ = current_time; } + bool is_initial_usage_update = false; + bool is_usage_update_needed = false; + for (CdmSessionMap::iterator iter = sessions_.begin(); iter != sessions_.end(); ++iter) { - iter->second->OnTimerEvent(update_usage_information); + is_initial_usage_update = is_initial_usage_update || + iter->second->is_initial_usage_update(); + is_usage_update_needed = is_usage_update_needed || + iter->second->is_usage_update_needed(); - if (update_usage_information && iter->second->is_usage_update_needed()) { - // usage is updated for all sessions so this needs to be - // called only once per update usage information period - CdmResponseType status = iter->second->UpdateUsageInformation(); - if (NO_ERROR != status) { - LOGW("Update usage information failed: %d", status); - } else { - update_usage_information = false; + iter->second->OnTimerEvent(usage_update_period_expired); + } + + if (is_usage_update_needed && + (usage_update_period_expired || is_initial_usage_update)) { + bool has_usage_been_updated = false; + for (CdmSessionMap::iterator iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { + iter->second->reset_usage_flags(); + if (!has_usage_been_updated) { + // usage is updated for all sessions so this needs to be + // called only once per update usage information period + CdmResponseType status = iter->second->UpdateUsageInformation(); + if (NO_ERROR != status) { + LOGW("Update usage information failed: %d", status); + } else { + has_usage_been_updated = true; + } } } - iter->second->reset_is_usage_update_needed(); } } diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index c10adedf..95c432cf 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -72,6 +72,7 @@ void CdmSession::Create( license_received_ = false; is_offline_ = false; is_release_ = false; + is_initial_usage_update_ = true; is_usage_update_needed_ = false; is_initial_decryption_ = true; has_decrypted_recently_ = false; @@ -152,11 +153,18 @@ CdmResponseType CdmSession::RestoreOfflineSession( return UNKNOWN_ERROR; } - if (!license_parser_->RestoreOfflineLicense(key_request_, key_response_, - offline_key_renewal_response_, - playback_start_time, - last_playback_time)) { - return UNKNOWN_ERROR; + if (license_type == kLicenseTypeRelease) { + if (!license_parser_->RestoreLicenseForRelease(key_request_, + key_response_)) { + return UNKNOWN_ERROR; + } + } else { + if (!license_parser_->RestoreOfflineLicense(key_request_, key_response_, + offline_key_renewal_response_, + playback_start_time, + last_playback_time)) { + return UNKNOWN_ERROR; + } } license_received_ = true; @@ -172,7 +180,7 @@ CdmResponseType CdmSession::RestoreUsageSession( key_request_ = key_request; key_response_ = key_response; - if (!license_parser_->RestoreUsageLicense(key_request_, key_response_)) { + if (!license_parser_->RestoreLicenseForRelease(key_request_, key_response_)) { return UNKNOWN_ERROR; } diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index b6012414..3514985b 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -421,10 +421,19 @@ CdmResponseType CryptoSession::LoadKeys( provider_session_token.length()); if (OEMCrypto_SUCCESS == sts) { + if (!provider_session_token.empty()) { + sts = OEMCrypto_UpdateUsageTable(); + if (sts != OEMCrypto_SUCCESS) { + LOGW("CryptoSession::LoadKeys: OEMCrypto_UpdateUsageTable error=%ld", + sts); + } + } return KEY_ADDED; } else if (OEMCrypto_ERROR_TOO_MANY_KEYS == sts) { + LOGE("CryptoSession::LoadKeys: OEMCrypto_LoadKeys error=%d", sts); return INSUFFICIENT_CRYPTO_RESOURCES; } else { + LOGE("CryptoSession::LoadKeys: OEMCrypto_LoadKeys error=%d", sts); return KEY_ERROR; } } @@ -439,7 +448,7 @@ bool CryptoSession::LoadCertificatePrivateKey(std::string& wrapped_key) { wrapped_key.size()); if (OEMCrypto_SUCCESS != sts) { - LOGD("LoadCertificatePrivateKey: OEMCrypto_LoadDeviceRSAKey error=%d", sts); + LOGE("LoadCertificatePrivateKey: OEMCrypto_LoadDeviceRSAKey error=%d", sts); return false; } @@ -509,7 +518,7 @@ bool CryptoSession::GenerateDerivedKeys(const std::string& message) { enc_deriv_message.size()); if (OEMCrypto_SUCCESS != sts) { - LOGD("GenerateDerivedKeys: OEMCrypto_GenerateDerivedKeys error=%d", sts); + LOGE("GenerateDerivedKeys: OEMCrypto_GenerateDerivedKeys error=%d", sts); return false; } @@ -533,7 +542,7 @@ bool CryptoSession::GenerateDerivedKeys(const std::string& message, enc_deriv_message.size()); if (OEMCrypto_SUCCESS != sts) { - LOGD("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d", sts); + LOGE("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d", sts); return false; } @@ -554,7 +563,7 @@ bool CryptoSession::GenerateSignature(const std::string& message, if (OEMCrypto_SUCCESS != sts) { if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { - LOGD("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts); + LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts); return false; } @@ -567,7 +576,7 @@ bool CryptoSession::GenerateSignature(const std::string& message, &length); if (OEMCrypto_SUCCESS != sts) { - LOGD("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts); + LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts); return false; } } @@ -593,7 +602,7 @@ bool CryptoSession::GenerateRsaSignature(const std::string& message, if (OEMCrypto_SUCCESS != sts) { if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { - LOGD("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts); + LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts); return false; } @@ -606,7 +615,7 @@ bool CryptoSession::GenerateRsaSignature(const std::string& message, &length, kSign_RSASSA_PSS); if (OEMCrypto_SUCCESS != sts) { - LOGD("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts); + LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts); return false; } } @@ -774,10 +783,32 @@ CdmResponseType CryptoSession::GenerateUsageReport( *usage_duration_status = kUsageDurationPlaybackNotBegun; return NO_ERROR; } + LOGV("OEMCrypto_PST_Report.status: %d\n", pst_report.status); + LOGV("OEMCrypto_PST_Report.clock_security_level: %d\n", + pst_report.clock_security_level); + LOGV("OEMCrypto_PST_Report.pst_length: %d\n", pst_report.pst_length); + LOGV("OEMCrypto_PST_Report.padding: %d\n", pst_report.padding); + LOGV("OEMCrypto_PST_Report.seconds_since_license_received: %lld\n", + ntohll64(pst_report.seconds_since_license_received)); + LOGV("OEMCrypto_PST_Report.seconds_since_first_decrypt: %lld\n", + ntohll64(pst_report.seconds_since_first_decrypt)); + LOGV("OEMCrypto_PST_Report.seconds_since_last_decrypt: %lld\n", + ntohll64(pst_report.seconds_since_last_decrypt)); + LOGV("OEMCrypto_PST_Report: %s\n", b2a_hex(*usage_report).c_str()); + + // When usage report state is inactive, we have to deduce whether the + // license was ever used. + if (kInactive == pst_report.status && + (0 > ntohll64(pst_report.seconds_since_first_decrypt) || + ntohll64(pst_report.seconds_since_license_received) < + ntohll64(pst_report.seconds_since_first_decrypt))) { + *usage_duration_status = kUsageDurationPlaybackNotBegun; + return NO_ERROR; + } *usage_duration_status = kUsageDurationsValid; - *seconds_since_started = pst_report.seconds_since_first_decrypt; - *seconds_since_last_played = pst_report.seconds_since_last_decrypt; + *seconds_since_started = ntohll64(pst_report.seconds_since_first_decrypt); + *seconds_since_last_played = ntohll64(pst_report.seconds_since_last_decrypt); return NO_ERROR; } @@ -799,6 +830,31 @@ CdmResponseType CryptoSession::ReleaseUsageInformation( status); return UNKNOWN_ERROR; } + status = OEMCrypto_UpdateUsageTable(); + if (status != OEMCrypto_SUCCESS) { + LOGW("CryptoSession::ReleaseUsageInformation: OEMCrypto_UpdateUsageTable: " + "error=%ld", + status); + } + + return NO_ERROR; +} + +CdmResponseType CryptoSession::DeleteAllUsageReports() { + LOGV("DeleteAllUsageReports"); + OEMCryptoResult status = OEMCrypto_DeleteUsageTable(); + + if (OEMCrypto_SUCCESS != status) { + LOGE("CryptoSession::DeleteAllUsageReports: Delete Usage Table error =%ld", + status); + } + + status = OEMCrypto_UpdateUsageTable(); + if (status != OEMCrypto_SUCCESS) { + LOGE("CryptoSession::ReleaseUsageInformation: OEMCrypto_UpdateUsageTable: " + "error=%ld", + status); + } return NO_ERROR; } @@ -840,7 +896,7 @@ bool CryptoSession::RewrapDeviceRSAKey(const std::string& message, const std::string& enc_rsa_key, const std::string& rsa_key_iv, std::string* wrapped_rsa_key) { - LOGD("CryptoSession::RewrapDeviceRSAKey, session id=%ld", + LOGV("CryptoSession::RewrapDeviceRSAKey, session id=%ld", static_cast(oec_session_id_)); const uint8_t* signed_msg = reinterpret_cast(message.data()); diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index f73df395..655b8bb4 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -755,7 +755,6 @@ bool CdmLicense::RestoreOfflineLicense( const CdmKeyResponse& license_renewal_response, int64_t playback_start_time, int64_t last_playback_time) { - if (license_request.empty() || license_response.empty()) { LOGE( "CdmLicense::RestoreOfflineLicense: key_request or response empty: " @@ -827,26 +826,28 @@ bool CdmLicense::RestoreOfflineLicense( return true; } -bool CdmLicense::RestoreUsageLicense(const CdmKeyMessage& license_request, - const CdmKeyResponse& license_response) { +bool CdmLicense::RestoreLicenseForRelease( + const CdmKeyMessage& license_request, + const CdmKeyResponse& license_response) { if (license_request.empty() || license_response.empty()) { LOGE( - "CdmLicense::RestoreUsageLicense: key_request or response empty: %u %u", + "CdmLicense::RestoreLicenseForRelease: key_request or response empty:" + " %u %u", license_request.size(), license_response.size()); return false; } SignedMessage signed_request; if (!signed_request.ParseFromString(license_request)) { - LOGE("CdmLicense::RestoreUsageLicense: license_request parse failed"); + LOGE("CdmLicense::RestoreLicenseForRelease: license_request parse failed"); return false; } if (signed_request.type() != SignedMessage::LICENSE_REQUEST) { LOGE( - "CdmLicense::RestoreUsageLicense: license request type: expected = %d," - " actual = %d", + "CdmLicense::RestoreLicenseForRelease: license request type: expected " + "= %d, actual = %d", SignedMessage::LICENSE_REQUEST, signed_request.type()); return false; } @@ -855,20 +856,21 @@ bool CdmLicense::RestoreUsageLicense(const CdmKeyMessage& license_request, SignedMessage signed_response; if (!signed_response.ParseFromString(license_response)) { - LOGE("CdmLicense::RestoreUsageLicense: unable to parse signed license" + LOGE("CdmLicense::RestoreLicenseForRelease: unable to parse signed license" " response"); return false; } if (SignedMessage::LICENSE != signed_response.type()) { - LOGE("CdmLicense::RestoreUsageLicense: unrecognized signed message type: %d" + LOGE("CdmLicense::RestoreLicenseForRelease: unrecognized signed message " + "type: %d" , signed_response.type()); return false; } if (Properties::use_certificates_as_identification()) { if (!signed_response.has_session_key()) { - LOGE("CdmLicense::RestoreUsageLicense: no session keys present"); + LOGE("CdmLicense::RestoreLicenseForRelease: no session keys present"); return false; } @@ -880,16 +882,24 @@ bool CdmLicense::RestoreUsageLicense(const CdmKeyMessage& license_request, } if (!signed_response.has_signature()) { - LOGE("CdmLicense::RestoreUsageLicense: license response is not signed"); + LOGE("CdmLicense::RestoreLicenseForRelease: license response is not" + " signed"); return false; } License license; if (!license.ParseFromString(signed_response.msg())) { - LOGE("CdmLicense::RestoreUsageLicense: unable to parse license response"); + LOGE("CdmLicense::RestoreLicenseForRelease: unable to parse license" + " response"); return false; } + if (license.id().has_provider_session_token()) + provider_session_token_ = license.id().provider_session_token(); + + if (license.policy().has_renewal_server_url()) + server_url_ = license.policy().renewal_server_url(); + policy_engine_->SetLicense(license); return true; } diff --git a/libwvdrmengine/cdm/core/src/string_conversions.cpp b/libwvdrmengine/cdm/core/src/string_conversions.cpp index 8fc04124..0f8e2d91 100644 --- a/libwvdrmengine/cdm/core/src/string_conversions.cpp +++ b/libwvdrmengine/cdm/core/src/string_conversions.cpp @@ -174,14 +174,31 @@ int64_t htonll64(int64_t x) { // Convert to big endian (network-byte-order) int64_t number; } mixed; mixed.number = 1; - if (mixed.array[0] == 1) { - mixed.number = x; // Little Endian. + if (mixed.array[0] == 1) { // Little Endian. + mixed.number = x; uint32_t temp = mixed.array[0]; mixed.array[0] = htonl(mixed.array[1]); mixed.array[1] = htonl(temp); return mixed.number; - } else { - return x; // Big Endian. + } else { // Big Endian. + return x; + } +} + +int64_t ntohll64(int64_t x) { // Convert from big endian (network-byte-order) + union { + uint32_t array[2]; + int64_t number; + } mixed; + mixed.number = 1; + if (mixed.array[0] == 1) { // Little Endian. + mixed.number = x; + uint32_t temp = mixed.array[0]; + mixed.array[0] = ntohl(mixed.array[1]); + mixed.array[1] = ntohl(temp); + return mixed.number; + } else { // Big Endian. + return x; } } diff --git a/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp b/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp new file mode 100644 index 00000000..42f2bfa4 --- /dev/null +++ b/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp @@ -0,0 +1,240 @@ +// Copyright 2014 Google Inc. All Rights Reserved. + +#include "cdm_session.h" +#include "crypto_key.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "properties.h" +#include "string_conversions.h" + +namespace { +const std::string kToken = wvcdm::a2bs_hex( + "0AAE02080212107E0A892DEEB021E7AF696B938BB1D5B1188B85AD9D05228E023082010A02" + "82010100DBEDF2BFB0EC98213766E65049B9AB176FA4B1FBFBB2A0C96C87D9F2B895E0ED77" + "93BDA057E6BC3E0CA2348BC6831E03609445CA4D418CB98EAC98FFC87AB2364CE76BA26BEE" + "CDB0C45BD2A6FE9FD38CC5A1C26303AEEB7E9C3CAFAB0D10E46C07E50BEDAB42BF21F40BD2" + "E055DB0B455191D6B4CEEB11B3F1AFA42B5C0CE4C96B75A5283C0E3AE049AA7CF86D1C4EF6" + "6A9088B53BCF320ABC9B98A22C219DC109014EFEA72DA5FF2ED5D655DE7AE06EAC6C6B4191" + "523B2CD2DC1EBFF5F08B11CFE056F2826C1323F12704EC7EBBC1AF935129E5543804492AF9" + "23B848F4AF47B4BFB131C39DDDC99DBAEEE0F30AD2ADBBC63E60793D0876E37391008BB4DB" + "F7020301000128DD22128002A9E571776EA9D22A1BD14248DA88E12FD859989F613360B8D2" + "DA40AF31CC071C7A138466F0EB745E3FD664C0E1A5E4F01098B8D56C34A0158DF9916D192F" + "841ADCA17FD630E1C0CBE652CAC6A52B6A1581BE4029CE6FAE0E04D2D2C7861187AF8299D8" + "3E008DB9A2789672CA1DED903773D7E82B234CE2C799EB73CF80600C08F17EEDDDF369D2B8" + "4A08292F22D1F18FE89521905E713BA674F2217881DBD7711B8C48D5FDCE6FAB51F935E293" + "CB29191AB012B115FD2F5F23164B063D0A929C3E254BF0F4FA60051EB6B3498ED99FF77C19" + "68E8CD83A35CEB054D05433FD0EA6AAE43C87DDA377591D1DCC1831EE130BFFF6D139A5ADA" + "738B0F257CCE2649E71AB4050AAE020801121017DCBC27D11341D497135442A188DAA6188F" + "89809105228E023082010A0282010100D21ADD7549D2748B3494526A9C3FB86C79376BBE8C" + "8856F601B8D10461F77ACC7331B10DEBF365120056CDB5662D25907B74F12382F0F4A0CA47" + "5EEA9562815C6228F6F698ADA27879D8890F2A2D96A746DDEF5316301C003519C2A2250354" + "674169FDDA41CE14D3C52BEA7A20384515012D5952B38AA19E15E8563CC7AAA81C2122880A" + "A370A64FEA23C53FB83AC3DB5753214730A349E07F64BF32BE7EAD30D02612AF110BB44FB0" + "8E1D308173B327EF64D40C41639542B2D1A73C98A6607EC6C683B513A58470514106EF87AE" + "1E7B9C695B93A104DF7437BFC4167789748A43ED208F2C1FA710793C688885EAE732A8BFDF" + "5B423B23D75B88FC0ADC8FBDB5020301000128DD2212800372D2FB88098BA3B85B6B4354E0" + "3767DBE2D7724663FB0A62ABF7704EA910E01F221349EE16D0152C769384050CE78520668C" + "06CCFD3D789AF3EB69FF163615CD609169FDBE2E15A029D34AD2605625BC81844C9D1E2CE0" + "519039F3799ADAEF86641E20B033DC16DF2E5B9A1A2A417B8BB3B7A4D9AD1A99367448587D" + "A13DDE05A3ED9D62FA42078973B4AA40263D7BFA23F1072E94CDF323FA45F78408823E55C4" + "F4C5C723819CF44CE6D98E50C04EC24D93B1AAB8877B9108B9CA391308E1A3645EBB0E7CAC" + "BB40B5451560ED799421873BFB5ABB917FA60DB9C77CB8606AF7E3142626F5EA40E5CB8AA0" + "89D8E7D6A9361935C426A4450EA8BC2E57290D3BF0A0962991D2A91B752FC80C3E7E4E5503" + "3D71C94B325307A68815F026448F56A2741CEBEFC18E8C142F5F62BFAA67A291517DDE982D" + "8CD5A9DF6E3D3A99B806F6D60991358C5BE77117D4F3168F3348E9A048539F892F4D783152" + "C7A8095224AA56B78C5CF7BD1AB1B179C0C0D11E3C3BAC84C141A00191321E3ACC17242E68" + "3C"); +const std::string kWrappedKey = wvcdm::a2bs_hex( + "3B84252DD84F1A710365014A114507FFFA3DD404625D61D1EEC7C3A39D72CB8D9318ADE9DA" + "05D69F9776DAFDA49A97BC30E84CA275925DFD98CA04F7DB23465103A224852192DE232902" + "99FF82024F5CCA7716ACA9BE0B56348BA16B9E3136D73789C842CB2ECA4820DDAAF59CCB9B" + "FCF2B4B0E2E5199FDCEC8DEBFFE50BB03041D8E767EA3FE6834C2E79E261ABF17B68EA66E1" + "45AD0A6B056F39C06531A9038C996BADD524E57AE7D5339F13C574E7A398C03D65FD730BAC" + "36F25347350DD2AD69EFA4DC040DC2D9DD4F53A729839FA3496CF580F2CBD51C3522DD67BC" + "BA4A91E89E2BD70449F28E026638920A6DF7B9A0B2C977ACC65AE845E76EF81CADAA746DAF" + "51D4D6FCBC083BE50DA1874D6EB1A30579B23C30881D94A8E5181FE20BF8F8C5F2522B1E7D" + "092B1E20BDE5373F40286DE15267247F88C564BD4EBF4F69B889A03C9892584DC340D87EE1" + "DFF2942D1B7E7EBD846349575F2DE6FDEF71BB005CFBEA845D87937BEBCAFEAC785A092C0A" + "76CE7F7A4FE2F8E43045DED5202A2A55F547BA5DE67AF9E6B2B7DC89EFAD34AC0B40BF4B8F" + "F82F8706B9A88FB9C7A0972E4A4B6CA970BF4F086573D595E5DB8ED0FDA4F9446ACD4B119C" + "1E949C194B042A5CFFC13043FF79F049068A67CC1EB671A10EF7DA927753C4D149E9D0000D" + "4307008BA0AED576ACCADF0CE6758F683087F26C2E38297B8C7D78DC3F1E8F24D7B3A0BED9" + "C066F8348FD19CDB54A92C4E944EE11E11B3B44344E0DB0E1B4BD6CF9295AB66C05454776A" + "8FE33AF659F67718AB43ACB52E83F8C29129DCE9654E8F1EBF9DAB9E933955E24389A37DBE" + "17BA89AC8C750B025CB2F65D5C8BF32FED87EF368F15751AA2114159B6C9C6C814D0720DA4" + "6E885BBB764ADC250D05F70306C3190991C31439BF273A33B6D1773E4FD089F32E753FA3C7" + "7B5ED7DB28407D87396F1F8C83B58176EDFE1F923BDB7DA0ADE58CA2BCD6E76F9463BE7A5A" + "909BE2731241BF1436F3E6A639FC7C717445D89AA5812E4532405B0FB368FE736E22D10FFD" + "15FACCF69FAC468B5552C7887763B96578038CCF154F333E2095BBFF71D5C1235E032174FB" + "44EAB4A753E7A917666A400EBE4F3D2C90100155C27F4B30C8ACFEDA6EFC763EF3556874E5" + "8A5AB0AEBBF39990F79EF4D65EC4697D7BBEEF4F32AE8C4A8A94814A9BE532B5AC902BC0C5" + "FB0A3E661AFF5961B6E79C82CC32FA7B7B48297347503FD58B110B93208167CC1FB96AD822" + "42F60B9D2BF9CCEE8E778A3D3A3302303FB4E33F607D46AACE49D3546A993EDC6FBEE6E19D" + "36831D85877013C57FE335F38D5CD9C3E09C1CEE28BC92C53A364663A7C031DF43B89BAAB4" + "AA8176900FD483AD70E3844BD15EE4F01D8BE72186BBF9E019FCEE5961166696854D1A901F" + "9D71B69B05F75FF233DB3C37F18DCADA640F68C4386F2E528CD77B93521A4574EF399375CD" + "2BE7B9FDC0AE62249717B7E0022BF55C0D023669DD09355EAA90E9DF9BEA309DF7561423BF" + "1DAD177F07A442E1591553924C0F67C2E86774009825490322A6B74319B4C77AA6195CA393" + "03A311F762FB0FD445278D9ACF26A9049C5031BE91F2C4A6BE994CA5A3CEBC2ACCF93AB1EB" + "993A6AA6DEB152DE8C9BB0E6B37B478393B50D1AAE99C086A0ED6D93BA7DD2DEEAB58EE34B" + "C5EE06BE238E8DE6CB44211097C5C90D5C04857918856F86B7036986C20A43153892ED9093" + "33EF70621A98184DDAB5E14BC971CF98CF6C91A37FFA83B00AD3BCABBAAB2DEF1C52F43003" + "E74C92B44F9205D22262FB47948654229DE1920F8EDF96A19A88A1CA1552F8856FB4CBF83B" + "AA3348419159D207F65FCE9C1A500C6818"); +} // namespace + +namespace wvcdm { + +// gmock methods +using ::testing::_; +using ::testing::Eq; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; + +class MockDeviceFiles : public DeviceFiles { + public: + MOCK_METHOD1(Init, bool(CdmSecurityLevel)); + MOCK_METHOD2(RetrieveCertificate, bool(std::string*, std::string*)); +}; + +class MockCryptoSession : public CryptoSession { + public: + MOCK_METHOD1(GetToken, bool(std::string*)); + MOCK_METHOD0(GetSecurityLevel, CdmSecurityLevel()); + MOCK_METHOD0(Open, CdmResponseType()); + MOCK_METHOD1(Open, CdmResponseType(SecurityLevel)); + MOCK_METHOD1(LoadCertificatePrivateKey, bool(std::string&)); + MOCK_METHOD0(DeleteAllUsageReports, CdmResponseType()); +}; + +class MockPolicyEngine : public PolicyEngine { + public: + // Leaving a place holder for when PolicyEngine methods need to be mocked +}; + +class MockCdmLicense : public CdmLicense { + public: + MOCK_METHOD3(Init, bool(const std::string&, CryptoSession*, PolicyEngine*)); +}; + +class CdmSessionTest : public ::testing::Test { + protected: + virtual void SetUp() { + license_parser_ = new MockCdmLicense(); + crypto_session_ = new MockCryptoSession(); + policy_engine_ = new MockPolicyEngine(); + file_handle_ = new MockDeviceFiles(); + } + + virtual void TearDown() { + if (cdm_session_) delete cdm_session_; + } + + void CreateSession() { + cdm_session_ = new CdmSession(license_parser_, crypto_session_, + policy_engine_, file_handle_, NULL); + } + + void CreateSession(const CdmClientPropertySet* cdm_client_property_set) { + cdm_session_ = + new CdmSession(license_parser_, crypto_session_, policy_engine_, + file_handle_, cdm_client_property_set); + } + + CdmSession* cdm_session_; + MockCdmLicense* license_parser_; + MockCryptoSession* crypto_session_; + MockPolicyEngine* policy_engine_; + MockDeviceFiles* file_handle_; +}; + +TEST_F(CdmSessionTest, InitWithCertificate) { + CdmSecurityLevel level = kSecurityLevelL1; + EXPECT_CALL(*crypto_session_, GetSecurityLevel()).WillOnce(Return(level)); + EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault))) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, RetrieveCertificate(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kToken), SetArgPointee<1>(kWrappedKey), + Return(true))); + EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) + .WillOnce(Return(true)); + EXPECT_CALL(*license_parser_, + Init(Eq(kToken), Eq(crypto_session_), Eq(policy_engine_))) + .WillOnce(Return(true)); + + Properties::set_use_certificates_as_identification(true); + + CreateSession(); + ASSERT_EQ(NO_ERROR, cdm_session_->Init()); +} + +TEST_F(CdmSessionTest, InitWithKeybox) { + CdmSecurityLevel level = kSecurityLevelL1; + EXPECT_CALL(*crypto_session_, GetSecurityLevel()).WillOnce(Return(level)); + EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault))) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, GetToken(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kToken), Return(true))); + EXPECT_CALL(*license_parser_, + Init(Eq(kToken), Eq(crypto_session_), Eq(policy_engine_))) + .WillOnce(Return(true)); + + Properties::set_use_certificates_as_identification(false); + + CreateSession(); + ASSERT_EQ(NO_ERROR, cdm_session_->Init()); +} + +TEST_F(CdmSessionTest, ReInitFail) { + CdmSecurityLevel level = kSecurityLevelL1; + EXPECT_CALL(*crypto_session_, GetSecurityLevel()).WillOnce(Return(level)); + EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault))) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, RetrieveCertificate(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kToken), SetArgPointee<1>(kWrappedKey), + Return(true))); + EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) + .WillOnce(Return(true)); + EXPECT_CALL(*license_parser_, + Init(Eq(kToken), Eq(crypto_session_), Eq(policy_engine_))) + .WillOnce(Return(true)); + + Properties::set_use_certificates_as_identification(true); + + CreateSession(); + ASSERT_EQ(NO_ERROR, cdm_session_->Init()); + ASSERT_EQ(UNKNOWN_ERROR, cdm_session_->Init()); +} + +TEST_F(CdmSessionTest, InitFailCryptoError) { + CdmSecurityLevel level = kSecurityLevelL1; + EXPECT_CALL(*crypto_session_, GetSecurityLevel()).WillOnce(Return(level)); + EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault))) + .WillOnce(Return(UNKNOWN_ERROR)); + + Properties::set_use_certificates_as_identification(true); + + CreateSession(); + ASSERT_EQ(UNKNOWN_ERROR, cdm_session_->Init()); +} + +TEST_F(CdmSessionTest, InitNeedsProvisioning) { + CdmSecurityLevel level = kSecurityLevelL1; + EXPECT_CALL(*crypto_session_, GetSecurityLevel()).WillOnce(Return(level)); + EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault))) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, RetrieveCertificate(NotNull(), NotNull())) + .WillOnce(Return(false)); + + Properties::set_use_certificates_as_identification(true); + + CreateSession(); + ASSERT_EQ(NEED_PROVISIONING, cdm_session_->Init()); +} + +} // wvcdm diff --git a/libwvdrmengine/cdm/core/test/config_test_env.cpp b/libwvdrmengine/cdm/core/test/config_test_env.cpp index d289fa2f..8acf357e 100644 --- a/libwvdrmengine/cdm/core/test/config_test_env.cpp +++ b/libwvdrmengine/cdm/core/test/config_test_env.cpp @@ -27,7 +27,7 @@ const std::string kCpOfflineKeyId = "00000020" // pssh data size // pssh data: "08011a0d7769646576696e655f746573" - "74220d6f66666c696e655f636c697031"; + "74220d6f66666c696e655f636c697032"; // Google Play license server data const std::string kGpLicenseServer = diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index ca589647..de7ed587 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -106,7 +106,10 @@ CdmResponseType WvContentDecryptionModule::AddKey( CdmResponseType WvContentDecryptionModule::RestoreKey( const CdmSessionId& session_id, const CdmKeySetId& key_set_id) { - return cdm_engine_->RestoreKey(session_id, key_set_id); + CdmResponseType sts = cdm_engine_->RestoreKey(session_id, key_set_id); + if (sts == KEY_ADDED) + EnablePolicyTimer(); + return sts; } CdmResponseType WvContentDecryptionModule::CancelKeyRequest( diff --git a/libwvdrmengine/cdm/test/Android.mk b/libwvdrmengine/cdm/test/Android.mk index 66700ced..9e66bac2 100644 --- a/libwvdrmengine/cdm/test/Android.mk +++ b/libwvdrmengine/cdm/test/Android.mk @@ -11,6 +11,10 @@ test_name := cdm_engine_test test_src_dir := ../core/test include $(LOCAL_PATH)/unit-test.mk +test_name := cdm_extended_duration_test +test_src_dir := . +include $(LOCAL_PATH)/unit-test.mk + test_name := cdm_session_unittest test_src_dir := ../core/test include $(LOCAL_PATH)/unit-test.mk diff --git a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp new file mode 100644 index 00000000..47fed1e1 --- /dev/null +++ b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp @@ -0,0 +1,1278 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include +#include +#include + +#include "clock.h" +#include "config_test_env.h" +#include "device_files.h" +#include "file_store.h" +#include "license_request.h" +#include "license_protocol.pb.h" +#include "log.h" +#include "gtest/gtest.h" +#include "OEMCryptoCENC.h" +#include "oemcrypto_adapter.h" +#include "properties.h" +#include "string_conversions.h" +#include "url_request.h" +#include "wv_cdm_constants.h" +#include "wv_cdm_event_listener.h" +#include "wv_content_decryption_module.h" + +namespace { + +// HTTP response codes. +const int kHttpOk = 200; +const int kHttpBadRequest = 400; +const int kHttpInternalServerError = 500; + +const uint32_t kMinute = 60; +const uint32_t kClockTolerance = 5; +const uint32_t kTwoMinutes = 120; + +// Default license server, can be configured using --server command line option +// Default key id (pssh), can be configured using --keyid command line option +std::string g_client_auth; +wvcdm::ConfigTestEnv* g_config = NULL; +wvcdm::KeyId g_key_id; +wvcdm::CdmKeySystem g_key_system; +std::string g_license_server; +wvcdm::KeyId g_wrong_key_id; +wvcdm::LicenseServerId g_license_server_id = wvcdm::kContentProtectionServer; + +std::string kServiceCertificate = + "0803121028703454C008F63618ADE7443DB6C4C8188BE7F99005228E023082010" + "A0282010100B52112B8D05D023FCC5D95E2C251C1C649B4177CD8D2BEEF355BB0" + "6743DE661E3D2ABC3182B79946D55FDC08DFE95407815E9A6274B322A2C7F5E06" + "7BB5F0AC07A89D45AEA94B2516F075B66EF811D0D26E1B9A6B894F2B9857962AA" + "171C4F66630D3E4C602718897F5E1EF9B6AAF5AD4DBA2A7E14176DF134A1D3185" + "B5A218AC05A4C41F081EFFF80A3A040C50B09BBC740EEDCD8F14D675A91980F92" + "CA7DDC646A06ADAD5101F74A0E498CC01F00532BAC217850BD905E90923656B7D" + "FEFEF42486767F33EF6283D4F4254AB72589390BEE55808F1D668080D45D893C2" + "BCA2F74D60A0C0D0A0993CEF01604703334C3638139486BC9DAF24FD67A07F9AD" + "94302030100013A1273746167696E672E676F6F676C652E636F6D"; + +// TODO(rfrias): refactor to print out the decryption test names +struct SubSampleInfo { + bool retrieve_key; + size_t num_of_subsamples; + bool validate_key_id; + bool is_encrypted; + bool is_secure; + wvcdm::KeyId key_id; + std::vector encrypt_data; + std::vector decrypt_data; + std::vector iv; + size_t block_offset; + uint8_t subsample_flags; +}; + +SubSampleInfo kEncryptedStreamingNoPstSubSample = { + // key SD, encrypted, 256b + true, 1, true, true, false, + wvcdm::a2bs_hex("371EA35E1A985D75D198A7F41020DC23"), + wvcdm::a2b_hex( + "64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36" + "17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d" + "7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab" + "ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c" + "2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4" + "9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4" + "6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012" + "c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685"), + wvcdm::a2b_hex( + "217ce9bde99bd91e9733a1a00b9b557ac3a433dc92633546156817fae26b6e1c" + "942ac20a89ff79f4c2f25fba99d6a44618a8c0420b27d54e3da17b77c9d43cca" + "595d259a1e4a8b6d7744cd98c5d3f921adc252eb7d8af6b916044b676a574747" + "8df21fdc42f166880d97a2225cd5c9ea5e7b752f4cf81bbdbe98e542ee10e1c6" + "ad868a6ac55c10d564fc23b8acff407daaf4ed2743520e02cda9680d9ea88e91" + "029359c4cf5906b6ab5bf60fbb3f1a1c7c59acfc7e4fb4ad8e623c04d503a3dd" + "4884604c8da8a53ce33db9ff8f1c5bb6bb97f37b39906bf41596555c1bcce9ed" + "08a899cd760ff0899a1170c2f224b9c52997a0785b7fe170805fd3e8b1127659"), + wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f"), 0, 3}; + +SubSampleInfo kEncryptedStreamingClip1SubSample = { + true, 1, true, true, false, + wvcdm::a2bs_hex("E82DDD1D07CBB31CDD31EBAAE0894609"), + wvcdm::a2b_hex( + "fe8670a01c86906c056b4bf85ad278464c4eb79c60de1da8480e66e78561350e" + "a25ae19a001f834c43aaeadf900b3c5a6745e885a4d1d1ae5bafac08dc1d60e5" + "f3465da303909ec4b09023490471f670b615d77db844192854fdab52b7806203" + "89b374594bbb6a2f2fcc31036d7cb8a3f80c0e27637b58a7650028fbf2470d68" + "1bbd77934af165d215ef325c74438c9d99a20fc628792db28c05ed5deff7d9d4" + "dba02ddb6cf11dc6e78cb5200940af9a2321c3a7c4c79be67b54a744dae1209c" + "fa02fc250ce18d30c7da9c3a4a6c9619bf8561a42ff1e55a7b14fa3c8de69196" + "c2b8e3ff672fc37003b479da5d567b7199917dbe5aa402890ebb066bce140b33"), + wvcdm::a2b_hex( + "d08733bd0ef671f467906b50ff8322091400f86fd6f016fea2b86e33923775b3" + "ebb4c8c6f3ba8b78dd200a74d3872a40264ab99e1d422e4f819abb7f249114aa" + "b334420b37c86ce81938615ab9d3a6b2de8db545cd88e35091031e73016fb386" + "1b754298329b52dbe483de3a532277815e659f3e05e89257333225b933d92e15" + "ef2deff287a192d2c8fc942a29a5f3a1d54440ac6385de7b34bb650b889e4ae9" + "58c957b5f5ff268f445c0a6b825fcad55290cb7b5c9814bc4c72984dcf4c8fd7" + "5f511c173b2e0a3163b18a1eac58539e5c188aeb0751b946ad4dcd08ea777a7f" + "37326df26fa509343faa98dff667629f557873f1284903202e451227ef465a62"), + wvcdm::a2b_hex("7362b5140c4ce0cd5f863858668d3f1a"), 0, 3}; + +SubSampleInfo kEncryptedOfflineClip2SubSample = { + true, 1, true, true, false, + wvcdm::a2bs_hex("3260F39E12CCF653529990168A3583FF"), + wvcdm::a2b_hex( + "3b2cbde084973539329bd5656da22d20396249bf4a18a51c38c4743360cc9fea" + "a1c78d53de1bd7e14dc5d256fd20a57178a98b83804258c239acd7aa38f2d7d2" + "eca614965b3d22049e19e236fc1800e60965d8b36415677bf2f843d50a6943c4" + "683c07c114a32f5e5fbc9939c483c3a1b2ecd3d82b554d649798866191724283" + "f0ab082eba2da79aaca5c4eaf186f9ee9a0c568f621f705a578f30e4e2ef7b96" + "5e14cc046ce6dbf272ee5558b098f332333e95fc879dea6c29bf34acdb649650" + "f08201b9e649960f2493fd7677cc3abf5ae70e5445845c947ba544456b431646" + "d95a133bff5f57614dda5e4446cd8837901d074149dadf4b775b5b07bb88ca20"), + wvcdm::a2b_hex( + "D3EE543581F16AB2EABFA13468133314D19CB6A14A42229BE83543828D801475" + "FAE1C2C5D193DA8445B9C4B1598E8FCBDF42EFF1FBB54EBC6A4815E2836C2848" + "15094DEDE76FE4658A2D6EA3E775A872CA71835CF274676C18556C665EC7F32A" + "4DBB04C10BA988B42758E37DCEFD99D9CE3AFFB1E816C412B4013890E1A31E25" + "64EBF2125BC54D66FECDF8A31956830121546DC183B3DAC103E223778875B590" + "3961448C287B367C585E510DA43BF9242B8E9A27B9F6F3EC7E4B5A0A74A1742B" + "F5CD65EA66D7D9E79C02C7E7D5CD02DB182DDD8EAC3525B0834F1F2822AD0006" + "944B5080B998BB0FE6E566AAFAE2328B37FD189F1920A964434ECF18E11AC81E"), + wvcdm::a2b_hex("7362b5140c4ce0cd5f863858668d3f1a"), 0, 3}; + +std::string kStreamingClip1PstInitData = wvcdm::a2bs_hex( + "000000427073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id + "08011a0d7769646576696e655f74657374220f73" // pssh data + "747265616d696e675f636c697033"); + +std::string kOfflineClip2PstInitData = wvcdm::a2bs_hex( + "000000427073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000020" // Widevine system id + "08011a0d7769646576696e655f74657374220d6f" // pssh data + "66666c696e655f636c697032"); +} // namespace + +namespace wvcdm { +// Protobuf generated classes +using video_widevine_server::sdk::LicenseIdentification; +using video_widevine_server::sdk::LicenseRequest_ContentIdentification; +using video_widevine_server::sdk::ClientIdentification; +using video_widevine_server::sdk::SignedMessage; + +class TestWvCdmClientPropertySet : public CdmClientPropertySet { + public: + TestWvCdmClientPropertySet() + : use_privacy_mode_(false), + is_session_sharing_enabled_(false), + session_sharing_id_(0) {} + virtual ~TestWvCdmClientPropertySet() {} + + virtual const std::string& security_level() const { return security_level_; } + virtual const std::string& service_certificate() const { + return service_certificate_; + } + virtual bool use_privacy_mode() const { return use_privacy_mode_; } + virtual bool is_session_sharing_enabled() const { + return is_session_sharing_enabled_; + } + virtual uint32_t session_sharing_id() const { return session_sharing_id_; } + + void set_security_level(const std::string& security_level) { + if (!security_level.compare(QUERY_VALUE_SECURITY_LEVEL_L1) || + !security_level.compare(QUERY_VALUE_SECURITY_LEVEL_L3)) { + security_level_ = security_level; + } + } + void set_service_certificate(const std::string& service_certificate) { + service_certificate_ = service_certificate; + } + void set_use_privacy_mode(bool use_privacy_mode) { + use_privacy_mode_ = use_privacy_mode; + } + void set_session_sharing_mode(bool enable) { + is_session_sharing_enabled_ = enable; + } + void set_session_sharing_id(uint32_t id) { session_sharing_id_ = id; } + + private: + std::string security_level_; + std::string service_certificate_; + bool use_privacy_mode_; + bool is_session_sharing_enabled_; + uint32_t session_sharing_id_; +}; + +class TestWvCdmEventListener : public WvCdmEventListener { + public: + TestWvCdmEventListener() : WvCdmEventListener() {} + virtual void OnEvent(const CdmSessionId& id, CdmEventType event) { + session_id_ = id; + event_type_ = event; + } + CdmSessionId session_id() { return session_id_; } + CdmEventType event_type() { return event_type_; } + + private: + CdmSessionId session_id_; + CdmEventType event_type_; +}; + +class WvCdmExtendedDurationTest : public testing::Test { + public: + WvCdmExtendedDurationTest() {} + ~WvCdmExtendedDurationTest() {} + + protected: + void GetOfflineConfiguration(std::string* key_id, std::string* client_auth) { + ConfigTestEnv config(g_license_server_id, false); + if (g_key_id.compare(a2bs_hex(g_config->key_id())) == 0) + key_id->assign(wvcdm::a2bs_hex(config.key_id())); + else + key_id->assign(g_key_id); + + if (g_client_auth.compare(g_config->client_auth()) == 0) + client_auth->assign(config.client_auth()); + else + client_auth->assign(g_client_auth); + } + + void GenerateKeyRequest(const std::string& init_data, + CdmLicenseType license_type) { + wvcdm::CdmAppParameterMap app_parameters; + std::string server_url; + EXPECT_EQ(wvcdm::KEY_MESSAGE, + decryptor_.GenerateKeyRequest( + session_id_, key_set_id_, "video/mp4", init_data, + license_type, app_parameters, &key_msg_, &server_url)); + EXPECT_EQ(0u, server_url.size()); + } + + void GenerateRenewalRequest(CdmLicenseType license_type, + std::string* server_url) { + // TODO application makes a license request, CDM will renew the license + // when appropriate. + std::string init_data; + wvcdm::CdmAppParameterMap app_parameters; + EXPECT_EQ(wvcdm::KEY_MESSAGE, + decryptor_.GenerateKeyRequest( + session_id_, key_set_id_, "video/mp4", init_data, + license_type, app_parameters, &key_msg_, server_url)); + // TODO(edwinwong, rfrias): Add tests cases for when license server url + // is empty on renewal. Need appropriate key id at the server. + EXPECT_NE(0u, server_url->size()); + } + + void GenerateKeyRelease(CdmKeySetId key_set_id) { + CdmSessionId session_id; + CdmInitData init_data; + wvcdm::CdmAppParameterMap app_parameters; + std::string server_url; + EXPECT_EQ(wvcdm::KEY_MESSAGE, + decryptor_.GenerateKeyRequest( + session_id, key_set_id, "video/mp4", init_data, + kLicenseTypeRelease, app_parameters, &key_msg_, &server_url)); + } + + // Post a request and extract the drm message from the response + std::string GetKeyRequestResponse(const std::string& server_url, + const std::string& client_auth) { + // Use secure connection and chunk transfer coding. + UrlRequest url_request(server_url + client_auth); + if (!url_request.is_connected()) { + return ""; + } + url_request.PostRequest(key_msg_); + std::string message; + int resp_bytes = url_request.GetResponse(&message); + + int status_code = url_request.GetStatusCode(message); + EXPECT_EQ(kHttpOk, status_code); + + std::string drm_msg; + if (kHttpOk == status_code) { + LicenseRequest lic_request; + lic_request.GetDrmMessage(message, drm_msg); + LOGV("HTTP response body: (%u bytes)", drm_msg.size()); + } + key_response_ = drm_msg; + return drm_msg; + } + + // Post a request and extract the signed provisioning message from + // the HTTP response. + std::string GetCertRequestResponse(const std::string& server_url) { + // Use secure connection and chunk transfer coding. + UrlRequest url_request(server_url); + if (!url_request.is_connected()) { + return ""; + } + + url_request.PostCertRequestInQueryString(key_msg_); + std::string message; + int resp_bytes = url_request.GetResponse(&message); + LOGD("end %d bytes response dump", resp_bytes); + + int status_code = url_request.GetStatusCode(message); + EXPECT_EQ(kHttpOk, status_code); + return message; + } + + // Post a request and extract the signed provisioning message from + // the HTTP response. + std::string GetUsageInfoResponse(const std::string& server_url, + const std::string& client_auth, + const std::string& usage_info_request) { + // Use secure connection and chunk transfer coding. + UrlRequest url_request(server_url + client_auth); + if (!url_request.is_connected()) { + return ""; + } + url_request.PostRequest(usage_info_request); + std::string message; + int resp_bytes = url_request.GetResponse(&message); + + int status_code = url_request.GetStatusCode(message); + EXPECT_EQ(kHttpOk, status_code); + + std::string usage_info; + if (kHttpOk == status_code) { + LicenseRequest license; + license.GetDrmMessage(message, usage_info); + LOGV("HTTP response body: (%u bytes)", usage_info.size()); + } + return usage_info; + } + + void VerifyKeyRequestResponse(const std::string& server_url, + const std::string& client_auth, + bool is_renewal) { + std::string resp = GetKeyRequestResponse(server_url, client_auth); + + if (is_renewal) { + // TODO application makes a license request, CDM will renew the license + // when appropriate + EXPECT_EQ(decryptor_.AddKey(session_id_, resp, &key_set_id_), + wvcdm::KEY_ADDED); + } else { + EXPECT_EQ(decryptor_.AddKey(session_id_, resp, &key_set_id_), + wvcdm::KEY_ADDED); + } + } + + void Unprovision() { + EXPECT_EQ(NO_ERROR, decryptor_.Unprovision(kSecurityLevelL1)); + EXPECT_EQ(NO_ERROR, decryptor_.Unprovision(kSecurityLevelL3)); + } + + void Provision() { + CdmResponseType status = + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + switch (status) { + case NO_ERROR: + decryptor_.CloseSession(session_id_); + return; + case NEED_PROVISIONING: + break; + default: + EXPECT_EQ(NO_ERROR, status); + return; + } + + std::string provisioning_server_url; + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority, cert, wrapped_key; + + status = decryptor_.GetProvisioningRequest( + cert_type, cert_authority, &key_msg_, &provisioning_server_url); + EXPECT_EQ(wvcdm::NO_ERROR, status); + if (NO_ERROR != status) return; + EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url()); + + std::string response = + GetCertRequestResponse(g_config->provisioning_server_url()); + EXPECT_NE(0, static_cast(response.size())); + EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.HandleProvisioningResponse( + response, &cert, &wrapped_key)); + EXPECT_EQ(0, static_cast(cert.size())); + EXPECT_EQ(0, static_cast(wrapped_key.size())); + decryptor_.CloseSession(session_id_); + return; + } + + void ValidateResponse(video_widevine_server::sdk::LicenseType license_type, + bool has_provider_session_token) { + // Validate signed response + SignedMessage signed_message; + EXPECT_TRUE(signed_message.ParseFromString(key_response_)); + EXPECT_EQ(SignedMessage::LICENSE, signed_message.type()); + EXPECT_TRUE(signed_message.has_signature()); + EXPECT_TRUE(!signed_message.msg().empty()); + + // Verify license + video_widevine_server::sdk::License license; + EXPECT_TRUE(license.ParseFromString(signed_message.msg())); + + // Verify license identification + license_id_ = license.id(); + EXPECT_EQ(license_type, license_id_.type()); + EXPECT_EQ(has_provider_session_token, + license_id_.has_provider_session_token()); + } + + void ValidateRenewalRequest(int64_t expected_seconds_since_started, + int64_t expected_seconds_since_last_played, + bool has_provider_session_token) { + // Validate signed renewal request + SignedMessage signed_message; + EXPECT_TRUE(signed_message.ParseFromString(key_msg_)); + EXPECT_EQ(SignedMessage::LICENSE_REQUEST, signed_message.type()); + EXPECT_TRUE(signed_message.has_signature()); + EXPECT_TRUE(!signed_message.msg().empty()); + + // Verify license request + video_widevine_server::sdk::LicenseRequest license_renewal; + EXPECT_TRUE(license_renewal.ParseFromString(signed_message.msg())); + + // Verify Content Identification + const LicenseRequest_ContentIdentification& content_id = + license_renewal.content_id(); + EXPECT_FALSE(content_id.has_cenc_id()); + EXPECT_FALSE(content_id.has_webm_id()); + EXPECT_TRUE(content_id.has_license()); + + const ::video_widevine_server::sdk:: + LicenseRequest_ContentIdentification_ExistingLicense& existing_license = + content_id.license(); + + const LicenseIdentification& id = existing_license.license_id(); + EXPECT_TRUE(std::equal(id.request_id().begin(), id.request_id().end(), + license_id_.request_id().begin())); + EXPECT_TRUE(std::equal(id.session_id().begin(), id.session_id().end(), + license_id_.session_id().begin())); + EXPECT_TRUE(std::equal(id.purchase_id().begin(), id.purchase_id().end(), + license_id_.purchase_id().begin())); + EXPECT_EQ(license_id_.type(), id.type()); + EXPECT_EQ(license_id_.version(), id.version()); + EXPECT_EQ(has_provider_session_token, !id.provider_session_token().empty()); + + EXPECT_LT(expected_seconds_since_started - kClockTolerance, + existing_license.seconds_since_started()); + EXPECT_LT(existing_license.seconds_since_started(), + expected_seconds_since_started + kClockTolerance); + EXPECT_LT(expected_seconds_since_last_played - kClockTolerance, + existing_license.seconds_since_last_played()); + EXPECT_LT(existing_license.seconds_since_last_played(), + expected_seconds_since_last_played + kClockTolerance); + EXPECT_TRUE(existing_license.session_usage_table_entry().empty()); + + EXPECT_EQ(::video_widevine_server::sdk::LicenseRequest_RequestType_RENEWAL, + license_renewal.type()); + EXPECT_LT(0, license_renewal.request_time()); + EXPECT_EQ(video_widevine_server::sdk::VERSION_2_1, + license_renewal.protocol_version()); + EXPECT_TRUE(license_renewal.has_key_control_nonce()); + } + + void ValidateReleaseRequest(std::string& usage_msg, + int64_t expected_seconds_since_license_received, + int64_t expected_seconds_since_first_playback, + int64_t expected_seconds_since_last_playback) { + // Validate signed renewal request + SignedMessage signed_message; + EXPECT_TRUE(signed_message.ParseFromString(usage_msg)); + EXPECT_EQ(SignedMessage::LICENSE_REQUEST, signed_message.type()); + EXPECT_TRUE(signed_message.has_signature()); + EXPECT_TRUE(!signed_message.msg().empty()); + + // Verify license request + video_widevine_server::sdk::LicenseRequest license_renewal; + EXPECT_TRUE(license_renewal.ParseFromString(signed_message.msg())); + + // Verify Content Identification + const LicenseRequest_ContentIdentification& content_id = + license_renewal.content_id(); + EXPECT_FALSE(content_id.has_cenc_id()); + EXPECT_FALSE(content_id.has_webm_id()); + EXPECT_TRUE(content_id.has_license()); + + const ::video_widevine_server::sdk:: + LicenseRequest_ContentIdentification_ExistingLicense& existing_license = + content_id.license(); + + const LicenseIdentification& id = existing_license.license_id(); + EXPECT_TRUE(std::equal(id.request_id().begin(), id.request_id().end(), + license_id_.request_id().begin())); + EXPECT_TRUE(std::equal(id.session_id().begin(), id.session_id().end(), + license_id_.session_id().begin())); + EXPECT_TRUE(std::equal(id.purchase_id().begin(), id.purchase_id().end(), + license_id_.purchase_id().begin())); + EXPECT_EQ(license_id_.type(), id.type()); + EXPECT_EQ(license_id_.version(), id.version()); + EXPECT_TRUE(!id.provider_session_token().empty()); + + EXPECT_LT(expected_seconds_since_first_playback - kClockTolerance, + existing_license.seconds_since_started()); + EXPECT_LT(existing_license.seconds_since_started(), + expected_seconds_since_first_playback + kClockTolerance); + EXPECT_LT(expected_seconds_since_last_playback - kClockTolerance, + existing_license.seconds_since_last_played()); + EXPECT_LT(existing_license.seconds_since_last_played(), + expected_seconds_since_last_playback + kClockTolerance); + EXPECT_TRUE(!existing_license.session_usage_table_entry().empty()); + + // Verify usage report + OEMCrypto_PST_Report usage_report; + memcpy(&usage_report, existing_license.session_usage_table_entry().data(), + sizeof(usage_report)); + EXPECT_EQ(sizeof(usage_report) + usage_report.pst_length, + existing_license.session_usage_table_entry().size()); + EXPECT_EQ(kInactive, usage_report.status); + EXPECT_EQ(id.provider_session_token().size(), usage_report.pst_length); + std::string pst(existing_license.session_usage_table_entry().data() + + sizeof(OEMCrypto_PST_Report), + usage_report.pst_length); + EXPECT_EQ(id.provider_session_token(), pst); + EXPECT_LE(kInsecureClock, usage_report.clock_security_level); + + int64_t seconds_since_license_received = + htonll64(usage_report.seconds_since_license_received); + int64_t seconds_since_first_decrypt = + htonll64(usage_report.seconds_since_first_decrypt); + int64_t seconds_since_last_decrypt = + htonll64(usage_report.seconds_since_last_decrypt); + // Detect licenses that were never used + if (seconds_since_first_decrypt < 0 || + seconds_since_first_decrypt > seconds_since_license_received) { + seconds_since_first_decrypt = 0; + seconds_since_last_decrypt = 0; + } + EXPECT_LT(expected_seconds_since_license_received - kClockTolerance, + seconds_since_license_received); + EXPECT_LT(seconds_since_license_received, + expected_seconds_since_license_received + kClockTolerance); + EXPECT_LT(expected_seconds_since_first_playback - kClockTolerance, + seconds_since_first_decrypt); + EXPECT_LT(seconds_since_first_decrypt, + expected_seconds_since_first_playback + kClockTolerance); + EXPECT_LT(expected_seconds_since_last_playback - kClockTolerance, + seconds_since_last_decrypt); + EXPECT_LT(seconds_since_last_decrypt, + expected_seconds_since_last_playback + kClockTolerance); + + EXPECT_EQ(::video_widevine_server::sdk::LicenseRequest_RequestType_RELEASE, + license_renewal.type()); + EXPECT_LT(0, license_renewal.request_time()); + EXPECT_EQ(video_widevine_server::sdk::VERSION_2_1, + license_renewal.protocol_version()); + EXPECT_TRUE(license_renewal.has_key_control_nonce()); + } + + void QueryKeyStatus(bool streaming, int64_t* license_duration_remaining, + int64_t* playback_duration_remaining) { + CdmQueryMap query_info; + CdmQueryMap::iterator itr; + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_.QueryKeyStatus(session_id_, &query_info)); + + itr = query_info.find(wvcdm::QUERY_KEY_LICENSE_TYPE); + ASSERT_TRUE(itr != query_info.end()); + if (streaming) { + EXPECT_EQ(wvcdm::QUERY_VALUE_STREAMING, itr->second); + } else { + EXPECT_EQ(wvcdm::QUERY_VALUE_OFFLINE, itr->second); + } + itr = query_info.find(wvcdm::QUERY_KEY_PLAY_ALLOWED); + ASSERT_TRUE(itr != query_info.end()); + EXPECT_EQ(wvcdm::QUERY_VALUE_TRUE, itr->second); + itr = query_info.find(wvcdm::QUERY_KEY_PERSIST_ALLOWED); + ASSERT_TRUE(itr != query_info.end()); + if (streaming) { + EXPECT_EQ(wvcdm::QUERY_VALUE_FALSE, itr->second); + } else { + EXPECT_EQ(wvcdm::QUERY_VALUE_TRUE, itr->second); + } + itr = query_info.find(wvcdm::QUERY_KEY_RENEW_ALLOWED); + ASSERT_TRUE(itr != query_info.end()); + EXPECT_EQ(wvcdm::QUERY_VALUE_TRUE, itr->second); + + std::istringstream ss; + itr = query_info.find(wvcdm::QUERY_KEY_LICENSE_DURATION_REMAINING); + ASSERT_TRUE(itr != query_info.end()); + ss.str(itr->second); + ASSERT_TRUE(ss >> *license_duration_remaining); + EXPECT_LT(0, *license_duration_remaining); + itr = query_info.find(wvcdm::QUERY_KEY_PLAYBACK_DURATION_REMAINING); + ASSERT_TRUE(itr != query_info.end()); + ss.clear(); + ss.str(itr->second); + ASSERT_TRUE(ss >> *playback_duration_remaining); + EXPECT_LT(0, *playback_duration_remaining); + + itr = query_info.find(wvcdm::QUERY_KEY_RENEWAL_SERVER_URL); + ASSERT_TRUE(itr != query_info.end()); + EXPECT_LT(0u, itr->second.size()); + } + + std::string GetSecurityLevel(TestWvCdmClientPropertySet* property_set) { + decryptor_.OpenSession(g_key_system, property_set, &session_id_); + CdmQueryMap query_info; + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_.QuerySessionStatus(session_id_, &query_info)); + CdmQueryMap::iterator itr = + query_info.find(wvcdm::QUERY_KEY_SECURITY_LEVEL); + EXPECT_TRUE(itr != query_info.end()); + decryptor_.CloseSession(session_id_); + return itr->second; + } + + CdmSecurityLevel GetDefaultSecurityLevel() { + std::string level = GetSecurityLevel(NULL).c_str(); + CdmSecurityLevel security_level = kSecurityLevelUninitialized; + if (level.compare(wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1) == 0) { + security_level = kSecurityLevelL1; + } else if (level.compare(wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3) == 0) { + security_level = kSecurityLevelL3; + } else { + EXPECT_TRUE(false); + } + return security_level; + } + + wvcdm::WvContentDecryptionModule decryptor_; + CdmKeyMessage key_msg_; + CdmKeyResponse key_response_; + CdmSessionId session_id_; + CdmKeySetId key_set_id_; + video_widevine_server::sdk::LicenseIdentification license_id_; +}; + +TEST_F(WvCdmExtendedDurationTest, VerifyLicenseRequestTest) { + Provision(); + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + GenerateKeyRequest(g_key_id, kLicenseTypeStreaming); + + EXPECT_TRUE(!key_msg_.empty()); + + // Validate signed request + SignedMessage signed_message; + EXPECT_TRUE(signed_message.ParseFromString(key_msg_)); + EXPECT_EQ(SignedMessage::LICENSE_REQUEST, signed_message.type()); + EXPECT_TRUE(signed_message.has_signature()); + EXPECT_TRUE(!signed_message.msg().empty()); + + // Verify license request + video_widevine_server::sdk::LicenseRequest license_request; + EXPECT_TRUE(license_request.ParseFromString(signed_message.msg())); + + // Verify Client Identification + const ClientIdentification& client_id = license_request.client_id(); + EXPECT_EQ(video_widevine_server::sdk:: + ClientIdentification_TokenType_DEVICE_CERTIFICATE, + client_id.type()); + + EXPECT_LT(0, client_id.client_info_size()); + for (int i = 0; i < client_id.client_info_size(); ++i) { + const ::video_widevine_server::sdk::ClientIdentification_NameValue& + name_value = client_id.client_info(i); + EXPECT_TRUE(!name_value.name().empty()); + EXPECT_TRUE(!name_value.value().empty()); + } + + EXPECT_FALSE(client_id.has_provider_client_token()); + EXPECT_FALSE(client_id.has_license_counter()); + + const ::video_widevine_server::sdk::ClientIdentification_ClientCapabilities& + client_capabilities = client_id.client_capabilities(); + EXPECT_FALSE(client_capabilities.has_client_token()); + EXPECT_TRUE(client_capabilities.has_session_token()); + EXPECT_FALSE(client_capabilities.video_resolution_constraints()); + EXPECT_TRUE(client_capabilities.has_max_hdcp_version()); + EXPECT_TRUE(client_capabilities.has_oem_crypto_api_version()); + + // Verify Content Identification + const LicenseRequest_ContentIdentification& content_id = + license_request.content_id(); + EXPECT_TRUE(content_id.has_cenc_id()); + EXPECT_FALSE(content_id.has_webm_id()); + EXPECT_FALSE(content_id.has_license()); + + const ::video_widevine_server::sdk::LicenseRequest_ContentIdentification_CENC& + cenc_id = content_id.cenc_id(); + EXPECT_TRUE(std::equal(cenc_id.pssh(0).begin(), cenc_id.pssh(0).end(), + g_key_id.begin() + 32)); + EXPECT_EQ(video_widevine_server::sdk::STREAMING, cenc_id.license_type()); + EXPECT_TRUE(cenc_id.has_request_id()); + + // Verify other license request fields + EXPECT_EQ(::video_widevine_server::sdk::LicenseRequest_RequestType_NEW, + license_request.type()); + EXPECT_LT(0, license_request.request_time()); + EXPECT_EQ(video_widevine_server::sdk::VERSION_2_1, + license_request.protocol_version()); + EXPECT_TRUE(license_request.has_key_control_nonce()); + + decryptor_.CloseSession(session_id_); +} + +TEST_F(WvCdmExtendedDurationTest, VerifyLicenseRenewalTest) { + Provision(); + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + GenerateKeyRequest(g_key_id, kLicenseTypeStreaming); + VerifyKeyRequestResponse(g_license_server, g_client_auth, false); + + // Validate signed response + SignedMessage signed_message; + EXPECT_TRUE(signed_message.ParseFromString(key_response_)); + EXPECT_EQ(SignedMessage::LICENSE, signed_message.type()); + EXPECT_TRUE(signed_message.has_signature()); + EXPECT_TRUE(!signed_message.msg().empty()); + + // Verify license + video_widevine_server::sdk::License license; + EXPECT_TRUE(license.ParseFromString(signed_message.msg())); + + // Verify license identification + video_widevine_server::sdk::LicenseIdentification license_id = license.id(); + EXPECT_LT(0, license_id.request_id().size()); + EXPECT_LT(0, license_id.session_id().size()); + EXPECT_EQ(video_widevine_server::sdk::STREAMING, license_id.type()); + EXPECT_FALSE(license_id.has_provider_session_token()); + + // Create renewal request + std::string license_server; + GenerateRenewalRequest(kLicenseTypeStreaming, &license_server); + EXPECT_TRUE(!license_server.empty()); + EXPECT_TRUE(!key_msg_.empty()); + + // Validate signed renewal request + signed_message.Clear(); + EXPECT_TRUE(signed_message.ParseFromString(key_msg_)); + EXPECT_EQ(SignedMessage::LICENSE_REQUEST, signed_message.type()); + EXPECT_TRUE(signed_message.has_signature()); + EXPECT_TRUE(!signed_message.msg().empty()); + + // Verify license request + video_widevine_server::sdk::LicenseRequest license_renewal; + EXPECT_TRUE(license_renewal.ParseFromString(signed_message.msg())); + + // Client Identification not filled in in renewal + + // Verify Content Identification + const LicenseRequest_ContentIdentification& content_id = + license_renewal.content_id(); + EXPECT_FALSE(content_id.has_cenc_id()); + EXPECT_FALSE(content_id.has_webm_id()); + EXPECT_TRUE(content_id.has_license()); + + const ::video_widevine_server::sdk:: + LicenseRequest_ContentIdentification_ExistingLicense& existing_license = + content_id.license(); + + const LicenseIdentification& id = existing_license.license_id(); + EXPECT_TRUE(std::equal(id.request_id().begin(), id.request_id().end(), + license_id.request_id().begin())); + EXPECT_TRUE(std::equal(id.session_id().begin(), id.session_id().end(), + license_id.session_id().begin())); + EXPECT_TRUE(std::equal(id.purchase_id().begin(), id.purchase_id().end(), + license_id.purchase_id().begin())); + EXPECT_EQ(license_id.type(), id.type()); + EXPECT_EQ(license_id.version(), id.version()); + EXPECT_TRUE(id.provider_session_token().empty()); + + EXPECT_EQ(0, existing_license.seconds_since_started()); + EXPECT_EQ(0, existing_license.seconds_since_last_played()); + EXPECT_TRUE(existing_license.session_usage_table_entry().empty()); + + EXPECT_EQ(::video_widevine_server::sdk::LicenseRequest_RequestType_RENEWAL, + license_renewal.type()); + EXPECT_LT(0, license_renewal.request_time()); + EXPECT_EQ(video_widevine_server::sdk::VERSION_2_1, + license_renewal.protocol_version()); + EXPECT_TRUE(license_renewal.has_key_control_nonce()); + + decryptor_.CloseSession(session_id_); +} + +class WvCdmStreamingNoPstTest : public WvCdmExtendedDurationTest, + public ::testing::WithParamInterface {}; + +TEST_P(WvCdmStreamingNoPstTest, UsageTest) { + Unprovision(); + Provision(); + + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + GenerateKeyRequest(g_key_id, kLicenseTypeStreaming); + VerifyKeyRequestResponse(g_license_server, g_client_auth, false); + + ValidateResponse(video_widevine_server::sdk::STREAMING, false); + + int64_t initial_license_duration_remaining = 0; + int64_t initial_playback_duration_remaining = 0; + QueryKeyStatus(true, &initial_license_duration_remaining, + &initial_playback_duration_remaining); + + sleep(kMinute); + int64_t expected_seconds_since_license_received = kMinute; + int64_t expected_seconds_since_initial_playback = 0; + int64_t expected_seconds_since_last_playback = 0; + + for (size_t i = 0; i < GetParam(); ++i) { + // Decrypt data + SubSampleInfo* data = &kEncryptedStreamingNoPstSubSample; + for (size_t i = 0; i < data->num_of_subsamples; i++) { + std::vector decrypt_buffer((data + i)->encrypt_data.size()); + CdmDecryptionParameters decryption_parameters( + &(data + i)->key_id, &(data + i)->encrypt_data.front(), + (data + i)->encrypt_data.size(), &(data + i)->iv, + (data + i)->block_offset, &decrypt_buffer[0]); + decryption_parameters.is_encrypted = (data + i)->is_encrypted; + decryption_parameters.is_secure = (data + i)->is_secure; + decryption_parameters.subsample_flags = (data + i)->subsample_flags; + EXPECT_EQ(NO_ERROR, + decryptor_.Decrypt(session_id_, (data + i)->validate_key_id, + decryption_parameters)); + + EXPECT_TRUE(std::equal((data + i)->decrypt_data.begin(), + (data + i)->decrypt_data.end(), + decrypt_buffer.begin())); + } + + sleep(kMinute); + expected_seconds_since_license_received += kMinute; + expected_seconds_since_initial_playback += kMinute; + expected_seconds_since_last_playback = kMinute; + } + + // Create renewal request and validate + std::string license_server; + GenerateRenewalRequest(kLicenseTypeStreaming, &license_server); + EXPECT_TRUE(!license_server.empty()); + EXPECT_TRUE(!key_msg_.empty()); + + ValidateRenewalRequest(expected_seconds_since_initial_playback, + expected_seconds_since_last_playback, false); + + // Query and validate usage information + int64_t license_duration_remaining = 0; + int64_t playback_duration_remaining = 0; + QueryKeyStatus(true, &license_duration_remaining, + &playback_duration_remaining); + + int64_t delta = + initial_license_duration_remaining - license_duration_remaining; + EXPECT_LT(expected_seconds_since_license_received - kClockTolerance, delta); + EXPECT_LT(delta, expected_seconds_since_license_received + kClockTolerance); + delta = initial_playback_duration_remaining - playback_duration_remaining; + EXPECT_LT(expected_seconds_since_initial_playback - kClockTolerance, delta); + EXPECT_LT(delta, expected_seconds_since_initial_playback + kClockTolerance); + + decryptor_.CloseSession(session_id_); +} + +INSTANTIATE_TEST_CASE_P(Cdm, WvCdmStreamingNoPstTest, + ::testing::Values(0, 1, 2)); + +class WvCdmStreamingPstTest : public WvCdmExtendedDurationTest, + public ::testing::WithParamInterface {}; + +TEST_P(WvCdmStreamingPstTest, UsageTest) { + Unprovision(); + Provision(); + + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + GenerateKeyRequest(kStreamingClip1PstInitData, kLicenseTypeStreaming); + VerifyKeyRequestResponse(g_license_server, g_client_auth, false); + + ValidateResponse(video_widevine_server::sdk::STREAMING, true); + + int64_t initial_license_duration_remaining = 0; + int64_t initial_playback_duration_remaining = 0; + QueryKeyStatus(true, &initial_license_duration_remaining, + &initial_playback_duration_remaining); + + sleep(kMinute); + int64_t expected_seconds_since_license_received = kMinute; + int64_t expected_seconds_since_initial_playback = 0; + int64_t expected_seconds_since_last_playback = 0; + + for (size_t i = 0; i < GetParam(); ++i) { + // Decrypt data + SubSampleInfo* data = &kEncryptedStreamingClip1SubSample; + for (size_t i = 0; i < data->num_of_subsamples; i++) { + std::vector decrypt_buffer((data + i)->encrypt_data.size()); + CdmDecryptionParameters decryption_parameters( + &(data + i)->key_id, &(data + i)->encrypt_data.front(), + (data + i)->encrypt_data.size(), &(data + i)->iv, + (data + i)->block_offset, &decrypt_buffer[0]); + decryption_parameters.is_encrypted = (data + i)->is_encrypted; + decryption_parameters.is_secure = (data + i)->is_secure; + decryption_parameters.subsample_flags = (data + i)->subsample_flags; + EXPECT_EQ(NO_ERROR, + decryptor_.Decrypt(session_id_, (data + i)->validate_key_id, + decryption_parameters)); + + EXPECT_TRUE(std::equal((data + i)->decrypt_data.begin(), + (data + i)->decrypt_data.end(), + decrypt_buffer.begin())); + } + + sleep(kMinute); + expected_seconds_since_license_received += kMinute; + expected_seconds_since_initial_playback += kMinute; + expected_seconds_since_last_playback = kMinute; + } + + // Create renewal request and validate + std::string license_server; + GenerateRenewalRequest(kLicenseTypeStreaming, &license_server); + EXPECT_TRUE(!license_server.empty()); + EXPECT_TRUE(!key_msg_.empty()); + + ValidateRenewalRequest(expected_seconds_since_initial_playback, + expected_seconds_since_last_playback, true); + + // Query and validate usage information + int64_t license_duration_remaining = 0; + int64_t playback_duration_remaining = 0; + QueryKeyStatus(true, &license_duration_remaining, + &playback_duration_remaining); + + int64_t delta = + initial_license_duration_remaining - license_duration_remaining; + EXPECT_LT(expected_seconds_since_license_received - kClockTolerance, delta); + EXPECT_LT(delta, expected_seconds_since_license_received + kClockTolerance); + delta = initial_playback_duration_remaining - playback_duration_remaining; + EXPECT_LT(expected_seconds_since_initial_playback - kClockTolerance, delta); + EXPECT_LT(delta, expected_seconds_since_initial_playback + kClockTolerance); + + decryptor_.CloseSession(session_id_); +} + +INSTANTIATE_TEST_CASE_P(Cdm, WvCdmStreamingPstTest, ::testing::Values(0, 1, 2)); + +class WvCdmStreamingUsageReportTest + : public WvCdmExtendedDurationTest, + public ::testing::WithParamInterface {}; + +TEST_P(WvCdmStreamingUsageReportTest, UsageTest) { + Unprovision(); + Provision(); + + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + GenerateKeyRequest(kStreamingClip1PstInitData, kLicenseTypeStreaming); + VerifyKeyRequestResponse(g_license_server, g_client_auth, false); + + ValidateResponse(video_widevine_server::sdk::STREAMING, true); + + int64_t initial_license_duration_remaining = 0; + int64_t initial_playback_duration_remaining = 0; + QueryKeyStatus(true, &initial_license_duration_remaining, + &initial_playback_duration_remaining); + + sleep(kMinute); + int64_t expected_seconds_since_license_received = kMinute; + int64_t expected_seconds_since_initial_playback = 0; + int64_t expected_seconds_since_last_playback = 0; + + for (size_t i = 0; i < GetParam(); ++i) { + // Decrypt data + SubSampleInfo* data = &kEncryptedStreamingClip1SubSample; + for (size_t i = 0; i < data->num_of_subsamples; i++) { + std::vector decrypt_buffer((data + i)->encrypt_data.size()); + CdmDecryptionParameters decryption_parameters( + &(data + i)->key_id, &(data + i)->encrypt_data.front(), + (data + i)->encrypt_data.size(), &(data + i)->iv, + (data + i)->block_offset, &decrypt_buffer[0]); + decryption_parameters.is_encrypted = (data + i)->is_encrypted; + decryption_parameters.is_secure = (data + i)->is_secure; + decryption_parameters.subsample_flags = (data + i)->subsample_flags; + EXPECT_EQ(NO_ERROR, + decryptor_.Decrypt(session_id_, (data + i)->validate_key_id, + decryption_parameters)); + + EXPECT_TRUE(std::equal((data + i)->decrypt_data.begin(), + (data + i)->decrypt_data.end(), + decrypt_buffer.begin())); + } + + sleep(kMinute); + expected_seconds_since_license_received += kMinute; + expected_seconds_since_initial_playback += kMinute; + expected_seconds_since_last_playback = kMinute; + } + + // Query and validate usage information + int64_t license_duration_remaining = 0; + int64_t playback_duration_remaining = 0; + QueryKeyStatus(true, &license_duration_remaining, + &playback_duration_remaining); + + int64_t delta = + initial_license_duration_remaining - license_duration_remaining; + EXPECT_LT(expected_seconds_since_license_received - kClockTolerance, delta); + EXPECT_LT(delta, expected_seconds_since_license_received + kClockTolerance); + delta = initial_playback_duration_remaining - playback_duration_remaining; + EXPECT_LT(expected_seconds_since_initial_playback - kClockTolerance, delta); + EXPECT_LT(delta, expected_seconds_since_initial_playback + kClockTolerance); + + decryptor_.CloseSession(session_id_); + + // Create usage report and validate + uint32_t num_usage_info = 0; + CdmUsageInfo usage_info; + CdmUsageInfoReleaseMessage release_msg; + CdmResponseType status = decryptor_.GetUsageInfo(&usage_info); + EXPECT_EQ(usage_info.empty() ? NO_ERROR : KEY_MESSAGE, status); + while (usage_info.size() > 0) { + for (size_t i = 0; i < usage_info.size(); ++i) { + ValidateReleaseRequest(usage_info[i], + expected_seconds_since_license_received, + expected_seconds_since_initial_playback, + expected_seconds_since_last_playback); + release_msg = + GetUsageInfoResponse(g_license_server, g_client_auth, usage_info[i]); + EXPECT_EQ(NO_ERROR, decryptor_.ReleaseUsageInfo(release_msg)); + } + status = decryptor_.GetUsageInfo(&usage_info); + switch (status) { + case KEY_MESSAGE: + EXPECT_FALSE(usage_info.empty()); + break; + case NO_ERROR: + EXPECT_TRUE(usage_info.empty()); + break; + default: + FAIL() << "GetUsageInfo failed with error " << static_cast(status); + break; + } + } +} + +INSTANTIATE_TEST_CASE_P(Cdm, WvCdmStreamingUsageReportTest, + ::testing::Values(0, 1, 2)); + +class WvCdmOfflineUsageReportTest + : public WvCdmExtendedDurationTest, + public ::testing::WithParamInterface {}; + +TEST_P(WvCdmOfflineUsageReportTest, UsageTest) { + Unprovision(); + Provision(); + + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + GenerateKeyRequest(kOfflineClip2PstInitData, kLicenseTypeOffline); + VerifyKeyRequestResponse(g_license_server, g_client_auth, false); + + ValidateResponse(video_widevine_server::sdk::OFFLINE, true); + + CdmKeySetId key_set_id = key_set_id_; + EXPECT_FALSE(key_set_id_.empty()); + + int64_t initial_license_duration_remaining = 0; + int64_t initial_playback_duration_remaining = 0; + QueryKeyStatus(false, &initial_license_duration_remaining, + &initial_playback_duration_remaining); + + decryptor_.CloseSession(session_id_); + + sleep(kMinute); + int64_t expected_seconds_since_license_received = kMinute; + int64_t expected_seconds_since_initial_playback = 0; + int64_t expected_seconds_since_last_playback = 0; + + for (size_t i = 0; i < GetParam(); ++i) { + session_id_.clear(); + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + EXPECT_EQ(wvcdm::KEY_ADDED, decryptor_.RestoreKey(session_id_, key_set_id)); + + // Query and validate usage information + int64_t license_duration_remaining = 0; + int64_t playback_duration_remaining = 0; + QueryKeyStatus(false, &license_duration_remaining, + &playback_duration_remaining); + + int64_t delta = + initial_license_duration_remaining - license_duration_remaining; + EXPECT_LT(expected_seconds_since_license_received - kClockTolerance, delta); + EXPECT_LT(delta, expected_seconds_since_license_received + kClockTolerance); + delta = initial_playback_duration_remaining - playback_duration_remaining; + EXPECT_LT(expected_seconds_since_initial_playback - kClockTolerance, delta); + EXPECT_LT(delta, expected_seconds_since_initial_playback + kClockTolerance); + + // Decrypt data + SubSampleInfo* data = &kEncryptedOfflineClip2SubSample; + for (size_t i = 0; i < data->num_of_subsamples; i++) { + std::vector decrypt_buffer((data + i)->encrypt_data.size()); + CdmDecryptionParameters decryption_parameters( + &(data + i)->key_id, &(data + i)->encrypt_data.front(), + (data + i)->encrypt_data.size(), &(data + i)->iv, + (data + i)->block_offset, &decrypt_buffer[0]); + decryption_parameters.is_encrypted = (data + i)->is_encrypted; + decryption_parameters.is_secure = (data + i)->is_secure; + decryption_parameters.subsample_flags = (data + i)->subsample_flags; + EXPECT_EQ(NO_ERROR, + decryptor_.Decrypt(session_id_, (data + i)->validate_key_id, + decryption_parameters)); + + EXPECT_TRUE(std::equal((data + i)->decrypt_data.begin(), + (data + i)->decrypt_data.end(), + decrypt_buffer.begin())); + } + + sleep(10); + decryptor_.CloseSession(session_id_); + + sleep(kMinute - 10); + expected_seconds_since_license_received += kMinute; + expected_seconds_since_initial_playback += kMinute; + expected_seconds_since_last_playback = kMinute; + } + + session_id_.clear(); + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + EXPECT_EQ(wvcdm::KEY_ADDED, decryptor_.RestoreKey(session_id_, key_set_id)); + + // Query and validate usage information + int64_t license_duration_remaining = 0; + int64_t playback_duration_remaining = 0; + QueryKeyStatus(false, &license_duration_remaining, + &playback_duration_remaining); + + int64_t delta = + initial_license_duration_remaining - license_duration_remaining; + EXPECT_LT(expected_seconds_since_license_received - kClockTolerance, delta); + EXPECT_LT(delta, expected_seconds_since_license_received + kClockTolerance); + delta = initial_playback_duration_remaining - playback_duration_remaining; + EXPECT_LT(expected_seconds_since_initial_playback - kClockTolerance, delta); + EXPECT_LT(delta, expected_seconds_since_initial_playback + kClockTolerance); + + decryptor_.CloseSession(session_id_); + + session_id_.clear(); + key_set_id_.clear(); + GenerateKeyRelease(key_set_id); + ValidateReleaseRequest(key_msg_, expected_seconds_since_license_received, + expected_seconds_since_initial_playback, + expected_seconds_since_last_playback); + key_set_id_ = key_set_id; + VerifyKeyRequestResponse(g_license_server, g_client_auth, false); +} + +INSTANTIATE_TEST_CASE_P(Cdm, WvCdmOfflineUsageReportTest, + ::testing::Values(0, 1, 2)); +} // namespace wvcdm + +void show_menu(char* prog_name) { + std::cout << std::endl; + std::cout << "usage: " << prog_name << " [options]" << std::endl << std::endl; + std::cout << " enclose multiple arguments in '' when using adb shell" + << std::endl; + std::cout << " e.g. adb shell '" << prog_name << " --server=\"url\"'" + << std::endl; + std::cout << " or adb shell '" << prog_name << " -u\"url\"'" << std::endl + << std::endl; + + std::cout << std::setw(35) << std::left << " -f/--use_full_path"; + std::cout << "specify server url is not a proxy server" << std::endl; + std::cout << std::endl; + + std::cout << std::setw(35) << std::left << " -i/--license_server_id="; + std::cout << "specifies which default server settings to use: " << std::endl; + std::cout << std::setw(35) << std::left << " "; + std::cout << "gp (case sensitive) for GooglePlay server" << std::endl; + std::cout << std::setw(35) << std::left << " "; + std::cout << "cp (case sensitive) for Content Protection server" << std::endl + << std::endl; + + std::cout << std::setw(35) << std::left << " -k/--keyid="; + std::cout << "configure the key id or pssh, in hex format" << std::endl + << std::endl; + + std::cout << std::setw(35) << std::left << " -p/--port="; + std::cout << "specifies the connection port" << std::endl << std::endl; + + std::cout << std::setw(35) << std::left << " -s/--secure_transfer"; + std::cout << "use https transfer protocol" << std::endl << std::endl; + + std::cout << std::setw(35) << std::left << " -u/--server="; + std::cout + << "configure the license server url, please include http[s] in the url" + << std::endl << std::endl; +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + + bool show_usage = false; + static const struct option long_options[] = { + {"keyid", required_argument, NULL, 'k'}, + {"license_server_id", required_argument, NULL, 'i'}, + {"license_server_url", required_argument, NULL, 'u'}, + {NULL, 0, NULL, '\0'}}; + + int option_index = 0; + int opt = 0; + while ((opt = getopt_long(argc, argv, "i:k:u:", long_options, + &option_index)) != -1) { + switch (opt) { + case 'i': { + std::string license_id(optarg); + if (!license_id.compare("gp")) { + g_license_server_id = wvcdm::kGooglePlayServer; + } else if (!license_id.compare("cp")) { + g_license_server_id = wvcdm::kContentProtectionServer; + } else { + std::cout << "Invalid license server id" << optarg << std::endl; + show_usage = true; + } + break; + } + case 'k': { + g_key_id.clear(); + g_key_id.assign(optarg); + break; + } + case 'u': { + g_license_server.clear(); + g_license_server.assign(optarg); + break; + } + case '?': { + show_usage = true; + break; + } + } + } + + if (show_usage) { + show_menu(argv[0]); + return 0; + } + + g_config = new wvcdm::ConfigTestEnv(g_license_server_id); + g_client_auth.assign(g_config->client_auth()); + g_key_system.assign(g_config->key_system()); + g_wrong_key_id.assign(g_config->wrong_key_id()); + + // The following variables are configurable through command line + // options. If the command line arguments are absent, use the settings + // in kLicenseServers[] pointed to by g_config. + if (g_key_id.empty()) { + g_key_id.assign(g_config->key_id()); + } + if (g_license_server.empty()) { + g_license_server.assign(g_config->license_server()); + } + + // Displays server url, port and key Id being used + std::cout << std::endl; + std::cout << "Server: " << g_license_server << std::endl; + std::cout << "KeyID: " << g_key_id << std::endl << std::endl; + + g_key_id = wvcdm::a2bs_hex(g_key_id); + g_config->set_license_server(g_license_server); + + int status = RUN_ALL_TESTS(); + delete g_config; + return status; +} diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index 9eb33715..d6e1f25c 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -581,8 +581,49 @@ class WvCdmRequestLicenseTest : public testing::Test { } } + void Unprovision() { + EXPECT_EQ(NO_ERROR, decryptor_.Unprovision(kSecurityLevelL1)); + EXPECT_EQ(NO_ERROR, decryptor_.Unprovision(kSecurityLevelL3)); + } + + void Provision() { + CdmResponseType status = + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + switch (status) { + case NO_ERROR: + decryptor_.CloseSession(session_id_); + return; + case NEED_PROVISIONING: + break; + default: + EXPECT_EQ(NO_ERROR, status); + return; + } + + std::string provisioning_server_url; + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority, cert, wrapped_key; + + status = decryptor_.GetProvisioningRequest( + cert_type, cert_authority, &key_msg_, &provisioning_server_url); + EXPECT_EQ(wvcdm::NO_ERROR, status); + if (NO_ERROR != status) return; + EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url()); + + std::string response = + GetCertRequestResponse(g_config->provisioning_server_url()); + EXPECT_NE(0, static_cast(response.size())); + EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.HandleProvisioningResponse( + response, &cert, &wrapped_key)); + EXPECT_EQ(0, static_cast(cert.size())); + EXPECT_EQ(0, static_cast(wrapped_key.size())); + decryptor_.CloseSession(session_id_); + return; + } + std::string GetSecurityLevel(TestWvCdmClientPropertySet* property_set) { - decryptor_.OpenSession(g_key_system, property_set, &session_id_); + EXPECT_EQ(NO_ERROR, + decryptor_.OpenSession(g_key_system, property_set, &session_id_)); CdmQueryMap query_info; EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.QuerySessionStatus(session_id_, &query_info)); @@ -864,6 +905,9 @@ TEST_F(WvCdmRequestLicenseTest, AddStreamingKeyTest) { } TEST_F(WvCdmRequestLicenseTest, AddKeyOfflineTest) { + Unprovision(); + Provision(); + // override default settings unless configured through the command line std::string key_id; std::string client_auth; @@ -876,6 +920,9 @@ TEST_F(WvCdmRequestLicenseTest, AddKeyOfflineTest) { } TEST_F(WvCdmRequestLicenseTest, RestoreOfflineKeyTest) { + Unprovision(); + Provision(); + // override default settings unless configured through the command line std::string key_id; std::string client_auth; @@ -896,6 +943,9 @@ TEST_F(WvCdmRequestLicenseTest, RestoreOfflineKeyTest) { } TEST_F(WvCdmRequestLicenseTest, ReleaseOfflineKeyTest) { + Unprovision(); + Provision(); + // override default settings unless configured through the command line std::string key_id; std::string client_auth; @@ -923,6 +973,9 @@ TEST_F(WvCdmRequestLicenseTest, ReleaseOfflineKeyTest) { } TEST_F(WvCdmRequestLicenseTest, ReleaseRetryOfflineKeyTest) { + Unprovision(); + Provision(); + // override default settings unless configured through the command line std::string key_id; std::string client_auth; @@ -959,6 +1012,9 @@ TEST_F(WvCdmRequestLicenseTest, ReleaseRetryOfflineKeyTest) { } TEST_F(WvCdmRequestLicenseTest, ExpiryOnReleaseOfflineKeyTest) { + Unprovision(); + Provision(); + // override default settings unless configured through the command line std::string key_id; std::string client_auth; @@ -1034,7 +1090,7 @@ class WvCdmUsageInfoTest : public WvCdmRequestLicenseTest, public ::testing::WithParamInterface {}; -TEST_P(WvCdmUsageInfoTest, DISABLED_UsageInfo) { +TEST_P(WvCdmUsageInfoTest, UsageInfo) { CdmSecurityLevel security_level = GetDefaultSecurityLevel(); DeviceFiles handle; EXPECT_TRUE(handle.Init(security_level));