diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index e0527117..64ff516d 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -100,6 +100,8 @@ class CdmSession { is_usage_update_needed_ = false; } + bool DeleteLicense(); + private: // Internal constructor void Create(CdmLicense* license_parser, CryptoSession* crypto_session, @@ -112,7 +114,6 @@ class CdmSession { CdmResponseType StoreLicense(); bool StoreLicense(DeviceFiles::LicenseState state); - bool DeleteLicense(); // instance variables bool initialized_; diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h index ac225651..a02e5404 100644 --- a/libwvdrmengine/cdm/core/include/device_files.h +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -108,7 +108,9 @@ class DeviceFiles { FRIEND_TEST(DeviceFilesUsageInfoTest, Store); FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest); FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test); + FRIEND_TEST(WvCdmRequestLicenseTest, UsageInfoRetryTest); FRIEND_TEST(WvCdmUsageInfoTest, UsageInfo); + FRIEND_TEST(WvCdmExtendedDurationTest, UsageOverflowTest); #endif scoped_ptr file_; diff --git a/libwvdrmengine/cdm/core/include/license.h b/libwvdrmengine/cdm/core/include/license.h index ac4c502b..e88dd214 100644 --- a/libwvdrmengine/cdm/core/include/license.h +++ b/libwvdrmengine/cdm/core/include/license.h @@ -36,8 +36,8 @@ class CdmLicense { const CdmSessionId& session_id, CdmKeyMessage* signed_request, std::string* server_url); - virtual bool PrepareKeyUpdateRequest(bool is_renewal, CdmKeyMessage* signed_request, - std::string* server_url); + virtual CdmResponseType PrepareKeyUpdateRequest( + bool is_renewal, CdmKeyMessage* signed_request, std::string* server_url); virtual CdmResponseType HandleKeyResponse(const CdmKeyResponse& license_response); virtual CdmResponseType HandleKeyUpdateResponse( bool is_renewal, const CdmKeyResponse& license_response); diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 3c91e2d1..905cb0c4 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -654,16 +654,22 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, CdmUsageInfo* usage_info) { // Return a random usage report from a random security level SecurityLevel security_level = ((rand() % 2) == 0) ? kLevelDefault : kLevel3; - CdmResponseType status = GetUsageInfo(app_id, security_level, usage_info); + CdmResponseType status = UNKNOWN_ERROR; + do { + status = GetUsageInfo(app_id, security_level, usage_info); + + if (KEY_MESSAGE == status && !usage_info->empty()) + return status; + } while (KEY_CANCELED == status); - if (KEY_MESSAGE == status && !usage_info->empty()) - return status; security_level = (kLevel3 == security_level) ? kLevelDefault : kLevel3; - status = GetUsageInfo(app_id, security_level, usage_info); - if (NEED_PROVISIONING == status) - return NO_ERROR; // Valid scenario that one of the security - // levels has not been provisioned + do { + status = GetUsageInfo(app_id, security_level, usage_info); + if (NEED_PROVISIONING == status) + return NO_ERROR; // Valid scenario that one of the security + // levels has not been provisioned + } while (KEY_CANCELED == status); return status; } @@ -716,13 +722,20 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, status = usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url); - if (KEY_MESSAGE != status) { - LOGE("CdmEngine::GetUsageInfo: generate release request error: %d", - status); - usage_info->clear(); - return status; + switch (status) { + case KEY_MESSAGE: + break; + case KEY_CANCELED: // usage information not present in + usage_session_->DeleteLicense(); // OEMCrypto, delete and try again + usage_info->clear(); + break; + default: + LOGE("CdmEngine::GetUsageInfo: generate release request error: %d", + status); + usage_info->clear(); + break; } - return KEY_MESSAGE; + return status; } CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) { diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 51006d21..35f692fa 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -385,11 +385,11 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) { // session keys. CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request, std::string* server_url) { - if (!license_parser_->PrepareKeyUpdateRequest(true, key_request, - server_url)) { - LOGE("CdmSession::GenerateRenewalRequest: ERROR on prepare"); - return KEY_ERROR; - } + CdmResponseType status = + license_parser_->PrepareKeyUpdateRequest(true, key_request, server_url); + + if (KEY_MESSAGE != status) + return status; if (is_offline_) { offline_key_renewal_request_ = *key_request; @@ -413,8 +413,11 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) { CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request, std::string* server_url) { is_release_ = true; - if (!license_parser_->PrepareKeyUpdateRequest(false, key_request, server_url)) - return UNKNOWN_ERROR; + CdmResponseType status = + license_parser_->PrepareKeyUpdateRequest(false, key_request, server_url); + + if (KEY_MESSAGE != status) + return status; if (is_offline_) { // Mark license as being released if (!StoreLicense(DeviceFiles::kLicenseStateReleasing)) diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 3514985b..4a82a557 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -716,13 +716,19 @@ CdmResponseType CryptoSession::DeactivateUsageInformation( OEMCryptoResult status = OEMCrypto_DeactivateUsageEntry(pst, provider_session_token.length()); - if (OEMCrypto_SUCCESS != status) { - LOGE("CryptoSession::DeactivateUsageInformation: Deactivate Usage Entry " - " error=%ld", + switch(status) { + case OEMCrypto_SUCCESS: + return NO_ERROR; + case OEMCrypto_ERROR_INVALID_CONTEXT: + LOGE("CryptoSession::DeactivateUsageInformation: Deactivate Usage Entry " + " invalid context error"); + return KEY_CANCELED; + default: + LOGE("CryptoSession::DeactivateUsageInformation: Deactivate Usage Entry " + " error=%ld", status); return UNKNOWN_ERROR; } - return NO_ERROR; } CdmResponseType CryptoSession::GenerateUsageReport( diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index 185c39df..8a6e9f5b 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -476,20 +476,21 @@ bool CdmLicense::PrepareKeyRequest(const InitializationData& init_data, return true; } -bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, - CdmKeyMessage* signed_request, - std::string* server_url) { +CdmResponseType CdmLicense::PrepareKeyUpdateRequest( + bool is_renewal, + CdmKeyMessage* signed_request, + std::string* server_url) { if (!initialized_) { LOGE("CdmLicense::PrepareKeyUpdateRequest: not initialized"); - return false; + return UNKNOWN_ERROR; } if (!signed_request) { LOGE("CdmLicense::PrepareKeyUpdateRequest: No signed request provided"); - return false; + return UNKNOWN_ERROR; } if (!server_url) { LOGE("CdmLicense::PrepareKeyUpdateRequest: No server url provided"); - return false; + return UNKNOWN_ERROR; } LicenseRequest license_request; @@ -513,7 +514,7 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, CdmResponseType status = session_->DeactivateUsageInformation(provider_session_token_); if (NO_ERROR != status) - return false; + return status; } std::string usage_report; @@ -526,7 +527,7 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, if (NO_ERROR == status) current_license->set_session_usage_table_entry(usage_report); else - return false; + return KEY_ERROR; } } @@ -545,7 +546,7 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, // of the license response. uint32_t nonce; if (!session_->GenerateNonce(&nonce)) { - return false; + return KEY_ERROR; } license_request.set_key_control_nonce(nonce); LOGD("PrepareKeyUpdateRequest: nonce=%u", nonce); @@ -559,13 +560,13 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, std::string license_request_signature; if (!session_->PrepareRenewalRequest(serialized_license_req, &license_request_signature)) - return false; + return KEY_ERROR; if (license_request_signature.empty()) { LOGE( "CdmLicense::PrepareKeyUpdateRequest: empty license request" " signature"); - return false; + return KEY_ERROR; } // Put serialize license request and signature together @@ -576,7 +577,7 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, signed_message.SerializeToString(signed_request); *server_url = server_url_; - return true; + return KEY_MESSAGE; } CdmResponseType CdmLicense::HandleKeyResponse( diff --git a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp index 20fc230f..92ee187d 100644 --- a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp +++ b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp @@ -32,6 +32,8 @@ const uint32_t kMinute = 60; const uint32_t kClockTolerance = 5; const uint32_t kTwoMinutes = 120; +const uint32_t kMaxUsageTableSize = 50; + // 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; @@ -116,6 +118,29 @@ SubSampleInfo kEncryptedStreamingClip1SubSample = { "37326df26fa509343faa98dff667629f557873f1284903202e451227ef465a62"), wvcdm::a2b_hex("7362b5140c4ce0cd5f863858668d3f1a"), 0, 3}; +SubSampleInfo kEncryptedStreamingClip5SubSample = { + true, 1, true, true, false, + wvcdm::a2bs_hex("3AE243D83B93B3311A1D777FF5FBE01A"), + wvcdm::a2b_hex( + "934997779aa1aeb45d6ba8845f13786575d0adf85a5e93674d9597f8d4286ed7" + "dcce02f306e502bbd9f1cadf502f354038ca921276d158d911bdf3171d335b18" + "0ae0f9abece16ff31ee263228354f724da2f3723b19caa38ea02bd6563b01208" + "fb5bf57854ac0fe38d5883197ef90324b2721ff20fdcf9a53819515e6daa096e" + "70f6f5c1d29a4a13dafd127e2e1f761ea0e28fd451607552ecbaef5da3c780bc" + "aaf2667b4cc4f858f01d480cac9e32c3fbb5705e5d2adcceebefc2535c117208" + "e65f604799fc3d7223e16908550f287a4bea687008cb0064cf14d3aeedb8c705" + "09ebc5c2b8b5315f43c04d78d2f55f4b32c7d33e157114362106395cc0bb6d93"), + wvcdm::a2b_hex( + "2dd54eee1307753508e1f250d637044d6e8f5abf057dab73e9e95f83910e4efc" + "191c9bac63950f13fd51833dd94a4d03f2b64fb5c721970c418fe53fa6f74ad5" + "a6e16477a35c7aa6e28909b069cd25770ef80da20918fc30fe95fd5c87fd3522" + "1649de17ca2c7b3dc31f936f0cbdf97c7b1c15de3a86b279dc4b4de64943914a" + "99734556c4b7a1a0b022c1933cb0786068fc18d49fed2f2b49f3ac6d01c32d07" + "92175ce2844eaf9064e6a3fcffade038d690cbed81659351163a22432f0d0545" + "037e1c805d8e92a1272b4196ad0ce22f26bb80063137a8e454d3b97e2414283d" + "ed0716cd8bceb80cf59166a217006bd147c51b04dfb183088ce3f51e9b9f759e"), + wvcdm::a2b_hex("b358ab21ac90455bbf60490daad457e3"), 0, 3}; + SubSampleInfo kEncryptedOfflineClip2SubSample = { true, 1, true, true, false, wvcdm::a2bs_hex("3260F39E12CCF653529990168A3583FF"), @@ -150,6 +175,7 @@ std::string kOfflineClip2PstInitData = wvcdm::a2bs_hex( "EDEF8BA979D64ACEA3C827DCD51D21ED00000020" // Widevine system id "08011a0d7769646576696e655f74657374220d6f" // pssh data "66666c696e655f636c697032"); + } // namespace namespace wvcdm { @@ -167,6 +193,7 @@ class TestWvCdmClientPropertySet : public CdmClientPropertySet { session_sharing_id_(0) {} virtual ~TestWvCdmClientPropertySet() {} + virtual const std::string& app_id() const { return app_id_; } virtual const std::string& security_level() const { return security_level_; } virtual const std::string& service_certificate() const { return service_certificate_; @@ -177,6 +204,7 @@ class TestWvCdmClientPropertySet : public CdmClientPropertySet { } virtual uint32_t session_sharing_id() const { return session_sharing_id_; } + void set_app_id(const std::string& app_id) { app_id_ = app_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)) { @@ -195,6 +223,7 @@ class TestWvCdmClientPropertySet : public CdmClientPropertySet { void set_session_sharing_id(uint32_t id) { session_sharing_id_ = id; } private: + std::string app_id_; std::string security_level_; std::string service_certificate_; bool use_privacy_mode_; @@ -794,6 +823,53 @@ TEST_F(WvCdmExtendedDurationTest, VerifyLicenseRenewalTest) { decryptor_.CloseSession(session_id_); } +TEST_F(WvCdmExtendedDurationTest, UsageOverflowTest) { + Provision(); + SubSampleInfo* data = &kEncryptedStreamingClip5SubSample; + TestWvCdmClientPropertySet client_property_set; + TestWvCdmClientPropertySet* property_set = NULL; + + CdmSecurityLevel security_level = GetDefaultSecurityLevel(); + DeviceFiles handle; + EXPECT_TRUE(handle.Init(security_level)); + File file; + handle.SetTestFile(&file); + EXPECT_TRUE(handle.DeleteAllUsageInfoForApp("")); + + for (size_t i = 0; i < kMaxUsageTableSize + 100; ++i) { + decryptor_.OpenSession(g_key_system, property_set, &session_id_); + std::string key_id = a2bs_hex( + "000000427073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id + "08011a0d7769646576696e655f74657374220f73" // pssh data + "747265616d696e675f636c697035"); + + GenerateKeyRequest(key_id, kLicenseTypeStreaming); + VerifyKeyRequestResponse(g_license_server, g_client_auth, false); + decryptor_.CloseSession(session_id_); + } + + 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) { + 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; + } + } +} + class WvCdmStreamingNoPstTest : public WvCdmExtendedDurationTest, public ::testing::WithParamInterface {}; diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index f931a1b9..0f8dc0e7 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -1200,6 +1200,68 @@ TEST_F(WvCdmRequestLicenseTest, RemoveKeys) { ASSERT_EQ(NO_ERROR, decryptor_.CloseSession(session_id_)); } +TEST_F(WvCdmRequestLicenseTest, UsageInfoRetryTest) { + Unprovision(); + Provision(kLevelDefault); + + CdmSecurityLevel security_level = GetDefaultSecurityLevel(); + std::string app_id = ""; + DeviceFiles handle; + EXPECT_TRUE(handle.Init(security_level)); + File file; + handle.SetTestFile(&file); + EXPECT_TRUE(handle.DeleteAllUsageInfoForApp(app_id)); + + SubSampleInfo* data = &usage_info_sub_samples_icp[0]; + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + std::string key_id = a2bs_hex( + "000000427073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id + "08011a0d7769646576696e655f74657374220f73" // pssh data + "747265616d696e675f636c697033"); + + GenerateKeyRequest(key_id, kLicenseTypeStreaming, NULL); + VerifyKeyRequestResponse(g_license_server, g_client_auth, false); + + std::vector decrypt_buffer(data->encrypt_data.size()); + CdmDecryptionParameters decryption_parameters( + &data->key_id, &data->encrypt_data.front(), data->encrypt_data.size(), + &data->iv, data->block_offset, &decrypt_buffer[0]); + decryption_parameters.is_encrypted = data->is_encrypted; + decryption_parameters.is_secure = data->is_secure; + EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id_, data->validate_key_id, + decryption_parameters)); + + EXPECT_TRUE(std::equal(data->decrypt_data.begin(), data->decrypt_data.end(), + decrypt_buffer.begin())); + decryptor_.CloseSession(session_id_); + + uint32_t num_usage_info = 0; + CdmUsageInfo usage_info; + CdmUsageInfoReleaseMessage release_msg; + CdmResponseType status = decryptor_.GetUsageInfo(app_id, &usage_info); + EXPECT_EQ(usage_info.empty() ? NO_ERROR : KEY_MESSAGE, status); + + // Discard and retry to verify usage reports can be generated multiple times + // before release. + status = decryptor_.GetUsageInfo(app_id, &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) { + release_msg = + GetUsageInfoResponse(g_license_server, g_client_auth, usage_info[i]); + EXPECT_EQ(NO_ERROR, decryptor_.ReleaseUsageInfo(release_msg)); + } + status = decryptor_.GetUsageInfo(app_id, &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; + } + } +} + class WvCdmUsageInfoTest : public WvCdmRequestLicenseTest, public ::testing::WithParamInterface {};