From 481a1effcb809a5f3acb2f9c860f9b5ad5f0051e Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Fri, 18 Aug 2017 15:08:06 -0700 Subject: [PATCH] Create new usage header if old one corrupted Merge of http://go/wvgerrit/31561 If the usage table header is corrupted, or if it is stale, then the CDM should create a new one. bug: 64572642 Testing: pushed stale usage table to Fugu, and netflix recovered gracefully. New unit test UsageTableHeaderTest.StaleHeader. Change-Id: Ic66854ff6b0b252a0f4ca20e09f27852a50d6fcc --- libwvdrmengine/cdm/core/src/cdm_session.cpp | 26 ++++++++++++------- .../cdm/core/src/usage_table_header.cpp | 20 +++++++------- .../core/test/usage_table_header_unittest.cpp | 26 ++++++++++++++++++- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 073d01d6..82ba5696 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -225,7 +225,8 @@ CdmResponseType CdmSession::RestoreOfflineSession( } if (usage_support_type_ == kUsageEntrySupport && - provider_session_token.size() > 0) { + provider_session_token.size() > 0 && + usage_table_header_ != nullptr) { CdmResponseType sts = usage_table_header_->LoadEntry(crypto_session_.get(), usage_entry_, usage_entry_number_); @@ -250,7 +251,8 @@ CdmResponseType CdmSession::RestoreOfflineSession( } if (usage_support_type_ == kUsageEntrySupport && - provider_session_token.size() > 0) { + provider_session_token.size() > 0 && + usage_table_header_ != nullptr) { CdmResponseType sts = usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); if (sts != NO_ERROR) { @@ -282,9 +284,10 @@ CdmResponseType CdmSession::RestoreUsageSession( usage_entry_number_ = usage_data.usage_entry_number; usage_provider_session_token_ = usage_data.provider_session_token; - if (usage_support_type_ == kUsageEntrySupport) { + if (usage_support_type_ == kUsageEntrySupport && + usage_table_header_ != nullptr) { CdmResponseType sts = usage_table_header_->LoadEntry( - crypto_session_.get(), usage_entry_, usage_entry_number_); + crypto_session_.get(), usage_entry_, usage_entry_number_); if (sts != NO_ERROR) { LOGE("CdmSession::RestoreUsageSession: failed to load usage entry = %d", sts); @@ -296,7 +299,8 @@ CdmResponseType CdmSession::RestoreUsageSession( return RELEASE_LICENSE_ERROR_2; } - if (usage_support_type_ == kUsageEntrySupport) { + if (usage_support_type_ == kUsageEntrySupport && + usage_table_header_ != nullptr) { CdmResponseType sts = usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); if (sts != NO_ERROR) { @@ -440,7 +444,8 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) { // to be created. CdmResponseType sts; std::string provider_session_token; - if (usage_support_type_ == kUsageEntrySupport) { + if (usage_support_type_ == kUsageEntrySupport && + usage_table_header_ != nullptr) { if (license_parser_->ExtractProviderSessionToken( key_response, &provider_session_token) && !provider_session_token.empty()) { @@ -456,7 +461,8 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) { // Update or delete entry if usage table header+entries are supported if (usage_support_type_ == kUsageEntrySupport && - !provider_session_token.empty()) { + !provider_session_token.empty() && + usage_table_header_ != nullptr) { if (sts != KEY_ADDED) { CdmResponseType sts = usage_table_header_->DeleteEntry(usage_entry_number_, @@ -475,7 +481,8 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) { if (is_offline_ || has_provider_session_token()) { if (has_provider_session_token() && - usage_support_type_ == kUsageEntrySupport) { + usage_support_type_ == kUsageEntrySupport && + usage_table_header_ != nullptr) { usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); } @@ -886,7 +893,8 @@ CdmResponseType CdmSession::UpdateUsageTableInformation() { CdmResponseType CdmSession::UpdateUsageEntryInformation() { if (usage_support_type_ != kUsageEntrySupport || - !has_provider_session_token()) { + !has_provider_session_token() && + usage_table_header_ != nullptr) { LOGE("CdmSession::UpdateUsageEntryInformation: Unexpected usage type " "supported: %d", usage_support_type_); return INCORRECT_USAGE_SUPPORT_TYPE_2; diff --git a/libwvdrmengine/cdm/core/src/usage_table_header.cpp b/libwvdrmengine/cdm/core/src/usage_table_header.cpp index dc547ace..d34b26bb 100644 --- a/libwvdrmengine/cdm/core/src/usage_table_header.cpp +++ b/libwvdrmengine/cdm/core/src/usage_table_header.cpp @@ -45,22 +45,24 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, return false; } - if (!file_handle_->RetrieveUsageTableInfo(&usage_table_header_, + CdmResponseType status = USAGE_INFO_NOT_FOUND; + if (file_handle_->RetrieveUsageTableInfo(&usage_table_header_, &usage_entry_info_)) { - CdmResponseType status = - crypto_session->CreateUsageTableHeader(&usage_table_header_); - if (status != NO_ERROR) return false; - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); - } else { - CdmResponseType status = - crypto_session->LoadUsageTableHeader(usage_table_header_); + status = crypto_session->LoadUsageTableHeader(usage_table_header_); if (status != NO_ERROR) { LOGE( "UsageTableHeader::Init: load usage table failed, security level: %d", security_level); - return false; } } + if (status != NO_ERROR) { + file_handle_->DeleteAllLicenses(); + usage_entry_info_.clear(); + status = crypto_session->CreateUsageTableHeader(&usage_table_header_); + if (status != NO_ERROR) return false; + file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + } + requested_security_level_ = security_level_ == kSecurityLevelL3 ? kLevel3 : kLevelDefault; is_inited_ = true; diff --git a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp index 075a40ac..64bb0db6 100644 --- a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp @@ -82,6 +82,7 @@ class MockDeviceFiles : public DeviceFiles { bool(const std::string&, const std::string&, std::string*, CdmKeyMessage*, CdmKeyResponse*, CdmUsageEntry*, uint32_t*)); + MOCK_METHOD0(DeleteAllLicenses, bool()); MOCK_METHOD7(StoreUsageInfo, bool(const std::string&, const CdmKeyMessage&, const CdmKeyResponse&, const std::string&, @@ -179,6 +180,7 @@ TEST_P(UsageTableHeaderInitializationTest, CreateUsageTableHeader) { EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) .WillOnce( DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, kEmptyUsageEntryInfoVector)) .WillOnce(Return(true)); @@ -560,7 +562,6 @@ TEST_F(UsageTableHeaderTest, usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - device_files_->DeleteAllLicenses(); EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); EXPECT_CALL( *crypto_session_, @@ -1558,4 +1559,27 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreSecureStopAndUnknknown) { device_files_, &metrics)); } +// If the crypto session says the usage table header is stale, init should fail. +TEST_F(UsageTableHeaderTest, StaleHeader) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, + kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2}; + + EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), + SetArgPointee<1>(usage_entry_info_vector), + Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) + .WillOnce(Return(LOAD_USAGE_HEADER_GENERATION_SKEW)); + EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, + kEmptyUsageEntryInfoVector)) + .WillOnce(Return(true)); + + EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); +} + } // namespace wvcdm