diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h index 2733a8b1..5de50572 100644 --- a/libwvdrmengine/cdm/core/include/device_files.h +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -110,6 +110,7 @@ class DeviceFiles { virtual bool RetrieveLicense(const std::string& key_set_id, CdmLicenseData* license_data, ResponseType* result); + virtual bool DeleteLicense(const std::string& key_set_id); virtual bool ListLicenses(std::vector* key_set_ids); virtual bool DeleteAllFiles(); @@ -153,6 +154,27 @@ class DeviceFiles { virtual bool DeleteUsageInfo(const std::string& usage_info_file_name, const std::string& provider_session_token); + // Deletes a set of provider sessions from the specified usage info. + // Sessions removed are based on the provided |key_set_ids|. If + // there are no remaining sessions associated with the usage info + // then the file will be deleted; otherwise, the remaining sessions + // are written back to the usage info file. + // + // Args: + // usage_info_file_name: name of the file containing the usage info + // message. This name should _not_ be the complete path, just + // the file name. + // key_set_ids: The list of key set IDs to be removed from the + // usage info. Note that any key set ids that are not present + // in the usage info are silently ignored. + // Returns: + // `true` if the file existed, and operations were completed as + // expected. `false` if the file does not exist or if there is an + // issue writing the result back to file. + virtual bool DeleteMultipleUsageInfoByKeySetIds( + const std::string& usage_info_file_name, + const std::vector& key_set_ids); + // Delete usage information from the file system. Puts a list of all the // psts that were deleted from the file into |provider_session_tokens|. virtual bool DeleteAllUsageInfoForApp( @@ -211,9 +233,12 @@ class DeviceFiles { const CdmUsageTableHeader& usage_table_header, const std::vector& usage_entry_info); + // When retrieving usage table information from the file system; any + // table that has yet to be updated for the LRU attributes will be + // indicated by |lru_upgrade|. virtual bool RetrieveUsageTableInfo( CdmUsageTableHeader* usage_table_header, - std::vector* usage_entry_info); + std::vector* usage_entry_info, bool* lru_upgrade); virtual bool DeleteUsageTableInfo(); @@ -258,6 +283,7 @@ class DeviceFiles { FRIEND_TEST(DeviceFilesUsageInfoTest, Store); FRIEND_TEST(DeviceFilesUsageTableTest, Read); FRIEND_TEST(DeviceFilesUsageTableTest, Store); + FRIEND_TEST(DeviceFilesUsageTableTest, ReadWithoutLruData); FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest); FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test); FRIEND_TEST(WvCdmRequestLicenseTest, UsageInfoRetryTest); diff --git a/libwvdrmengine/cdm/core/include/usage_table_header.h b/libwvdrmengine/cdm/core/include/usage_table_header.h index 33d837aa..8d6ec30e 100644 --- a/libwvdrmengine/cdm/core/include/usage_table_header.h +++ b/libwvdrmengine/cdm/core/include/usage_table_header.h @@ -10,6 +10,7 @@ #include #include +#include "clock.h" #include "crypto_session.h" #include "device_files.h" #include "disallow_copy_and_assign.h" @@ -61,11 +62,13 @@ class UsageTableHeader { bool persistent_license, const CdmKeySetId& key_set_id, const std::string& usage_info_filename, + const CdmKeyResponse& license_message, uint32_t* usage_entry_number); virtual CdmResponseType LoadEntry(CryptoSession* crypto_session, const CdmUsageEntry& usage_entry, uint32_t usage_entry_number); - virtual CdmResponseType UpdateEntry(CryptoSession* crypto_session, + virtual CdmResponseType UpdateEntry(uint32_t usage_entry_number, + CryptoSession* crypto_session, CdmUsageEntry* usage_entry); // The licenses or usage info records specified by |usage_entry_number| @@ -85,6 +88,27 @@ class UsageTableHeader { size_t size() { return usage_entry_info_.size(); } + const std::vector& usage_entry_info() const { + return usage_entry_info_; + } + + // Set the reference clock used for the method GetCurrentTime(). + void SetClock(Clock* clock) { + if (clock != nullptr) + clock_ref_ = clock; + else + clock_ref_ = &clock_; + } + + static bool DetermineLicenseToRemoveForTesting( + const std::vector& usage_entry_info_list, + int64_t current_time, size_t unexpired_threshold, size_t removal_count, + std::vector* removal_candidates) { + return DetermineLicenseToRemove(usage_entry_info_list, current_time, + unexpired_threshold, removal_count, + removal_candidates); + } + private: CdmResponseType MoveEntry(uint32_t from /* usage entry number */, const CdmUsageEntry& from_usage_entry, @@ -111,6 +135,59 @@ class UsageTableHeader { virtual bool CreateDummyOldUsageEntry(CryptoSession* crypto_session); + // Performs and LRU upgrade on all loaded CdmUsageEntryInfo from a + // device file that had not yet been upgraded to use the LRU data. + virtual bool LruUpgradeAllUsageEntries(); + + virtual bool GetRemovalCandidates(std::vector* removal_candidates); + + int64_t GetCurrentTime() { return clock_ref_->GetCurrentTime(); } + + // Uses an LRU-base algorithm to determine which licenses should be + // removed. This is intended to be used if the usage table is full + // and a new entry needs to be added. + // + // Algorithm overview: + // Given the set of all usage entry infos, the entries which are + // of unknown storage type or are the most stale will be returned, + // with some exceptions for offline licenses. + // 1) Expired licenses will always be considered. Expiration is + // determined using the usage entry info's + // |offline_license_expiry| compared to the provided + // |current_time|. + // 2) Unexpired offline licenses will only be considered for + // removal if the number of unexpired offline licenses exceeds + // |unexpired_threshold|. + // The number of licenses to be considered will be less than or + // equal to the requested |removal_count|. + // + // Unknown storage types will be considered above all other entry + // types. + // + // Parameters: + // [in] usage_entry_info_list: The complete list of known usage + // entries. + // [in] current_time: The current time to compare expiration times + // against. + // [in] unexpired_threshold: The maximum number of unexpired + // offline licenses that are present, before offline + // licenses would be considered for removal. + // [in] removal_count: The desired number of removal candidate to + // find. Note that the actual number will be anywhere + // between 1 and |removal_count|. Must be greater than or + // equal to 1. + // [out] removal_candidates: List of usage entry numbers of the + // entries to be removed. Assume to be unaffected if the + // function returns |false|. + // + // Returns: + // |true| if at least one removal candidate can be determined. + // Otherwise returns |false|. + static bool DetermineLicenseToRemove( + const std::vector& usage_entry_info_list, + int64_t current_time, size_t unexpired_threshold, size_t removal_count, + std::vector* removal_candidates); + // This handle and file system is only to be used when accessing // usage_table_header. Usage entries should use the file system provided // by CdmSession. @@ -133,6 +210,14 @@ class UsageTableHeader { metrics::CryptoMetrics alternate_crypto_metrics_; + // |clock_| represents the system's "wall clock". For the clock's purpose + // we do not need a more secure clock. + Clock clock_; + // |clock_ref_| is a pointer to the clock which is to be used for + // obtaining the current time. By default, this points to the internal + // |clock_| variable, however, it can be overrided for testing purpose. + Clock* clock_ref_; + #if defined(UNIT_TEST) // Test related declarations friend class UsageTableHeaderTest; diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index f9ac6dcc..aa770a8d 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -400,6 +400,7 @@ enum CdmResponseType { INVALID_SRM_LIST = 346, KEYSET_ID_NOT_FOUND_4 = 347, SESSION_NOT_FOUND_22 = 348, + USAGE_INVALID_PARAMETERS_2 = 349, // Don't forget to add new values to // * core/test/test_printers.cpp. // * android/include/mapErrors-inl.h @@ -504,11 +505,25 @@ struct CdmUsageEntryInfo { CdmUsageEntryStorageType storage_type; CdmKeySetId key_set_id; std::string usage_info_file_name; + int64_t last_use_time; + int64_t offline_license_expiry_time; // Only for offline licenses. bool operator==(const CdmUsageEntryInfo& other) const { - return storage_type == other.storage_type && - key_set_id == other.key_set_id && - (storage_type != kStorageUsageInfo || - usage_info_file_name == other.usage_info_file_name); + if (this == &other) { + return true; + } + if (storage_type != other.storage_type || key_set_id != other.key_set_id || + last_use_time != other.last_use_time) { + return false; + } + // Certain fields only have meaning based on the storage type. + if (storage_type == kStorageUsageInfo) { + return usage_info_file_name == other.usage_info_file_name; + } + if (storage_type == kStorageLicense) { + return offline_license_expiry_time == other.offline_license_expiry_time; + } + // else storage_type == kStorageTypeUnknown + return true; } }; diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index b797ed7e..f1c19ac3 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -313,8 +313,8 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, if (usage_support_type_ == kUsageEntrySupport && !provider_session_token.empty() && usage_table_header_ != nullptr) { - CdmResponseType sts = - usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); + CdmResponseType sts = usage_table_header_->UpdateEntry( + usage_entry_number_, crypto_session_.get(), &usage_entry_); if (sts != NO_ERROR) { LOGE("Failed to update usage entry: status = %d", static_cast(sts)); return sts; @@ -367,8 +367,8 @@ CdmResponseType CdmSession::RestoreUsageSession( if (usage_support_type_ == kUsageEntrySupport && usage_table_header_ != nullptr) { - sts = - usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); + sts = usage_table_header_->UpdateEntry( + usage_entry_number_, crypto_session_.get(), &usage_entry_); if (sts != NO_ERROR) { LOGE("Failed to update usage entry: status = %d", static_cast(sts)); return sts; @@ -513,7 +513,8 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { GetApplicationId(&app_id); sts = usage_table_header_->AddEntry( crypto_session_.get(), is_offline_, key_set_id_, - DeviceFiles::GetUsageInfoFileName(app_id), &usage_entry_number_); + DeviceFiles::GetUsageInfoFileName(app_id), key_response, + &usage_entry_number_); crypto_metrics_->usage_table_header_add_entry_.Increment(sts); if (sts != NO_ERROR) return sts; } @@ -553,7 +554,8 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { if (has_provider_session_token() && usage_support_type_ == kUsageEntrySupport && usage_table_header_ != nullptr) { - usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); + usage_table_header_->UpdateEntry(usage_entry_number_, + crypto_session_.get(), &usage_entry_); } if (!is_offline_) @@ -735,8 +737,8 @@ CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyRequest* key_request) { if (has_provider_session_token() && usage_support_type_ == kUsageEntrySupport) { - status = - usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); + status = usage_table_header_->UpdateEntry( + usage_entry_number_, crypto_session_.get(), &usage_entry_); if (status != NO_ERROR) { LOGE("Update usage entry failed: status = %d", static_cast(status)); @@ -1055,8 +1057,8 @@ CdmResponseType CdmSession::UpdateUsageEntryInformation() { // TODO(blueeyes): Add measurements to all UpdateEntry calls in a way that // allos us to isolate this particular use case within // UpdateUsageEntryInformation. - M_TIME(sts = usage_table_header_->UpdateEntry(crypto_session_.get(), - &usage_entry_), + M_TIME(sts = usage_table_header_->UpdateEntry( + usage_entry_number_, crypto_session_.get(), &usage_entry_), crypto_metrics_, usage_table_header_update_entry_, sts); if (sts != NO_ERROR) return sts; diff --git a/libwvdrmengine/cdm/core/src/device_files.cpp b/libwvdrmengine/cdm/core/src/device_files.cpp index 80929f26..c49c34db 100644 --- a/libwvdrmengine/cdm/core/src/device_files.cpp +++ b/libwvdrmengine/cdm/core/src/device_files.cpp @@ -532,6 +532,49 @@ bool DeviceFiles::DeleteAllUsageInfoForApp( return RemoveFile(usage_info_file_name); } +bool DeviceFiles::DeleteMultipleUsageInfoByKeySetIds( + const std::string& usage_info_file_name, + const std::vector& key_set_ids) { + if (!FileExists(usage_info_file_name)) return false; + if (key_set_ids.empty()) { + LOGW("No key set IDs provided"); + return true; + } + + video_widevine_client::sdk::File file; + if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { + LOGW("Unable to retrieve usage info file"); + return false; + } + + const auto is_deletable = + [&key_set_ids]( + const video_widevine_client::sdk::UsageInfo::ProviderSession& session) + -> bool { + return std::find(key_set_ids.cbegin(), key_set_ids.cend(), + session.key_set_id()) != key_set_ids.cend(); + }; + + auto sessions = file.mutable_usage_info()->mutable_sessions(); + const int initial_size = sessions->size(); + sessions->erase( + std::remove_if(sessions->begin(), sessions->end(), is_deletable), + sessions->end()); + + if (sessions->size() == initial_size) { + // Nothing deleted. + return true; + } + + if (sessions->size() > 0) { + std::string serialized_file; + file.SerializeToString(&serialized_file); + return StoreFileWithHash(usage_info_file_name, serialized_file) == kNoError; + } + + return RemoveFile(usage_info_file_name); +} + bool DeviceFiles::DeleteAllUsageInfo() { RETURN_FALSE_IF_UNINITIALIZED(); return RemoveFile(kUsageInfoFileNamePrefix + std::string(kWildcard) + @@ -891,12 +934,16 @@ bool DeviceFiles::StoreUsageTableInfo( case kStorageLicense: info->set_storage( UsageTableInfo_UsageEntryInfo_UsageEntryStorage_LICENSE); + info->set_last_use_time(usage_entry_info[i].last_use_time); + info->set_offline_license_expiry_time( + usage_entry_info[i].offline_license_expiry_time); break; case kStorageUsageInfo: info->set_storage( UsageTableInfo_UsageEntryInfo_UsageEntryStorage_USAGE_INFO); info->set_usage_info_file_name( usage_entry_info[i].usage_info_file_name); + info->set_last_use_time(usage_entry_info[i].last_use_time); break; case kStorageTypeUnknown: default: @@ -905,6 +952,7 @@ bool DeviceFiles::StoreUsageTableInfo( break; } } + usage_table_info->set_use_lru(true); std::string serialized_file; file.SerializeToString(&serialized_file); @@ -915,10 +963,11 @@ bool DeviceFiles::StoreUsageTableInfo( bool DeviceFiles::RetrieveUsageTableInfo( CdmUsageTableHeader* usage_table_header, - std::vector* usage_entry_info) { + std::vector* usage_entry_info, bool* lru_upgrade) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(usage_table_header); RETURN_FALSE_IF_NULL(usage_entry_info); + RETURN_FALSE_IF_NULL(lru_upgrade); video_widevine_client::sdk::File file; if (RetrieveHashedFile(GetUsageTableFileName(), &file) != kNoError) { @@ -947,6 +996,8 @@ bool DeviceFiles::RetrieveUsageTableInfo( const UsageTableInfo& usage_table_info = file.usage_table_info(); + *lru_upgrade = !usage_table_info.use_lru(); + *usage_table_header = usage_table_info.usage_table_header(); usage_entry_info->resize(usage_table_info.usage_entry_info_size()); for (int i = 0; i < usage_table_info.usage_entry_info_size(); ++i) { @@ -956,11 +1007,15 @@ bool DeviceFiles::RetrieveUsageTableInfo( switch (info.storage()) { case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_LICENSE: (*usage_entry_info)[i].storage_type = kStorageLicense; + (*usage_entry_info)[i].last_use_time = info.last_use_time(); + (*usage_entry_info)[i].offline_license_expiry_time = + info.offline_license_expiry_time(); break; case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_USAGE_INFO: (*usage_entry_info)[i].storage_type = kStorageUsageInfo; (*usage_entry_info)[i].usage_info_file_name = info.usage_info_file_name(); + (*usage_entry_info)[i].last_use_time = info.last_use_time(); break; case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_UNKNOWN: default: diff --git a/libwvdrmengine/cdm/core/src/device_files.proto b/libwvdrmengine/cdm/core/src/device_files.proto index ed822194..8190ba3d 100644 --- a/libwvdrmengine/cdm/core/src/device_files.proto +++ b/libwvdrmengine/cdm/core/src/device_files.proto @@ -33,6 +33,10 @@ message License { optional LicenseState state = 1; optional bytes pssh_data = 2; + // |license_request|, |license|, |renewal_request|, |renewal| represents a + // serialized video_widevine::SignedMessage containing a + // video_widevine::License or video_widevine::LicenseRequest from + // license_protocol.proto optional bytes license_request = 3; optional bytes license = 4; optional bytes renewal_request = 5; @@ -53,6 +57,8 @@ message UsageInfo { message ProviderSession { optional bytes token = 1; optional bytes license_request = 2; + // |license| represents a video_widevine::SignedMessage containing a + // video_widevine::License from license_protocol.proto optional bytes license = 3; optional bytes key_set_id = 4; optional bytes usage_entry = 5; @@ -82,10 +88,16 @@ message UsageTableInfo { optional UsageEntryStorage storage = 1; optional bytes key_set_id = 2; optional bytes usage_info_file_name = 3; // hash of the app_id + + // LRU table replacement data. + optional int64 last_use_time = 4 [default = 0]; + // Only used if storage == LICENSE (offline license). + optional int64 offline_license_expiry_time = 5 [default = 0]; } optional bytes usage_table_header = 1; repeated UsageEntryInfo usage_entry_info = 2; + optional bool use_lru = 3 [default = false]; } message File { diff --git a/libwvdrmengine/cdm/core/src/usage_table_header.cpp b/libwvdrmengine/cdm/core/src/usage_table_header.cpp index ac4e2b3a..3ad1b34f 100644 --- a/libwvdrmengine/cdm/core/src/usage_table_header.cpp +++ b/libwvdrmengine/cdm/core/src/usage_table_header.cpp @@ -4,12 +4,16 @@ #include "usage_table_header.h" +#include + #include "cdm_random.h" #include "crypto_session.h" #include "license.h" #include "log.h" #include "wv_cdm_constants.h" +namespace wvcdm { + namespace { std::string kEmptyString; size_t kMaxCryptoRetries = 3; @@ -22,15 +26,119 @@ std::string kOldUsageEntryServerMacKey(wvcdm::MAC_KEY_SIZE, 0); std::string kOldUsageEntryClientMacKey(wvcdm::MAC_KEY_SIZE, 0); std::string kOldUsageEntryPoviderSessionToken = "nahZ6achSheiqua3TohQuei0ahwohv"; +constexpr int64_t kDefaultExpireDuration = 33 * 24 * 60 * 60; // 33 Days +// Number of elements to consider for removal using the LRU algorithm. +constexpr size_t kLruRemovalSetSize = 3; +// Threshold for maximum number of unexpired offline licenses before +// they are considered to be removed. This could occur if there are +// not enough expired offline or streaming licenses to remove. +// This threshold is set to prevent thrashing in the case that there +// are a very large number of unexpired offline licenses and few +// expired / streaming licenses (ie, number of unexpired licenses nears +// the capacity of the usage table). +constexpr size_t kLruUnexpiredThreshold = 150; + +// Convert |license_message| -> SignedMessage -> License. +bool ParseLicenseFromLicenseMessage(const CdmKeyResponse& license_message, + video_widevine::License* license) { + using video_widevine::SignedMessage; + if (license == nullptr) { + LOGE("Output parameter |license| is null"); + return false; + } + SignedMessage signed_license_response; + if (!signed_license_response.ParseFromString(license_message)) { + LOGW("Unabled to parse signed license response"); + return false; + } + + if (signed_license_response.type() != SignedMessage::LICENSE) { + LOGW("Unexpected signed message: type = %d, expected_type = %d", + static_cast(signed_license_response.type()), + static_cast(SignedMessage::LICENSE)); + return false; + } + + if (!signed_license_response.has_signature()) { + LOGW("License response message is not signed"); + return false; + } + + if (!license->ParseFromString(signed_license_response.msg())) { + LOGW("Failed to parse license"); + return false; + } + return true; +} + +bool RetrieveOfflineLicense(DeviceFiles* device_files, + const std::string& key_set_id, + CdmKeyResponse* license_message, + uint32_t* usage_entry_number) { + if (device_files == nullptr) { + LOGE("DeviceFiles handle is null"); + return false; + } + if (license_message == nullptr) { + LOGE("Output parameter |license_message| is null"); + return false; + } + if (usage_entry_number == nullptr) { + LOGE("Output parameter |usage_entry_number| is null"); + return false; + } + DeviceFiles::CdmLicenseData license_data; + DeviceFiles::ResponseType result = DeviceFiles::kNoError; + if (!device_files->RetrieveLicense(key_set_id, &license_data, &result)) { + LOGW("Failed to retrieve license: key_set_id = %s, result = %d", + key_set_id.c_str(), static_cast(result)); + return false; + } + *license_message = std::move(license_data.license); + *usage_entry_number = license_data.usage_entry_number; + return true; +} + +bool RetrieveUsageInfoLicense(DeviceFiles* device_files, + const std::string& usage_info_file_name, + const std::string& key_set_id, + CdmKeyResponse* license_message, + uint32_t* usage_entry_number) { + if (device_files == nullptr) { + LOGE("DeviceFiles handle is null"); + return false; + } + if (license_message == nullptr) { + LOGE("Output parameter |license_message| is null"); + return false; + } + if (usage_entry_number == nullptr) { + LOGE("Output parameter |usage_entry_number| is null"); + return false; + } + CdmUsageEntry usage_entry; + std::string provider_session_token; + CdmKeyMessage license_request; + if (!device_files->RetrieveUsageInfoByKeySetId( + usage_info_file_name, key_set_id, &provider_session_token, + &license_request, license_message, &usage_entry, + usage_entry_number)) { + LOGW( + "Failed to retrieve usage information: " + "key_set_id = %s, usage_info_file_name = %s", + key_set_id.c_str(), usage_info_file_name.c_str()); + return false; + } + return true; +} } // namespace -namespace wvcdm { - UsageTableHeader::UsageTableHeader() : security_level_(kSecurityLevelUninitialized), requested_security_level_(kLevelDefault), - is_inited_(false) { + is_inited_(false), + clock_ref_(&clock_) { file_system_.reset(new FileSystem()); file_handle_.reset(new DeviceFiles(file_system_.get())); } @@ -67,15 +175,28 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics(); if (metrics == nullptr) metrics = &alternate_crypto_metrics_; - if (file_handle_->RetrieveUsageTableInfo(&usage_table_header_, - &usage_entry_info_)) { + bool run_lru_upgrade = false; + if (file_handle_->RetrieveUsageTableInfo( + &usage_table_header_, &usage_entry_info_, &run_lru_upgrade)) { LOGI("Number of usage entries: %zu", usage_entry_info_.size()); status = crypto_session->LoadUsageTableHeader(usage_table_header_); + bool lru_success = true; + if (status == NO_ERROR && run_lru_upgrade) { + // If the loaded table info does not contain LRU information, then + // the information must be added immediately before being used. + if (!LruUpgradeAllUsageEntries()) { + LOGE( + "Unable to init usage table header: " + "Failed to perform LRU upgrade to usage entry table"); + lru_success = false; + } + } + // If the usage table header has been successfully loaded, and is at // minimum capacity (>200), we need to make sure we can still add and // remove entries. If not, clear files/data and recreate usage header table. - if (status == NO_ERROR) { + if (status == NO_ERROR && lru_success) { if (usage_entry_info_.size() > kMinUsageEntriesSupported) { uint32_t temporary_usage_entry_number; @@ -89,14 +210,15 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, CryptoSession* local_crypto_session = test_crypto_session_.get(); if (local_crypto_session == nullptr) { scoped_crypto_session.reset( - (CryptoSession::MakeCryptoSession(metrics))); + CryptoSession::MakeCryptoSession(metrics)); local_crypto_session = scoped_crypto_session.get(); } result = local_crypto_session->Open(requested_security_level_); if (result == NO_ERROR) { result = AddEntry(local_crypto_session, true, kDummyKeySetId, - kEmptyString, &temporary_usage_entry_number); + kEmptyString, kEmptyString, + &temporary_usage_entry_number); } } if (result == NO_ERROR) { @@ -113,9 +235,9 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, } } - if (status != NO_ERROR) { - LOGE("Failed to load usage table: security_level = %d", - static_cast(security_level)); + if (status != NO_ERROR || !lru_success) { + LOGE("Failed to load usage table: security_level = %d, status = %d", + static_cast(security_level), static_cast(status)); file_handle_->DeleteAllLicenses(); file_handle_->DeleteAllUsageInfo(); file_handle_->DeleteUsageTableInfo(); @@ -141,7 +263,7 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, CdmResponseType UsageTableHeader::AddEntry( CryptoSession* crypto_session, bool persistent_license, const CdmKeySetId& key_set_id, const std::string& usage_info_file_name, - uint32_t* usage_entry_number) { + const CdmKeyResponse& license_message, uint32_t* usage_entry_number) { LOGI("Adding usage entry"); metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics(); @@ -149,19 +271,33 @@ CdmResponseType UsageTableHeader::AddEntry( CdmResponseType status = crypto_session->CreateUsageEntry(usage_entry_number); - // If usage entry creation fails due to insufficient resources, release a - // random entry and try again. - for (uint32_t retry_count = 0; retry_count < kMaxCryptoRetries && - status == INSUFFICIENT_CRYPTO_RESOURCES_3; - ++retry_count) { - if (usage_entry_info_.size() == 0) { - break; + if (status == INSUFFICIENT_CRYPTO_RESOURCES_3) { + // If usage entry creation fails due to insufficient resources, release an + // entry based on LRU. + std::vector removal_candidates; + if (!GetRemovalCandidates(&removal_candidates)) { + LOGE("Could not determine which license to remove"); + return status; } - uint32_t entry_number_to_delete = - CdmRandom::RandomInRange(usage_entry_info_.size() - 1); - DeleteEntry(entry_number_to_delete, file_handle_.get(), metrics); - status = crypto_session->CreateUsageEntry(usage_entry_number); + for (size_t i = 0; i < removal_candidates.size() && + status == INSUFFICIENT_CRYPTO_RESOURCES_3; + ++i) { + const uint32_t entry_number_to_delete = removal_candidates[i]; + if (DeleteEntry(entry_number_to_delete, file_handle_.get(), metrics) == + NO_ERROR) { + // If the entry was deleted, it is still possible for the create new + // entry to fail. If so, we must ensure that the previously last + // entry was not in the |removal_candidates| as it has now been swapped + // with the deleted entry. + for (uint32_t& entry_number : removal_candidates) { + if (entry_number == usage_entry_info_.size()) { + entry_number = entry_number_to_delete; + } + } + } + status = crypto_session->CreateUsageEntry(usage_entry_number); + } } if (status != NO_ERROR) return status; @@ -181,7 +317,7 @@ CdmResponseType UsageTableHeader::AddEntry( "New entry number is larger than table size, resizing: " "entry_info_number = %u, table_size = %zu", *usage_entry_number, usage_entry_info_.size()); - size_t number_of_entries = usage_entry_info_.size(); + const size_t number_of_entries = usage_entry_info_.size(); usage_entry_info_.resize(*usage_entry_number + 1); for (size_t i = number_of_entries; i < usage_entry_info_.size() - 1; ++i) { usage_entry_info_[i].storage_type = kStorageTypeUnknown; @@ -195,9 +331,28 @@ CdmResponseType UsageTableHeader::AddEntry( usage_entry_info_[*usage_entry_number].storage_type = persistent_license ? kStorageLicense : kStorageUsageInfo; usage_entry_info_[*usage_entry_number].key_set_id = key_set_id; - if (!persistent_license) + usage_entry_info_[*usage_entry_number].last_use_time = GetCurrentTime(); + if (!persistent_license) { usage_entry_info_[*usage_entry_number].usage_info_file_name = usage_info_file_name; + usage_entry_info_[*usage_entry_number].offline_license_expiry_time = 0; + } else { + // Need to determine the expire time for offline licenses. + video_widevine::License license; + if (license_message.size() > 0 && + ParseLicenseFromLicenseMessage(license_message, &license)) { + const video_widevine::License::Policy& policy = license.policy(); + usage_entry_info_[*usage_entry_number].offline_license_expiry_time = + license.license_start_time() + policy.rental_duration_seconds() + + policy.playback_duration_seconds(); + } else { + // If the license duration cannot be determined for any reason, it + // is assumed to last at most 33 days. + usage_entry_info_[*usage_entry_number].offline_license_expiry_time = + usage_entry_info_[*usage_entry_number].last_use_time + + kDefaultExpireDuration; + } + } LOGI("New usage entry: usage_entry_number = %u", *usage_entry_number); file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); @@ -243,17 +398,29 @@ CdmResponseType UsageTableHeader::LoadEntry(CryptoSession* crypto_session, status = crypto_session->LoadUsageEntry(usage_entry_number, usage_entry); } + if (status == NO_ERROR) { + usage_entry_info_[usage_entry_number].last_use_time = GetCurrentTime(); + } + return status; } -CdmResponseType UsageTableHeader::UpdateEntry(CryptoSession* crypto_session, +CdmResponseType UsageTableHeader::UpdateEntry(uint32_t usage_entry_number, + CryptoSession* crypto_session, CdmUsageEntry* usage_entry) { LOGI("Locking to update entry"); std::unique_lock auto_lock(usage_table_header_lock_); + if (usage_entry_number >= usage_entry_info_.size()) { + LOGE("Usage entry number %u is larger than usage entry size %zu", + usage_entry_number, usage_entry_info_.size()); + return USAGE_INVALID_PARAMETERS_2; + } + CdmResponseType status = crypto_session->UpdateUsageEntry(&usage_table_header_, usage_entry); if (status != NO_ERROR) return status; + usage_entry_info_[usage_entry_number].last_use_time = GetCurrentTime(); file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); return NO_ERROR; @@ -602,7 +769,7 @@ bool UsageTableHeader::UpgradeLicensesFromUsageTable( if (!CreateDummyOldUsageEntry(crypto_session.get())) continue; status = AddEntry(crypto_session.get(), true /* persistent license */, - key_set_ids[i], kEmptyString, + key_set_ids[i], kEmptyString, license_data.license, &license_data.usage_entry_number); if (status != NO_ERROR) continue; @@ -615,7 +782,8 @@ bool UsageTableHeader::UpgradeLicensesFromUsageTable( continue; } - status = UpdateEntry(crypto_session.get(), &license_data.usage_entry); + status = UpdateEntry(license_data.usage_entry_number, crypto_session.get(), + &license_data.usage_entry); if (status != NO_ERROR) { crypto_session->Close(); @@ -676,9 +844,10 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable( // TODO(rfrias): We need to fill in the app id, but it is hashed // and we have no way to extract. Use the hased filename instead? - status = AddEntry(crypto_session.get(), false /* usage info */, - usage_data[j].key_set_id, usage_info_file_names[i], - &(usage_data[j].usage_entry_number)); + status = + AddEntry(crypto_session.get(), false /* usage info */, + usage_data[j].key_set_id, usage_info_file_names[i], + usage_data[j].license, &(usage_data[j].usage_entry_number)); if (status != NO_ERROR) continue; @@ -691,7 +860,8 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable( continue; } - status = UpdateEntry(crypto_session.get(), &(usage_data[j].usage_entry)); + status = UpdateEntry(usage_data[j].usage_entry_number, + crypto_session.get(), &(usage_data[j].usage_entry)); if (status != NO_ERROR) { crypto_session->Close(); @@ -735,4 +905,258 @@ void UsageTableHeader::DeleteEntryForTest(uint32_t usage_entry_number) { usage_entry_info_.resize(usage_entry_info_.size() - 1); } +bool UsageTableHeader::LruUpgradeAllUsageEntries() { + LOGV("Upgrading all usage entries with LRU information"); + if (usage_entry_info_.size() == 0) return true; // Nothing to upgrade. + + // For each entry, the status upgrading that entry is stored. At the + // end, all problematic licenses will be marked as invalid. + std::vector bad_license_file_entries; + + for (size_t usage_entry_number = 0; + usage_entry_number < usage_entry_info_.size(); ++usage_entry_number) { + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_[usage_entry_number]; + + uint32_t retrieved_entry_number; + CdmKeyResponse license_message; + bool retrieve_response = false; + switch (usage_entry_info.storage_type) { + case kStorageLicense: { + retrieve_response = RetrieveOfflineLicense( + file_handle_.get(), usage_entry_info.key_set_id, &license_message, + &retrieved_entry_number); + break; + } + case kStorageUsageInfo: { + retrieve_response = RetrieveUsageInfoLicense( + file_handle_.get(), usage_entry_info.usage_info_file_name, + usage_entry_info.key_set_id, &license_message, + &retrieved_entry_number); + break; + } + case kStorageTypeUnknown: + bad_license_file_entries.push_back(usage_entry_number); + continue; + default: { + LOGW("Unknown usage entry storage type: %d, usage_entry_number = %u", + static_cast(usage_entry_info.storage_type), + usage_entry_number); + bad_license_file_entries.push_back(usage_entry_number); + continue; + } + } + if (!retrieve_response) { + LOGW("Could not retrieve license message: usage_entry_number = %u", + usage_entry_number); + bad_license_file_entries.push_back(usage_entry_number); + continue; + } + + if (retrieved_entry_number != usage_entry_number) { + LOGW( + "Usage entry number mismatched: usage_entry_number = %u, " + "retrieved_entry_number = %u", + usage_entry_number, retrieved_entry_number); + bad_license_file_entries.push_back(usage_entry_number); + continue; + } + + video_widevine::License license; + if (!ParseLicenseFromLicenseMessage(license_message, &license)) { + LOGW("Could not parse license: usage_entry_number = %u", + usage_entry_number); + bad_license_file_entries.push_back(usage_entry_number); + continue; + } + + // If |license_start_time| is 0, then this entry will be considered + // for replacement above all others. + usage_entry_info.last_use_time = license.license_start_time(); + + if (usage_entry_info.storage_type == kStorageLicense) { + // Only offline licenses need |offline_license_expiry_time| set. + const video_widevine::License::Policy& policy = license.policy(); + // TODO(b/139372190): Change how these fields are set once feature is + // implemented. + if (policy.license_duration_seconds() == 0) { + // Zero implies unlimited license duration. + usage_entry_info.offline_license_expiry_time = + license.license_start_time() + policy.rental_duration_seconds() + + policy.playback_duration_seconds(); + } else { + usage_entry_info.offline_license_expiry_time = + license.license_start_time() + policy.license_duration_seconds(); + } + } else { + usage_entry_info.offline_license_expiry_time = 0; + } + } // End for loop. + + if (bad_license_file_entries.size() == usage_entry_info_.size()) { + LOGE("Failed to perform LRU upgrade for every entry: count = %zu", + usage_entry_info_.size()); + return false; + } + + // Maps -> []. + std::map> usage_info_clean_up; + for (size_t usage_entry_number : bad_license_file_entries) { + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_[usage_entry_number]; + if (usage_entry_info.storage_type == kStorageLicense) { + file_handle_->DeleteLicense(usage_entry_info.key_set_id); + } else if (usage_entry_info.storage_type == kStorageUsageInfo) { + // To reduce write cycles, the deletion of usage info will be done + // in bulk. + auto it = usage_info_clean_up.find(usage_entry_info.usage_info_file_name); + if (it == usage_info_clean_up.end()) { + it = usage_info_clean_up + .emplace(usage_entry_info.usage_info_file_name, + std::vector()) + .first; + } + it->second.push_back(usage_entry_info.key_set_id); + } // else kStorageUnknown { Nothing special }. + usage_entry_info.storage_type = kStorageTypeUnknown; + usage_entry_info.key_set_id.clear(); + usage_entry_info.usage_info_file_name.clear(); + } + + for (const auto& p : usage_info_clean_up) { + file_handle_->DeleteMultipleUsageInfoByKeySetIds(p.first, p.second); + } + + return true; +} + +bool UsageTableHeader::GetRemovalCandidates( + std::vector* removal_candidates) { + LOGI("Locking to determine removal candidates"); + std::unique_lock auto_lock(usage_table_header_lock_); + return DetermineLicenseToRemove(usage_entry_info_, GetCurrentTime(), + kLruUnexpiredThreshold, kLruRemovalSetSize, + removal_candidates); +} + +// Static. +bool UsageTableHeader::DetermineLicenseToRemove( + const std::vector& usage_entry_info_list, + int64_t current_time, size_t unexpired_threshold, size_t removal_count, + std::vector* removal_candidates) { + if (removal_candidates == nullptr) { + LOGE("Output parameter |removal_candidates| is null"); + return false; + } + if (removal_count == 0) { + LOGE("|removal_count| cannot be zero"); + return false; + } + if (usage_entry_info_list.empty()) { + return false; + } + removal_candidates->clear(); + + std::vector unknown_storage_entry_numbers; + // |entry_numbers| contains expired offline and streaming license. + std::vector entry_numbers; + std::vector unexpired_offline_license_entry_numbers; + + // Separate the entries based on their priority properties. + for (uint32_t entry_number = 0; entry_number < usage_entry_info_list.size(); + ++entry_number) { + const CdmUsageEntryInfo& usage_entry_info = + usage_entry_info_list[entry_number]; + if (usage_entry_info.storage_type == kStorageLicense) { + if (usage_entry_info.offline_license_expiry_time > current_time) { + // Unexpired offline. + unexpired_offline_license_entry_numbers.push_back(entry_number); + } else { + // Expired offline. + entry_numbers.push_back(entry_number); + } + } else if (usage_entry_info.storage_type == kStorageUsageInfo) { + // Streaming. + entry_numbers.push_back(entry_number); + } else { + // Unknown entries. + unknown_storage_entry_numbers.push_back(entry_number); + } + } + + // Select any entries of unknown storage type. + if (!unknown_storage_entry_numbers.empty()) { + if (unknown_storage_entry_numbers.size() >= removal_count) { + // Case: There are enough entries with unknown storage types to + // fill the removal set. + removal_candidates->insert( + removal_candidates->begin(), unknown_storage_entry_numbers.begin(), + unknown_storage_entry_numbers.begin() + removal_count); + return true; + } + // Fill whatever are available, and check for more. + *removal_candidates = std::move(unknown_storage_entry_numbers); + } + + // Sort licenses based on last used time. + const auto compare_last_used = [&](uint32_t i, uint32_t j) { + return usage_entry_info_list[i].last_use_time < + usage_entry_info_list[j].last_use_time; + }; + + // Check if unexpired licenses should be considered too. + if (unexpired_offline_license_entry_numbers.size() > unexpired_threshold) { + std::sort(unexpired_offline_license_entry_numbers.begin(), + unexpired_offline_license_entry_numbers.end(), compare_last_used); + if (unexpired_offline_license_entry_numbers.size() > removal_count) { + unexpired_offline_license_entry_numbers.resize(removal_count); + } + // Merge the sets. + entry_numbers.insert(entry_numbers.end(), + unexpired_offline_license_entry_numbers.begin(), + unexpired_offline_license_entry_numbers.end()); + } + + // Sort expired offline and streaming license based on last used time. + std::sort(entry_numbers.begin(), entry_numbers.end(), compare_last_used); + + if ((entry_numbers.size() + removal_candidates->size()) <= removal_count) { + // Under testing conditions, it is possible for there to be fewer usage + // entries than there are being requested. + + // Move whatever values are available to the removal candidates. + removal_candidates->insert(removal_candidates->end(), entry_numbers.begin(), + entry_numbers.end()); + return removal_candidates->size() > 0; + } + + const size_t remaining_removal_count = + removal_count - removal_candidates->size(); + + // Based on the last use time the |remaining_removal_count|-th + // least recently used entry, filter out all elements which have + // been used more recently than it. This might result in a set + // which is larger than the desired size. + const int64_t cutoff_last_use_time = + usage_entry_info_list[entry_numbers[remaining_removal_count - 1]] + .last_use_time; + const auto equal_to_cutoff = [&](uint32_t entry_number) { + return usage_entry_info_list[entry_number].last_use_time == + cutoff_last_use_time; + }; + + const auto first_cutoff_it = + std::find_if(entry_numbers.begin(), entry_numbers.end(), equal_to_cutoff); + + const auto after_last_cutoff_it = + std::find_if_not(first_cutoff_it, entry_numbers.end(), equal_to_cutoff); + + // To avoid always selecting the greatest entry number (due to the + // sort & reverse), we randomize the set. + std::shuffle(first_cutoff_it, after_last_cutoff_it, + std::default_random_engine(CdmRandom::Rand())); + + removal_candidates->insert(removal_candidates->end(), entry_numbers.cbegin(), + entry_numbers.cbegin() + remaining_removal_count); + return true; +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp b/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp index b7613d5c..e732983d 100644 --- a/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp @@ -122,7 +122,8 @@ class MockUsageTableHeader : public UsageTableHeader { public: MockUsageTableHeader() : UsageTableHeader() {} - MOCK_METHOD2(UpdateEntry, CdmResponseType(CryptoSession* crypto_session, + MOCK_METHOD3(UpdateEntry, CdmResponseType(uint32_t usage_entry_number, + CryptoSession* crypto_session, CdmUsageEntry* usage_entry)); }; @@ -345,7 +346,7 @@ TEST_F(CdmSessionTest, UpdateUsageEntry) { DoAll(SetArgPointee<0>(kUsageEntrySupport), Return(NO_ERROR))); EXPECT_CALL(*license_parser_, provider_session_token()) .WillRepeatedly(Return("Mock provider session token")); - EXPECT_CALL(usage_table_header_, UpdateEntry(NotNull(), NotNull())) + EXPECT_CALL(usage_table_header_, UpdateEntry(_, NotNull(), NotNull())) .WillRepeatedly(Return(NO_ERROR)); EXPECT_EQ(NO_ERROR, cdm_session_->Init(nullptr)); diff --git a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp index 263785f1..6dc0ec11 100644 --- a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp @@ -1873,42 +1873,17 @@ constexpr size_t kNumberOfHlsAttributes = ArraySize(kHlsAttributesTestData); // kUsageTableInfoTestData are equal. const CdmUsageEntryInfo kUsageEntriesTestData[] = { // usage entry 0 - { - kStorageLicense, - "ksid0", - "", - }, + {kStorageLicense, "ksid0", "", 1318402800, 1321254000}, // usage entry 1 - { - kStorageLicense, - "ksid1", - "", - }, + {kStorageLicense, "ksid1", "", 1050649200, 1053500400}, // usage entry 2 - { - kStorageUsageInfo, - "", - "app_id_2", - }, + {kStorageUsageInfo, "", "app_id_2", 694252800, 0}, // usage entry 3 - { - kStorageUsageInfo, - "", - "app_id_3", - }, + {kStorageUsageInfo, "", "app_id_3", 983001600, 0}, // usage entry 4 - { - kStorageLicense, - "ksid4", - "", - }, + {kStorageLicense, "ksid4", "", 316166400, 319017600}, // usage entry 5 - { - kStorageUsageInfo, - "", - "app_id_5", - }, - + {kStorageUsageInfo, "", "app_id_5", 802162800, 0}, }; struct UsageTableTestInfo { @@ -1920,47 +1895,90 @@ const UsageTableTestInfo kUsageTableInfoTestData[] = { // usage table 0 {a2bs_hex("5574517CCC"), - a2bs_hex("0A18080510013A120A055574517CCC1209080112056B73696430122018268E3F" - "384F28D04BEE00304089C000463C22E987532855390915FD02C36B5C")}, + a2bs_hex("0A26080510013A200A055574517CCC1215080112056B7369643020F0F5D4F404" + "28F0F882F6041801122055C2834976220A2819445B8D6EF5ADE9D7A050006941" + "3EDFB9948F928B317B89")}, // usage table 1 {a2bs_hex("CA870203010001288001"), - a2bs_hex("0A2C080510013A260A0ACA870203010001288001120B080112056B736964301A" - "00120B080112056B736964311A00122049A8F3481444A5B64B6C4F05FBCC2EF8" - "CB67444A08654763F2F5B80F658D7B38")}, + a2bs_hex("0A46080510013A400A0ACA8702030100012880011217080112056B736964301A" + "0020F0F5D4F40428F0F882F6041217080112056B736964311A0020F0C4FEF403" + "28F0C7ACF60318011220B000674E4E24E67384C4631DE05CB3C1E07CDE6B0412" + "CACE359DEAED6CF2D566")}, // usage table 2 {a2bs_hex("7A7D507618A5D3A68F05228E023082010A028201"), - a2bs_hex("0A46080510013A400A147A7D507618A5D3A68F05228E023082010A028201120B" - "080112056B736964301A00120B080112056B736964311A00120E080212001A08" - "6170705F69645F321220783E93A02223BDB94E743856C0F69C35B213ACCDDE91" - "93E48E9186AA83B80584")}, + a2bs_hex("0A66080510013A600A147A7D507618A5D3A68F05228E023082010A0282011217" + "080112056B736964301A0020F0F5D4F40428F0F882F6041217080112056B7369" + "64311A0020F0C4FEF40328F0C7ACF6031214080212001A086170705F69645F32" + "2080EA85CB02180112202701F8F75537EBA12217796401A81E9E0F59A5B10F51" + "F6C12E3BE3D8747CB745")}, // usage table 3 {a2bs_hex("E83A4902772DAFD2740B7748E9C3B1752D6F12859CED07E82969B4EC"), - a2bs_hex("0A5E080510013A580A1CE83A4902772DAFD2740B7748E9C3B1752D6F12859CED" - "07E82969B4EC120B080112056B736964301A00120B080112056B736964311A00" - "120E080212001A086170705F69645F32120E080212001A086170705F69645F33" - "122084E67F1338727291BC3D92E28442DC8B0F44CB5AF7B98A799313B7EB7F55" - "ED18")}, + a2bs_hex("0A8401080510013A7E0A1CE83A4902772DAFD2740B7748E9C3B1752D6F12859C" + "ED07E82969B4EC1217080112056B736964301A0020F0F5D4F40428F0F882F604" + "1217080112056B736964311A0020F0C4FEF40328F0C7ACF6031214080212001A" + "086170705F69645F322080EA85CB021214080212001A086170705F69645F3320" + "80D4DDD40318011220776BD3D0BBCC573AEC7466A8B0E4F1A013495A3BE492F3" + "FF4BA3D15FC9697902")}, // usage table 4 {a2bs_hex("CA870203010001288001300112800250D1F8B1ECF849B60FF93E37C4DEEF" "52F1CCFC047EF42300131F9C4758F4"), - a2bs_hex("0A7C080510013A760A2DCA870203010001288001300112800250D1F8B1ECF849" - "B60FF93E37C4DEEF52F1CCFC047EF42300131F9C4758F4120B080112056B7369" - "64301A00120B080112056B736964311A00120E080212001A086170705F69645F" - "32120E080212001A086170705F69645F33120B080112056B736964341A001220" - "1CDFCFED5E58A1DF77E1B335305424E1F0260340F9CC15985684C43A4207652" - "1")}, + a2bs_hex("0AAF01080510013AA8010A2DCA870203010001288001300112800250D1F8B1EC" + "F849B60FF93E37C4DEEF52F1CCFC047EF42300131F9C4758F41217080112056B" + "736964301A0020F0F5D4F40428F0F882F6041217080112056B736964311A0020" + "F0C4FEF40328F0C7ACF6031214080212001A086170705F69645F322080EA85CB" + "021214080212001A086170705F69645F332080D4DDD4031217080112056B7369" + "64341A002080A2E196012880A58F980118011220E7F0F123E513FCF3BC6BC17B" + "1531A8317654C5EF005655348D82FA01FDAD85CB")}, // usage table 5 {a2bs_hex("EC83A4902772DAFD2740B7748E9C3B1752D6F12859CED07E8882969B433E" "C29AC6FDBE79230B0FAED5D94CF6B829A420BBE3270323941776EE60DD6B"), - a2bs_hex("0A9C01080510013A95010A3CEC83A4902772DAFD2740B7748E9C3B1752D6F128" + a2bs_hex("0AD401080510013ACD010A3CEC83A4902772DAFD2740B7748E9C3B1752D6F128" "59CED07E8882969B433EC29AC6FDBE79230B0FAED5D94CF6B829A420BBE32703" - "23941776EE60DD6B120B080112056B736964301A00120B080112056B73696431" - "1A00120E080212001A086170705F69645F32120E080212001A086170705F6964" - "5F33120B080112056B736964341A00120E080212001A086170705F69645F3512" - "20305C7A27A918268119E1996FC182C153DF805034A387F90C3585749E764731" - "32")}, + "23941776EE60DD6B1217080112056B736964301A0020F0F5D4F40428F0F882F6" + "041217080112056B736964311A0020F0C4FEF40328F0C7ACF603121408021200" + "1A086170705F69645F322080EA85CB021214080212001A086170705F69645F33" + "2080D4DDD4031217080112056B736964341A002080A2E196012880A58F980112" + "14080212001A086170705F69645F3520F090C0FE0218011220A35C771A67AECF" + "06A72468DC2C380E5CFDCD377A8ADF848F26B7F22D24D23872")}, }; +const CdmUsageEntryInfo kUsageEntriesWithoutLruData[] = { + {kStorageLicense, "ksid0", "", 0, 0}, + {kStorageUsageInfo, "", "app_id_1", 0, 0}}; + +const std::string kUsageTableWithoutLruData = a2bs_hex( + "0A1F080510013A191209080112056B73696430120C08021A086170705F69645F" + "31122044C964271799F0631AE388BD150A873C5DD16B35F61BFF0300857AEEB8" + "454FA2"); + +// DeleteMultipleUsageInfoByKeySetIds test data. + +const std::string kHashedUsageInfoFileWithThreeKeySetIds = a2bs_hex( + "0A36080310012A300A0E220C6B65795F7365745F69645F310A0E220C6B65795F" + "7365745F69645F320A0E220C6B65795F7365745F69645F331220781BE848CE8A" + "0CE84FF563D54D30150A115EAB27F7023C19191EC41BDC4EDAA9"); +// Only contains key set ID 1 & 2. +const std::string kHashedUsageInfoFileWithTwoKeySetIds = a2bs_hex( + "0A26080310012A200A0E220C6B65795F7365745F69645F310A0E220C6B65795F" + "7365745F69645F321220B693E7142BF263FF51B6F8AF4DD7F20E2701059A841C" + "F947995A7B39354E1CA9"); +const std::string kHashedUsageInfoFileWithKeySet1 = a2bs_hex( + "0A16080310012A100A0E220C6B65795F7365745F69645F3112200FBBB47C89DE" + "484D02BFB4CB20B19BA43CBCAD6F4A78EFB295ACC66BA0B83B85"); +const std::string kHashedUsageInfoFileWithKeySet2 = a2bs_hex( + "0A16080310012A100A0E220C6B65795F7365745F69645F321220B5F53E5A1D8E" + "860196D2B3E027FFB32F11C1B2269784A904A3EA6E59C2A6A96D"); +const std::string kHashedUsageInfoFileWithKeySet3 = a2bs_hex( + "0A16080310012A100A0E220C6B65795F7365745F69645F331220F28C1B20A302" + "543F44659D995A58899A03B9D51C65FD6C05AD1E6D2BACACADA7"); + +const std::vector kHashedUsageInfoFileWithSingleKeySetList = { + kHashedUsageInfoFileWithKeySet1, kHashedUsageInfoFileWithKeySet2, + kHashedUsageInfoFileWithKeySet3}; + +const std::vector kHashedUsageInfoFileKeySetList = { + "key_set_id_1", "key_set_id_2", "key_set_id_3"}; + class MockFile : public File { public: MockFile() {} @@ -1999,6 +2017,7 @@ using ::testing::_; using ::testing::AllOf; using ::testing::DoAll; using ::testing::Eq; +using ::testing::Expectation; using ::testing::Gt; using ::testing::HasSubstr; using ::testing::InSequence; @@ -2078,6 +2097,10 @@ class DeviceFilesHlsAttributesTest class DeviceFilesUsageTableTest : public DeviceFilesTest, public ::testing::WithParamInterface {}; +class DeviceFilesDeleteMultipleUsageInfoTest + : public DeviceFilesTest, + public ::testing::WithParamInterface {}; + MATCHER(IsCreateFileFlagSet, "") { return FileSystem::kCreate & arg; } MATCHER_P(IsStrEq, str, "") { // Estimating the length of data. We can have gmock provide length @@ -2606,6 +2629,221 @@ TEST_F(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem) { } } +// From a usage info file containing 3 provider sessions, 2 will be +// deleted using the |key_set_id| associated with them. +// It is expected that once the provider sessions are deleted, the +// usage info file will be overwritten with only the remaining provider +// session token present. +TEST_P(DeviceFilesDeleteMultipleUsageInfoTest, DeleteAllButOne) { + static const std::string kUsageInfoFileName = "usage_info_file.bin"; + const int key_set_id_index = GetParam(); + + const std::string& result_hashed_usage_info_file = + kHashedUsageInfoFileWithSingleKeySetList[key_set_id_index]; + + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + std::string file_path; + ASSERT_TRUE(Properties::GetDeviceFilesBasePath(kSecurityLevelL1, &file_path)); + file_path += kUsageInfoFileName; + + EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(file_path)) + .WillOnce(Return(kHashedUsageInfoFileWithThreeKeySetIds.size())); + + // File read expectations. + MockFile* file_in = new MockFile(); + EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly)) + .WillOnce(Return(file_in)); + Expectation read_original = + EXPECT_CALL(*file_in, Read(NotNull(), _)) + .WillOnce( + DoAll(SetArrayArgument<0>( + kHashedUsageInfoFileWithThreeKeySetIds.cbegin(), + kHashedUsageInfoFileWithThreeKeySetIds.cend()), + Return(kHashedUsageInfoFileWithThreeKeySetIds.size()))); + + // File write expectations. + MockFile* file_out = new MockFile(); + EXPECT_CALL(file_system, + DoOpen(file_path, FileSystem::kCreate | FileSystem::kTruncate)) + .WillOnce(Return(file_out)); + EXPECT_CALL(*file_out, Write(StrEq(result_hashed_usage_info_file), _)) + .After(read_original) + .WillOnce(Return(result_hashed_usage_info_file.size())); + + const auto not_current_key_set = + [key_set_id_index](const std::string& key_set_id) { + return key_set_id != kHashedUsageInfoFileKeySetList[key_set_id_index]; + }; + std::vector to_remove; + std::copy_if(kHashedUsageInfoFileKeySetList.cbegin(), + kHashedUsageInfoFileKeySetList.cend(), + std::back_inserter(to_remove), not_current_key_set); + EXPECT_TRUE(device_files.DeleteMultipleUsageInfoByKeySetIds( + kUsageInfoFileName, to_remove)); +} + +INSTANTIATE_TEST_CASE_P(DeviceFilesTest, DeviceFilesDeleteMultipleUsageInfoTest, + ::testing::Range(0, 3)); + +// Delete all provider sessions from a usage info file. It is expected +// that the usage info file will be deleted (not written back to with +// an empty provider session list). +TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, DeleteAllKeySetIds) { + static const std::string kUsageInfoFileName = "usage_info_file.bin"; + + // Setup + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + std::string file_path; + ASSERT_TRUE(Properties::GetDeviceFilesBasePath(kSecurityLevelL1, &file_path)); + file_path += kUsageInfoFileName; + + // File read expectations. + MockFile* file_in = new MockFile(); + EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(file_path)) + .WillOnce(Return(kHashedUsageInfoFileWithThreeKeySetIds.size())); + EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly)) + .WillOnce(Return(file_in)); + EXPECT_CALL(*file_in, Read(NotNull(), _)) + .WillOnce(DoAll( + SetArrayArgument<0>(kHashedUsageInfoFileWithThreeKeySetIds.cbegin(), + kHashedUsageInfoFileWithThreeKeySetIds.cend()), + Return(kHashedUsageInfoFileWithThreeKeySetIds.size()))); + EXPECT_CALL(file_system, Remove(file_path)).WillOnce(Return(true)); + + // Remove all sessions, and the file should be deleted. + EXPECT_TRUE(device_files.DeleteMultipleUsageInfoByKeySetIds( + kUsageInfoFileName, kHashedUsageInfoFileKeySetList)); +} + +// If the key set IDs provided cannot be found in the usage info file, +// then no action should be taken, and the function returns true. +TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, DeleteNone) { + static const std::string kUsageInfoFileName = "usage_info_file.bin"; + + // Setup + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + std::string file_path; + ASSERT_TRUE(Properties::GetDeviceFilesBasePath(kSecurityLevelL1, &file_path)); + file_path += kUsageInfoFileName; + + EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(true)); + + // Call, not providing any key set IDs. Should return true without any + // action, assuming the file exists. + EXPECT_TRUE(device_files.DeleteMultipleUsageInfoByKeySetIds( + kUsageInfoFileName, std::vector())); + + MockFile* file_in = new MockFile(); + EXPECT_CALL(file_system, FileSize(file_path)) + .WillOnce(Return(kHashedUsageInfoFileWithThreeKeySetIds.size())); + EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly)) + .WillOnce(Return(file_in)); + EXPECT_CALL(*file_in, Read(NotNull(), _)) + .WillOnce(DoAll( + SetArrayArgument<0>(kHashedUsageInfoFileWithThreeKeySetIds.cbegin(), + kHashedUsageInfoFileWithThreeKeySetIds.cend()), + Return(kHashedUsageInfoFileWithThreeKeySetIds.size()))); + + // Call, providing key set IDs which do not exist in the usage info + // file. + const std::vector key_set_ids = {"fictional_key_set_id_1", + "fictional_key_set_id_2", + "fictional_key_set_id_3"}; + EXPECT_TRUE(device_files.DeleteMultipleUsageInfoByKeySetIds( + kUsageInfoFileName, key_set_ids)); +} + +// Delete a single key from the file. +// Test will delete "key_set_id_2" from the usage info file containing +// "key_set_id_1" & "key_set_id_2". +TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, DeleteOne) { + static const std::string kUsageInfoFileName = "usage_info_file.bin"; + + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + std::string file_path; + ASSERT_TRUE(Properties::GetDeviceFilesBasePath(kSecurityLevelL1, &file_path)); + file_path += kUsageInfoFileName; + + EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(file_path)) + .WillOnce(Return(kHashedUsageInfoFileWithTwoKeySetIds.size())); + + // File read expectations. + MockFile* file_in = new MockFile(); + EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly)) + .WillOnce(Return(file_in)); + Expectation read_original = + EXPECT_CALL(*file_in, Read(NotNull(), _)) + .WillOnce(DoAll( + SetArrayArgument<0>(kHashedUsageInfoFileWithTwoKeySetIds.cbegin(), + kHashedUsageInfoFileWithTwoKeySetIds.cend()), + Return(kHashedUsageInfoFileWithTwoKeySetIds.size()))); + + // File write expectations. + MockFile* file_out = new MockFile(); + EXPECT_CALL(file_system, + DoOpen(file_path, FileSystem::kCreate | FileSystem::kTruncate)) + .WillOnce(Return(file_out)); + EXPECT_CALL(*file_out, Write(StrEq(kHashedUsageInfoFileWithKeySet1), _)) + .After(read_original) + .WillOnce(Return(kHashedUsageInfoFileWithKeySet1.size())); + + const std::vector to_remove = {"key_set_id_2"}; + EXPECT_TRUE(device_files.DeleteMultipleUsageInfoByKeySetIds( + kUsageInfoFileName, to_remove)); +} + +// There are a few error cases where DeleteMultipleUsageInfoByKeySetIds() +// will fail. Such as if the specified file does not exist, or if it +// the file is found to have a hash mismatch. In both these cases, +// false should be returned. +TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, BadFile) { + static const std::string kUsageInfoFileName = "usage_info_file.bin"; + static const std::string kHashlessUsageInfoFile = a2bs_hex( + "0A36080310012A300A0E220C6B65795F7365745F69645F310A0E220C6B65795F" + "7365745F69645F320A0E220C6B65795F7365745F69645F331220000000000000" + "0000000000000000000000000000000000000000000000000000"); + // Setup + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + std::string file_path; + ASSERT_TRUE(Properties::GetDeviceFilesBasePath(kSecurityLevelL1, &file_path)); + file_path += kUsageInfoFileName; + + // File does not exist. + EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(false)); + EXPECT_FALSE(device_files.DeleteMultipleUsageInfoByKeySetIds( + kUsageInfoFileName, kHashedUsageInfoFileKeySetList)); + + // File is missing hash. + MockFile* file_in = new MockFile(); + EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(file_path)) + .WillOnce(Return(kHashlessUsageInfoFile.size())); + EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly)) + .WillOnce(Return(file_in)); + EXPECT_CALL(*file_in, Read(NotNull(), _)) + .WillOnce(DoAll(SetArrayArgument<0>(kHashlessUsageInfoFile.cbegin(), + kHashlessUsageInfoFile.cend()), + Return(kHashlessUsageInfoFile.size()))); + + EXPECT_CALL(file_system, Remove(file_path)).WillOnce(Return(true)); + // Remove all sessions, and the file should be deleted. + EXPECT_FALSE(device_files.DeleteMultipleUsageInfoByKeySetIds( + kUsageInfoFileName, kHashedUsageInfoFileKeySetList)); +} + TEST_F(DeviceFilesUsageInfoTest, ListNullParam) { MockFileSystem file_system; @@ -3209,8 +3447,9 @@ TEST_P(DeviceFilesUsageTableTest, Read) { std::vector usage_entry_info; CdmUsageTableHeader usage_table_header; - ASSERT_TRUE(device_files.RetrieveUsageTableInfo(&usage_table_header, - &usage_entry_info)); + bool lru_upgrade; + ASSERT_TRUE(device_files.RetrieveUsageTableInfo( + &usage_table_header, &usage_entry_info, &lru_upgrade)); EXPECT_EQ(kUsageTableInfoTestData[index].usage_table_header, usage_table_header); EXPECT_EQ(index + 1u, usage_entry_info.size()); @@ -3222,10 +3461,52 @@ TEST_P(DeviceFilesUsageTableTest, Read) { usage_entry_info[i].key_set_id); EXPECT_EQ(kUsageEntriesTestData[i].usage_info_file_name, usage_entry_info[i].usage_info_file_name); + EXPECT_EQ(kUsageEntriesTestData[i].last_use_time, + usage_entry_info[i].last_use_time); + EXPECT_EQ(kUsageEntriesTestData[i].offline_license_expiry_time, + usage_entry_info[i].offline_license_expiry_time); } + + EXPECT_FALSE(lru_upgrade); } INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageTableTest, ::testing::Range(0, 6)); +TEST_F(DeviceFilesUsageTableTest, ReadWithoutLruData) { + // Setup file. + MockFile* file = new MockFile(); + EXPECT_CALL(*file, Read(NotNull(), Eq(kUsageTableWithoutLruData.size()))) + .WillOnce(DoAll(SetArrayArgument<0>(kUsageTableWithoutLruData.cbegin(), + kUsageTableWithoutLruData.cend()), + Return(kUsageTableWithoutLruData.size()))); + // Setup filesystem. + const std::string path = + device_base_path_ + DeviceFiles::GetUsageTableFileName(); + MockFileSystem file_system; + EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(path))) + .WillRepeatedly(Return(kUsageTableWithoutLruData.size())); + EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::vector usage_entry_info; + CdmUsageTableHeader usage_table_header; + bool lru_upgrade; + ASSERT_TRUE(device_files.RetrieveUsageTableInfo( + &usage_table_header, &usage_entry_info, &lru_upgrade)); + + EXPECT_EQ(ArraySize(kUsageEntriesWithoutLruData), usage_entry_info.size()); + + for (size_t i = 0; i < ArraySize(kUsageEntriesWithoutLruData); ++i) { + const CdmUsageEntryInfo& expected_entry = kUsageEntriesWithoutLruData[i]; + const CdmUsageEntryInfo& retrieved_entry = usage_entry_info[i]; + EXPECT_EQ(expected_entry, retrieved_entry); + } + + EXPECT_TRUE(lru_upgrade); +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/mock_clock.h b/libwvdrmengine/cdm/core/test/mock_clock.h index 58b3fe8c..98aae348 100644 --- a/libwvdrmengine/cdm/core/test/mock_clock.h +++ b/libwvdrmengine/cdm/core/test/mock_clock.h @@ -15,6 +15,18 @@ class MockClock : public Clock { MOCK_METHOD0(GetCurrentTime, int64_t()); }; +// Frozen clock will always return the same value for the current time. +// Intended to be used for testing where using the actual time would +// cause flaky tests. +class FrozenClock : public Clock { + int64_t always_time_; + + public: + FrozenClock(int64_t always_time = 0) : always_time_(always_time) {} + int64_t GetCurrentTime() override { return always_time_; } + void SetTime(int64_t new_time) { always_time_ = new_time; } +}; + } // wvcdm #endif // CDM_TEST_MOCK_CLOCK_H_ diff --git a/libwvdrmengine/cdm/core/test/test_printers.cpp b/libwvdrmengine/cdm/core/test/test_printers.cpp index d513599e..cb883b42 100644 --- a/libwvdrmengine/cdm/core/test/test_printers.cpp +++ b/libwvdrmengine/cdm/core/test/test_printers.cpp @@ -884,6 +884,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case USAGE_INVALID_PARAMETERS_1: *os << "USAGE_INVALID_PARAMETERS_1"; break; + case USAGE_INVALID_PARAMETERS_2: + *os << "USAGE_INVALID_PARAMETERS_2"; + break; case USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE: *os << "USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE"; break; diff --git a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp index 5af2ac7c..94910e63 100644 --- a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include "arraysize.h" @@ -14,6 +15,7 @@ #include "crypto_session.h" #include "device_files.h" #include "file_store.h" +#include "mock_clock.h" #include "test_base.h" #include "test_printers.h" #include "wv_cdm_constants.h" @@ -25,6 +27,8 @@ namespace { const std::string kEmptyString; +constexpr int64_t kDefaultExpireDuration = 33 * 24 * 60 * 60; // 33 Days + const CdmUsageTableHeader kEmptyUsageTableHeader; const CdmUsageTableHeader kUsageTableHeader = "some usage table header"; const CdmUsageTableHeader kAnotherUsageTableHeader = @@ -44,60 +48,80 @@ const CdmUsageEntryInfo kUsageEntryInfoOfflineLicense1 = { /* storage_type = */ kStorageLicense, /* key_set_id = */ "offline_key_set_1", /* usage_info_file_name = */ "", -}; + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ kDefaultExpireDuration}; const CdmUsageEntryInfo kUsageEntryInfoOfflineLicense2 = { /* storage_type = */ kStorageLicense, /* key_set_id = */ "offline_key_set_2", /* usage_info_file_name = */ "", -}; + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ kDefaultExpireDuration}; const CdmUsageEntryInfo kUsageEntryInfoOfflineLicense3 = { /* storage_type = */ kStorageLicense, /* key_set_id = */ "offline_key_set_3", - /* usage_info_file_name = */ ""}; + /* usage_info_file_name = */ "", + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ kDefaultExpireDuration}; const CdmUsageEntryInfo kUsageEntryInfoOfflineLicense4 = { /* storage_type = */ kStorageLicense, /* key_set_id = */ "offline_key_set_4", - /* usage_info_file_name = */ ""}; + /* usage_info_file_name = */ "", + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ kDefaultExpireDuration}; const CdmUsageEntryInfo kUsageEntryInfoOfflineLicense5 = { /* storage_type = */ kStorageLicense, /* key_set_id = */ "offline_key_set_5", - /* usage_info_file_name = */ ""}; + /* usage_info_file_name = */ "", + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ kDefaultExpireDuration}; const CdmUsageEntryInfo kUsageEntryInfoOfflineLicense6 = { /* storage_type = */ kStorageLicense, /* key_set_id = */ "offline_key_set_6", - /* usage_info_file_name = */ ""}; + /* usage_info_file_name = */ "", + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ kDefaultExpireDuration}; const CdmUsageEntryInfo kUsageEntryInfoSecureStop1 = { /* storage_type = */ kStorageUsageInfo, /* key_set_id = */ "secure_stop_key_set_1", /* usage_info_file_name = */ "usage_info_file_1", -}; + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ 0}; const CdmUsageEntryInfo kUsageEntryInfoSecureStop2 = { /* storage_type = */ kStorageUsageInfo, /* key_set_id = */ "secure_stop_key_set_2", /* usage_info_file_name = */ "usage_info_file_2", -}; + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ 0}; const CdmUsageEntryInfo kUsageEntryInfoSecureStop3 = { /* storage_type = */ kStorageUsageInfo, /* key_set_id = */ "secure_stop_key_set_3", - /* usage_info_file_name = */ "usage_info_file_3"}; + /* usage_info_file_name = */ "usage_info_file_3", + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ 0}; const CdmUsageEntryInfo kUsageEntryInfoSecureStop4 = { /* storage_type = */ kStorageUsageInfo, /* key_set_id = */ "secure_stop_key_set_4", - /* usage_info_file_name = */ "usage_info_file_4"}; + /* usage_info_file_name = */ "usage_info_file_4", + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ 0}; const CdmUsageEntryInfo kUsageEntryInfoSecureStop5 = { /* storage_type = */ kStorageUsageInfo, /* key_set_id = */ "secure_stop_key_set_5", - /* usage_info_file_name = */ "usage_info_file_5"}; + /* usage_info_file_name = */ "usage_info_file_5", + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ 0}; const CdmUsageEntryInfo kUsageEntryInfoStorageTypeUnknown = { /* storage_type = */ kStorageTypeUnknown, /* key_set_id = */ "", /* usage_info_file_name = */ "", -}; + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ 0}; const CdmUsageEntryInfo kDummyUsageEntryInfo = { /* storage_type = */ kStorageLicense, /* key_set_id = */ "DummyKsid", /* usage_info_file_name = */ "", -}; + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ kDefaultExpireDuration}; const std::vector kEmptyLicenseList; @@ -164,6 +188,72 @@ int64_t kPlaybackStartTime = 1030005; int64_t kPlaybackDuration = 300; int64_t kGracePeriodEndTime = 60; +// ==== LRU Upgrade Data ==== +const CdmUsageTableHeader kUpgradableUsageTableHeader = {0}; + +// Usage entries. +const CdmUsageEntryInfo kUpgradableUsageEntryInfo1 = { + /* storage_type = */ kStorageLicense, + /* key_set_id = */ "offline_key_set_1", + /* usage_info_file_name = */ "", + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ 0}; +const CdmUsageEntryInfo kUpgradableUsageEntryInfo2 = { + /* storage_type = */ kStorageUsageInfo, + /* key_set_id = */ "streaming_key_set_2", + /* usage_info_file_name = */ "streaming_license_file_2", + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ 0}; +const CdmUsageEntryInfo kUpgradableUsageEntryInfo3 = { + /* storage_type = */ kStorageLicense, + /* key_set_id = */ "offline_key_set_3", + /* usage_info_file_name = */ "", + /* last_use_time = */ 0, + /* offline_license_expiry_time = */ 0}; +std::vector kUpgradableUsageEntryInfoList; + +// Offline license 1. +// license_start_time = 1563399000 +// license_duration_seconds = 259200 (3 days) +// rental_duration_seconds = 604800 (7 days) +// playback_duration_seconds = 86400 (1 day) +const CdmKeyResponse kUpgradableLicenseInfo1 = a2bs_hex( + "08021214120C2080F5242880A3053080E90F20D8A6BEE9051A20D5F7ACE8D84A166C69BB" + "27523C84C019464B90AA9BF06B8332004839119BFD14"); +// Streaming license 2. +// license_start_time = 1563399000 +const CdmKeyResponse kUpgradableLicenseInfo2 = a2bs_hex( + "0802120620D8A6BEE9051A201956F2FD69E5E96DA8C65FDD04A3C294E484F219F2B1A8DD" + "C2B0737F6EF5BD22"); +// Offline license 3. +// license_start_time = 1563399000 +// license_duration_seconds = 0 (unlimited) +// rental_duration_seconds = 604800 (7 days) +// playback_duration_seconds = 86400 (1 day) +const CdmKeyResponse kUpgradableLicenseInfo3 = a2bs_hex( + "08021212120A2080F5242880A305300020D8A6BEE9051A207B09896F46C4EE443170E215" + "B2D8D5F072951027B152F4758AC3A339D7C7B4CE"); +std::vector kUpgradableLicenseInfoList; +std::vector kUpgradableLicenseDataList; +// Same as Offline license 1, but without signature. +const CdmKeyResponse kUnsignedUpgradableLicenseInfo1 = + a2bs_hex("08021214120C2080F5242880A3053080E90F20D8A6BEE905"); +// Same as streaming license 2, but message type is certificate, not license. +const CdmKeyResponse kWrongTypedUpgradableLicenseInfo2 = a2bs_hex( + "0805120620D8A6BEE9051A201956F2FD69E5E96DA8C65FDD04A3C294E484F219F2B1A8DD" + "C2B0737F6EF5BD22"); + +const int64_t kLruBaseTime = 1563399000; +const int64_t kUpgradedUsageEntryInfo1LastUsedTime = kLruBaseTime; +const int64_t kUpgradedUsageEntryInfo1ExpireTime = kLruBaseTime + 259200; +const int64_t kUpgradedUsageEntryInfo2LastUsedTime = kLruBaseTime; +const int64_t kUpgradedUsageEntryInfo2ExpireTime = 0; // Unset +const int64_t kUpgradedUsageEntryInfo3LastUsedTime = kLruBaseTime; +const int64_t kUpgradedUsageEntryInfo3ExpireTime = + kLruBaseTime + 604800 + 86400; +const CdmUsageTableHeader kUpgradedUsageTableHeader = {0}; +std::vector kUpgradedUsageEntryInfoList; + namespace { void InitVectorConstants() { @@ -214,6 +304,40 @@ void InitVectorConstants() { for (size_t i = 0; i < kLicenseArraySize; i++) { kLicenseList.push_back(kLicenseArray[i]); } + + // LRU Data. + kUpgradableUsageEntryInfoList.push_back(kUpgradableUsageEntryInfo1); + kUpgradableUsageEntryInfoList.push_back(kUpgradableUsageEntryInfo2); + kUpgradableUsageEntryInfoList.push_back(kUpgradableUsageEntryInfo3); + + kUpgradableLicenseInfoList.push_back(kUpgradableLicenseInfo1); + kUpgradableLicenseInfoList.push_back(kUpgradableLicenseInfo2); + kUpgradableLicenseInfoList.push_back(kUpgradableLicenseInfo3); + + kUpgradedUsageEntryInfoList.push_back(kUpgradableUsageEntryInfo1); + kUpgradedUsageEntryInfoList.back().last_use_time = + kUpgradedUsageEntryInfo1LastUsedTime; + kUpgradedUsageEntryInfoList.back().offline_license_expiry_time = + kUpgradedUsageEntryInfo1ExpireTime; + kUpgradedUsageEntryInfoList.push_back(kUpgradableUsageEntryInfo2); + kUpgradedUsageEntryInfoList.back().last_use_time = + kUpgradedUsageEntryInfo2LastUsedTime; + kUpgradedUsageEntryInfoList.back().offline_license_expiry_time = + kUpgradedUsageEntryInfo2ExpireTime; + kUpgradedUsageEntryInfoList.push_back(kUpgradableUsageEntryInfo3); + kUpgradedUsageEntryInfoList.back().last_use_time = + kUpgradedUsageEntryInfo3LastUsedTime; + kUpgradedUsageEntryInfoList.back().offline_license_expiry_time = + kUpgradedUsageEntryInfo3ExpireTime; + + for (size_t i = 0; i < kUpgradableLicenseInfoList.size(); ++i) { + DeviceFiles::CdmLicenseData license_data; + license_data.key_set_id = kUpgradableUsageEntryInfoList[i].key_set_id; + license_data.state = kActiveLicenseState; + license_data.license = kUpgradableLicenseInfoList[i]; + license_data.usage_entry_number = i; + kUpgradableLicenseDataList.push_back(license_data); + } } void ToVector(std::vector& vec, const CdmUsageEntryInfo* arr, @@ -225,22 +349,58 @@ void ToVector(std::vector& vec, const CdmUsageEntryInfo* arr, } } +// Used to quickly populate a vector of CdmUsageEntryInfo structs with LRU +// information. This is intended to allow tests which are not concerned with +// the LRU replacement policy of the UsageTableHeader, but are affected by its +// presents. +void GenericLruUpgrade(std::vector* usage_entry_info_list, + int64_t last_use_time = kLruBaseTime, + int64_t offline_license_expiry_time = + kLruBaseTime + kDefaultExpireDuration) { + if (usage_entry_info_list == nullptr) { + return; + } + for (auto& usage_entry_info : *usage_entry_info_list) { + usage_entry_info.last_use_time = last_use_time; + if (usage_entry_info.storage_type == kStorageLicense) { + usage_entry_info.offline_license_expiry_time = + offline_license_expiry_time; + } else { + usage_entry_info.offline_license_expiry_time = 0; + } + } +} + }; // namespace class MockDeviceFiles : public DeviceFiles { public: MockDeviceFiles() : DeviceFiles(&file_system_) { Init(kSecurityLevelL1); } - MOCK_METHOD2(RetrieveUsageTableInfo, - bool(CdmUsageTableHeader*, std::vector*)); + MOCK_METHOD3(RetrieveLicense, + bool(const std::string&, DeviceFiles::CdmLicenseData*, + DeviceFiles::ResponseType*)); + // Used for calling the non-mocked method. + bool RealRetrieveLicense(const std::string& key_set_id, + DeviceFiles::CdmLicenseData* license_data, + DeviceFiles::ResponseType* response) { + return DeviceFiles::RetrieveLicense(key_set_id, license_data, response); + } + + MOCK_METHOD3(RetrieveUsageTableInfo, + bool(CdmUsageTableHeader*, std::vector*, + bool* lru_upgrade)); MOCK_METHOD2(StoreUsageTableInfo, bool(const CdmUsageTableHeader&, const std::vector&)); MOCK_METHOD2(DeleteUsageInfo, bool(const std::string&, const std::string&)); + MOCK_METHOD2(DeleteMultipleUsageInfoByKeySetIds, + bool(const std::string&, const std::vector&)); MOCK_METHOD7(RetrieveUsageInfoByKeySetId, bool(const std::string&, const std::string&, std::string*, CdmKeyMessage*, CdmKeyResponse*, CdmUsageEntry*, uint32_t*)); + MOCK_METHOD1(DeleteLicense, bool(const std::string&)); MOCK_METHOD0(DeleteAllLicenses, bool()); MOCK_METHOD0(DeleteAllUsageInfo, bool()); MOCK_METHOD0(DeleteUsageTableInfo, bool()); @@ -290,6 +450,8 @@ class MockUsageTableHeader : public UsageTableHeader { using ::testing::_; using ::testing::AllOf; using ::testing::AtMost; +using ::testing::ContainerEq; +using ::testing::Contains; using ::testing::DoAll; using ::testing::ElementsAreArray; using ::testing::Ge; @@ -319,6 +481,12 @@ class UsageTableHeaderTest : public WvCdmTestBase { WvCdmTestBase::SetUp(); // UsageTableHeader will take ownership of the pointer device_files_ = new MockDeviceFiles(); + ON_CALL(*device_files_, RetrieveLicense(_, _, _)) + .WillByDefault( + Invoke(device_files_, &MockDeviceFiles::RealRetrieveLicense)); + + device_files_->DeleteAllFiles(); + crypto_session_ = new MockCryptoSession(&crypto_metrics_); usage_table_header_ = new UsageTableHeader(); @@ -326,6 +494,13 @@ class UsageTableHeaderTest : public WvCdmTestBase { // usage_table_header_ object takes ownership of these objects usage_table_header_->SetDeviceFiles(device_files_); usage_table_header_->SetCryptoSession(crypto_session_); + + // Several UsageTableHeader methods will make calls to GetCurrentTime(), + // but the test expectations require a fixed value. Frozen clock provides + // the test cases with a fixed value of 0, without needing to set + // EXPECT_CALL() in every test case. + test_clock_.reset(new FrozenClock()); + usage_table_header_->SetClock(test_clock_.get()); } // UsageTableHeaderTest maintains ownership of returned pointer @@ -346,6 +521,7 @@ class UsageTableHeaderTest : public WvCdmTestBase { mock_usage_table_header->SetCryptoSession(crypto_session_); usage_table_header_ = mock_usage_table_header; + usage_table_header_->SetClock(test_clock_.get()); return mock_usage_table_header; } @@ -356,10 +532,11 @@ class UsageTableHeaderTest : public WvCdmTestBase { void Init(CdmSecurityLevel security_level, const CdmUsageTableHeader& usage_table_header, const std::vector& usage_entry_info_vector) { - EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(usage_table_header), SetArgPointee<1>(usage_entry_info_vector), - Return(true))); + SetArgPointee<2>(false), Return(true))); EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(usage_table_header)) .WillOnce(Return(NO_ERROR)); EXPECT_TRUE(usage_table_header_->Init(security_level, crypto_session_)); @@ -369,6 +546,7 @@ class UsageTableHeaderTest : public WvCdmTestBase { metrics::CryptoMetrics crypto_metrics_; MockCryptoSession* crypto_session_; UsageTableHeader* usage_table_header_; + std::unique_ptr test_clock_; }; TEST_F(UsageTableHeaderTest, InitError) { @@ -389,10 +567,11 @@ class UsageTableHeaderInitializationTest }; TEST_P(UsageTableHeaderInitializationTest, CreateUsageTableHeader) { - EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), SetArgPointee<1>(kEmptyUsageEntryInfoVector), - Return(false))); + SetArgPointee<2>(false), Return(false))); EXPECT_CALL(*device_files_, ListLicenses(NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kEmptyLicenseList), Return(false))); EXPECT_CALL(*device_files_, ListUsageInfoFiles(NotNull())) @@ -410,10 +589,11 @@ TEST_P(UsageTableHeaderInitializationTest, CreateUsageTableHeader) { } TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveLicenses) { - EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), SetArgPointee<1>(kEmptyUsageEntryInfoVector), - Return(false))); + SetArgPointee<2>(false), Return(false))); EXPECT_CALL(*device_files_, ListLicenses(NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kLicenseList), Return(true))); EXPECT_CALL(*device_files_, ListUsageInfoFiles(NotNull())) @@ -429,16 +609,21 @@ TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveLicenses) { .Times(2) .WillRepeatedly(Return(true)); - for (size_t i = 0; i < kLicenseList.size(); ++i) + for (size_t i = 0; i < kLicenseList.size(); ++i) { device_files_->DeleteLicense(kLicenseList[i]); + EXPECT_CALL(*device_files_, + RetrieveLicense(kLicenseList[i], NotNull(), NotNull())) + .WillOnce(Return(false)); + } EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); } TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveUsageInfo) { - EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), SetArgPointee<1>(kEmptyUsageEntryInfoVector), - Return(false))); + SetArgPointee<2>(false), Return(false))); EXPECT_CALL(*device_files_, ListLicenses(NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kEmptyLicenseList), Return(false))); EXPECT_CALL(*device_files_, ListUsageInfoFiles(NotNull())) @@ -461,9 +646,11 @@ TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveUsageInfo) { } TEST_P(UsageTableHeaderInitializationTest, UsageTableHeaderExists) { - EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), - SetArgPointee<1>(kUsageEntryInfoVector), Return(true))); + SetArgPointee<1>(kUsageEntryInfoVector), + SetArgPointee<2>(false), Return(true))); EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) .WillOnce(Return(NO_ERROR)); @@ -473,9 +660,11 @@ TEST_P(UsageTableHeaderInitializationTest, UsageTableHeaderExists) { TEST_P(UsageTableHeaderInitializationTest, 200UsageEntries) { std::vector usage_entries_200 = k201UsageEntryInfoVector; usage_entries_200.resize(200); - EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), - SetArgPointee<1>(usage_entries_200), Return(true))); + SetArgPointee<1>(usage_entries_200), + SetArgPointee<2>(false), Return(true))); EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) .WillOnce(Return(NO_ERROR)); @@ -484,10 +673,11 @@ TEST_P(UsageTableHeaderInitializationTest, 200UsageEntries) { TEST_P(UsageTableHeaderInitializationTest, 201UsageEntries_AddEntryFails_UsageTableHeaderRecreated) { - EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), SetArgPointee<1>(k201UsageEntryInfoVector), - Return(true))); + SetArgPointee<2>(false), Return(true))); SecurityLevel security_level = (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; @@ -519,10 +709,11 @@ TEST_P(UsageTableHeaderInitializationTest, std::vector usage_entries_202 = k201UsageEntryInfoVector; usage_entries_202.push_back(kDummyUsageEntryInfo); - EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), SetArgPointee<1>(k201UsageEntryInfoVector), - Return(true))); + SetArgPointee<2>(false), Return(true))); EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) .WillOnce(Return(NO_ERROR)); EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) @@ -562,10 +753,11 @@ TEST_P(UsageTableHeaderInitializationTest, std::vector usage_entries_202 = k201UsageEntryInfoVector; usage_entries_202.push_back(kDummyUsageEntryInfo); - EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), SetArgPointee<1>(k201UsageEntryInfoVector), - Return(true))); + SetArgPointee<2>(false), Return(true))); EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) .WillOnce(Return(NO_ERROR)); @@ -615,7 +807,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailed_UnknownError) { kUsageEntryInfoOfflineLicense1.storage_type == kStorageLicense, kUsageEntryInfoOfflineLicense1.key_set_id, kUsageEntryInfoOfflineLicense1.usage_info_file_name, - &usage_entry_number)); + kEmptyString /* license */, &usage_entry_number)); } TEST_F(UsageTableHeaderTest, AddEntry_UsageEntryTooSmall) { @@ -633,7 +825,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_UsageEntryTooSmall) { kUsageEntryInfoOfflineLicense1.storage_type == kStorageLicense, kUsageEntryInfoOfflineLicense1.key_set_id, kUsageEntryInfoOfflineLicense1.usage_info_file_name, - &usage_entry_number)); + kEmptyString /* license */, &usage_entry_number)); } TEST_F(UsageTableHeaderTest, AddEntry_NextConsecutiveOfflineUsageEntry) { @@ -663,7 +855,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_NextConsecutiveOfflineUsageEntry) { kUsageEntryInfoOfflineLicense2.storage_type == kStorageLicense, kUsageEntryInfoOfflineLicense2.key_set_id, kUsageEntryInfoOfflineLicense2.usage_info_file_name, - &usage_entry_number)); + kEmptyString /* license */, &usage_entry_number)); EXPECT_EQ(expect_usage_entry_number, usage_entry_number); } @@ -694,7 +886,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_NextConsecutiveSecureStopUsageEntry) { kUsageEntryInfoSecureStop2.storage_type == kStorageLicense, kUsageEntryInfoSecureStop2.key_set_id, kUsageEntryInfoSecureStop2.usage_info_file_name, - &usage_entry_number)); + kEmptyString /* license */, &usage_entry_number)); EXPECT_EQ(expect_usage_entry_number, usage_entry_number); } @@ -728,7 +920,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_SkipUsageEntries) { kUsageEntryInfoSecureStop2.storage_type == kStorageLicense, kUsageEntryInfoSecureStop2.key_set_id, kUsageEntryInfoSecureStop2.usage_info_file_name, - &usage_entry_number)); + kEmptyString /* license */, &usage_entry_number)); EXPECT_EQ(expect_usage_entry_number, usage_entry_number); } @@ -768,7 +960,7 @@ TEST_F(UsageTableHeaderTest, kUsageEntryInfoOfflineLicense6.storage_type == kStorageLicense, kUsageEntryInfoOfflineLicense6.key_set_id, kUsageEntryInfoOfflineLicense6.usage_info_file_name, - &usage_entry_number)); + kEmptyString /* license */, &usage_entry_number)); // Verify added/deleted usage entry number and entries EXPECT_EQ(expected_usage_entry_number, usage_entry_number); @@ -791,9 +983,10 @@ TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailsTwice_SucceedsThirdTime) { // Initialize and setup MockUsageTableHeader* mock_usage_table_header = SetUpMock(); - Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); std::vector usage_entry_info_vector_at_start = k10UsageEntryInfoVector; + GenericLruUpgrade(&usage_entry_info_vector_at_start); + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector_at_start); uint32_t usage_entry_number_first_to_be_deleted; // randomly chosen uint32_t usage_entry_number_second_to_be_deleted; // randomly chosen @@ -828,7 +1021,7 @@ TEST_F(UsageTableHeaderTest, kUsageEntryInfoOfflineLicense6.storage_type == kStorageLicense, kUsageEntryInfoOfflineLicense6.key_set_id, kUsageEntryInfoOfflineLicense6.usage_info_file_name, - &usage_entry_number)); + kEmptyString /* license */, &usage_entry_number)); // Verify added/deleted usage entry number and entries EXPECT_EQ(expected_usage_entry_number, usage_entry_number); @@ -890,7 +1083,7 @@ TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailsThrice) { kUsageEntryInfoOfflineLicense6.storage_type == kStorageLicense, kUsageEntryInfoOfflineLicense6.key_set_id, kUsageEntryInfoOfflineLicense6.usage_info_file_name, - &usage_entry_number)); + kEmptyString /* license */, &usage_entry_number)); // Verify deleted usage entry number and entries EXPECT_LE(0u, usage_entry_number_first_to_be_deleted); @@ -944,7 +1137,7 @@ TEST_F(UsageTableHeaderTest, UpdateEntry_CryptoSessionError) { Return(UPDATE_USAGE_ENTRY_UNKNOWN_ERROR))); EXPECT_NE(NO_ERROR, - usage_table_header_->UpdateEntry(crypto_session_, &usage_entry)); + usage_table_header_->UpdateEntry(0, crypto_session_, &usage_entry)); } TEST_F(UsageTableHeaderTest, UpdateEntry) { @@ -962,7 +1155,7 @@ TEST_F(UsageTableHeaderTest, UpdateEntry) { .WillOnce(Return(true)); EXPECT_EQ(NO_ERROR, - usage_table_header_->UpdateEntry(crypto_session_, &usage_entry)); + usage_table_header_->UpdateEntry(0, crypto_session_, &usage_entry)); } TEST_F(UsageTableHeaderTest, @@ -1224,13 +1417,10 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastSecureStopEntry) { // # of usage entries 5 2 TEST_F(UsageTableHeaderTest, DeleteEntry_LastOfflineEntriesHaveMissingLicenses) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { + std::vector usage_entry_info_vector = { kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); uint32_t usage_entry_number_to_be_deleted = @@ -1250,6 +1440,12 @@ TEST_F(UsageTableHeaderTest, UnorderedElementsAre(kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown))) .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, + NotNull(), NotNull())); + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, + NotNull(), NotNull())); EXPECT_EQ(NO_ERROR, usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, @@ -1342,21 +1538,17 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastSecureStopEntriesAreMissing) { // # of usage entries 5 2 TEST_F(UsageTableHeaderTest, DeleteEntry_LastOfflineEntriesHaveIncorrectUsageEntryNumber) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { + std::vector usage_entry_info_vector = { kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); - Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = + const uint32_t usage_entry_number_to_be_deleted = usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; DeviceFiles::ResponseType sub_error_code; - const DeviceFiles::CdmLicenseData license_data_1{ + const DeviceFiles::CdmLicenseData offline_license_3_data{ usage_entry_info_vector[usage_entry_info_vector.size() - 1].key_set_id, kActiveLicenseState, kPsshData, @@ -1371,10 +1563,11 @@ TEST_F(UsageTableHeaderTest, kEmptyAppParameters, kUsageEntry, static_cast(usage_entry_info_vector.size() - 2)}; - EXPECT_TRUE(device_files_->StoreLicense(license_data_1, &sub_error_code)); + EXPECT_TRUE( + device_files_->StoreLicense(offline_license_3_data, &sub_error_code)); EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); - const DeviceFiles::CdmLicenseData license_data_2{ + const DeviceFiles::CdmLicenseData offline_license_2_data{ usage_entry_info_vector[usage_entry_info_vector.size() - 2].key_set_id, kActiveLicenseState, kPsshData, @@ -1389,7 +1582,8 @@ TEST_F(UsageTableHeaderTest, kEmptyAppParameters, kUsageEntry, static_cast(usage_entry_info_vector.size() - 3)}; - EXPECT_TRUE(device_files_->StoreLicense(license_data_2, &sub_error_code)); + EXPECT_TRUE( + device_files_->StoreLicense(offline_license_2_data, &sub_error_code)); EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); @@ -1398,13 +1592,16 @@ TEST_F(UsageTableHeaderTest, ShrinkUsageTableHeader(usage_entry_number_to_be_deleted, NotNull())) .WillOnce( DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); - EXPECT_CALL(*device_files_, StoreUsageTableInfo( kAnotherUsageTableHeader, UnorderedElementsAre(kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown))) .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, RetrieveLicense(offline_license_3_data.key_set_id, + NotNull(), NotNull())); + EXPECT_CALL(*device_files_, RetrieveLicense(offline_license_2_data.key_set_id, + NotNull(), NotNull())); EXPECT_EQ(NO_ERROR, usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, @@ -1617,6 +1814,8 @@ TEST_F(UsageTableHeaderTest, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3))) .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + RetrieveLicense(license_data.key_set_id, NotNull(), NotNull())); EXPECT_EQ(NO_ERROR, usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, @@ -1781,6 +1980,8 @@ TEST_F(UsageTableHeaderTest, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3))) .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, + RetrieveLicense(license_data.key_set_id, NotNull(), NotNull())); EXPECT_EQ(NO_ERROR, usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, @@ -1960,6 +2161,13 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntryIsOffline) { kUsageEntryInfoOfflineLicense3, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3))) .WillOnce(Return(true)); + // Expecting three calls to RetrieveLicense(), twice by usage table when + // swapping (probing for swap, and the actual swap_, then by test case + // to verify data is still there. + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, + NotNull(), NotNull())) + .Times(3); EXPECT_EQ(NO_ERROR, usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, @@ -2182,6 +2390,13 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreOfflineAndUnknknown) { kUsageEntryInfoOfflineLicense3, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown))) .WillOnce(Return(true)); + // Expecting three calls to RetrieveLicense(), twice by usage table when + // swapping (probing for swap, and the actual swap_, then by test case + // to verify data is still there. + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, + NotNull(), NotNull())) + .Times(3); EXPECT_EQ(NO_ERROR, usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, @@ -2324,9 +2539,11 @@ TEST_F(UsageTableHeaderTest, StaleHeader) { ToVector(usage_entry_info_vector, usage_entry_info_array, sizeof(usage_entry_info_array)); - EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull())) + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), - SetArgPointee<1>(usage_entry_info_vector), Return(true))); + SetArgPointee<1>(usage_entry_info_vector), + SetArgPointee<2>(false), Return(true))); EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) .WillOnce(Return(LOAD_USAGE_HEADER_GENERATION_SKEW)); EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) @@ -2410,4 +2627,853 @@ TEST_F(UsageTableHeaderTest, Shrink_MoreThanTable) { NO_ERROR); } +// LRU Usage Table Upgrade Test + +// Initial Test state: +// 1. Table info is load from device files and device files reports that +// the table header and entries are configured for LRU. +// 2. No upgrading action is taken. +TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_NoAction) { + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUpgradableUsageTableHeader), + SetArgPointee<1>(kUpgradableUsageEntryInfoList), + SetArgPointee<2>(/* lru_upgrade = */ false), + Return(true))); + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(kUpgradableUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + + // These function are called specifically by the LRU upgrading system. + EXPECT_CALL(*device_files_, RetrieveLicense(_, _, _)).Times(0); + EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId(_, _, _, _, _, _, _)) + .Times(0); + + EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); + EXPECT_EQ(usage_table_header_->usage_entry_info(), + kUpgradableUsageEntryInfoList); +} + +// Initial Test state: +// 1. Table info is load from device files; however, device files reports +// that the table has not been configured for upgrade. +// 2. The usage table header will load license or usage information to +// determine appropriate expiry and last_used times. +TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_Succeed) { + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUpgradableUsageTableHeader), + SetArgPointee<1>(kUpgradableUsageEntryInfoList), + SetArgPointee<2>(/* lru_upgrade = */ true), + Return(true))); + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(kUpgradableUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + + for (size_t i = 0; i < kUpgradableUsageEntryInfoList.size(); ++i) { + const CdmUsageEntryInfo& info = kUpgradableUsageEntryInfoList[i]; + + if (info.storage_type == kStorageLicense) { + EXPECT_CALL(*device_files_, + RetrieveLicense(info.key_set_id, NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(kUpgradableLicenseDataList[i]), + Return(true))); + } else { + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + info.usage_info_file_name, info.key_set_id, NotNull(), + NotNull(), NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<4>(kUpgradableLicenseInfoList[i]), + SetArgPointee<6>(static_cast(i)), + Return(true))); + } + } + + EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); + EXPECT_EQ(usage_table_header_->usage_entry_info(), + kUpgradedUsageEntryInfoList); +} + +// Initial Test state: +// 1. Table info is load from device files and must be upgraded. +// A few entries have the incorrect storage type value. +// 2. During the LRU upgrade process, any license that cannot be +// validated are cleared, and marked to be deleted. +// +// Entry# Storage type Result info +// ====== ============ =========== +// 0 Offline Upgraded +// 1 Unknown Cleared +// 2 Invalid (99) Cleared +TEST_F(UsageTableHeaderTest, + LruUsageTableUpgrade_PartialSucceedWithUnknownStorageTypes) { + std::vector upgradable_usage_entry_info_list = + kUpgradableUsageEntryInfoList; + std::vector upgraded_usage_entry_info_list = + kUpgradedUsageEntryInfoList; + + // Set the wrong storage type. + upgradable_usage_entry_info_list[1].storage_type = kStorageTypeUnknown; + upgradable_usage_entry_info_list[2].storage_type = + static_cast(99); + + // Set the expected output. + upgraded_usage_entry_info_list[1] = CdmUsageEntryInfo{}; + upgraded_usage_entry_info_list[1].storage_type = kStorageTypeUnknown; + upgraded_usage_entry_info_list[2] = CdmUsageEntryInfo{}; + upgraded_usage_entry_info_list[2].storage_type = kStorageTypeUnknown; + + // Load table expectations. + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUpgradableUsageTableHeader), + SetArgPointee<1>(upgradable_usage_entry_info_list), + SetArgPointee<2>(/* lru_upgrade = */ true), + Return(true))); + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(kUpgradableUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + + // Expectations of the one successful license. + EXPECT_CALL(*device_files_, + RetrieveLicense(upgradable_usage_entry_info_list[0].key_set_id, + NotNull(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kUpgradableLicenseDataList[0]), Return(true))); + + EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); + EXPECT_EQ(upgraded_usage_entry_info_list, + usage_table_header_->usage_entry_info()); +} + +// Initial Test state: +// 1. Table info is load from device files and must be upgraded. +// Offline license 1 (entry 0), is missing its license signature. +// 2. During the LRU upgrade process, any license that cannot be +// validated are cleared, and marked to be deleted. +// +// Entry# Storage type License Issue Result info +// ====== ============ ============= =========== +// 0 Offline No signature Cleared +// 1 Streaming Wrong type Cleared +// 2 Offline Upgraded +TEST_F(UsageTableHeaderTest, + LruUsageTableUpgrade_PartialSucceedWithLicenseParseIssues) { + std::vector license_data_list = + kUpgradableLicenseDataList; + std::vector upgradable_license_info_list = + kUpgradableLicenseInfoList; + std::vector upgraded_usage_entry_info_list = + kUpgradedUsageEntryInfoList; + + // Modify entry 0 to be invalid. + license_data_list[0].license = kUnsignedUpgradableLicenseInfo1; + CdmUsageEntryInfo& unsigned_usage_entry_info = + upgraded_usage_entry_info_list[0]; + unsigned_usage_entry_info.storage_type = kStorageTypeUnknown; + unsigned_usage_entry_info.key_set_id.clear(); + unsigned_usage_entry_info.last_use_time = 0; + unsigned_usage_entry_info.offline_license_expiry_time = 0; + + // Modify entry 1 to be invalid. + upgradable_license_info_list[1] = kWrongTypedUpgradableLicenseInfo2; + CdmUsageEntryInfo& wrond_typed_usage_entry_info = + upgraded_usage_entry_info_list[1]; + wrond_typed_usage_entry_info.storage_type = kStorageTypeUnknown; + wrond_typed_usage_entry_info.key_set_id.clear(); + wrond_typed_usage_entry_info.last_use_time = 0; + wrond_typed_usage_entry_info.offline_license_expiry_time = 0; + + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUpgradableUsageTableHeader), + SetArgPointee<1>(kUpgradableUsageEntryInfoList), + SetArgPointee<2>(/* lru_upgrade = */ true), + Return(true))); + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(kUpgradableUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + + for (size_t i = 0; i < kUpgradableUsageEntryInfoList.size(); ++i) { + const CdmUsageEntryInfo& info = kUpgradableUsageEntryInfoList[i]; + if (info.storage_type == kStorageLicense) { + EXPECT_CALL(*device_files_, + RetrieveLicense(info.key_set_id, NotNull(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(license_data_list[i]), Return(true))); + } else { + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + info.usage_info_file_name, info.key_set_id, NotNull(), + NotNull(), NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<4>(upgradable_license_info_list[i]), + SetArgPointee<6>(static_cast(i)), + Return(true))); + } + } + + // For the entries which failed, there should be an attempt to delete the + // files associated with them. + // Offline license. + EXPECT_CALL(*device_files_, + DeleteLicense(kUpgradableUsageEntryInfoList[0].key_set_id)) + .WillOnce(Return(true)); + // Streaming license. + const std::vector corrupted_usage_info_key_set_ids = { + kUpgradableUsageEntryInfoList[1].key_set_id}; + EXPECT_CALL(*device_files_, + DeleteMultipleUsageInfoByKeySetIds( + kUpgradableUsageEntryInfoList[1].usage_info_file_name, + ContainerEq(corrupted_usage_info_key_set_ids))) + .WillOnce(Return(true)); + + EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); + EXPECT_EQ(upgraded_usage_entry_info_list, + usage_table_header_->usage_entry_info()); +} + +// Initial Test state: +// 1. Table info is load from device files; however, device files reports +// that the table has not been configured for upgrade. +// 2. None of the entries can have their license info loaded. +TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_AllFailure) { + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUpgradableUsageTableHeader), + SetArgPointee<1>(kUpgradableUsageEntryInfoList), + SetArgPointee<2>(/* lru_upgrade = */ true), + Return(true))); + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(kUpgradableUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + + for (size_t i = 0; i < kUpgradableUsageEntryInfoList.size(); ++i) { + const CdmUsageEntryInfo& info = kUpgradableUsageEntryInfoList[i]; + + if (info.storage_type == kStorageLicense) { + EXPECT_CALL(*device_files_, + RetrieveLicense(info.key_set_id, NotNull(), NotNull())) + .WillOnce(Return(false)); + } else { + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + info.usage_info_file_name, info.key_set_id, NotNull(), + NotNull(), NotNull(), NotNull(), NotNull())) + .WillOnce(Return(false)); + } + } + + // After failure, these will be called to clear files and create a new + // usage table header. + EXPECT_CALL(*device_files_, DeleteAllLicenses()); + EXPECT_CALL(*device_files_, DeleteAllUsageInfo()); + EXPECT_CALL(*device_files_, DeleteUsageTableInfo()); + EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(_, _)); + + EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); +} + +TEST_F(UsageTableHeaderTest, LruLastUsedTime_CreateLicenseEntry) { + // General setup. + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUpgradedUsageTableHeader), + SetArgPointee<1>(kUpgradedUsageEntryInfoList), + SetArgPointee<2>(/* lru_upgrade = */ false), + Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUpgradedUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); + + // Expected values. + const uint32_t expected_usage_entry_number = + kUpgradedUsageEntryInfoList.size(); + const CdmUsageEntryInfo expected_new_entry = { + kStorageLicense, "offline_key_set_4", "", kLruBaseTime, + kLruBaseTime + kDefaultExpireDuration}; + std::vector expected_usage_info_list = + kUpgradedUsageEntryInfoList; + expected_usage_info_list.push_back(expected_new_entry); + + // AddKey expectations + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(expected_usage_entry_number), + Return(NO_ERROR))); + MockClock mock_clock; + usage_table_header_->SetClock(&mock_clock); + EXPECT_CALL(mock_clock, GetCurrentTime()).WillOnce(Return(kLruBaseTime)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kUpgradedUsageTableHeader, _)); + + // The Call. + uint32_t usage_entry_number = 0; + EXPECT_EQ(NO_ERROR, usage_table_header_->AddEntry( + crypto_session_, true /* persistent_license */, + expected_new_entry.key_set_id, + expected_new_entry.usage_info_file_name, kEmptyString, + &usage_entry_number)); + + EXPECT_EQ(expected_usage_entry_number, usage_entry_number); + EXPECT_EQ(expected_usage_info_list, usage_table_header_->usage_entry_info()); +} + +TEST_F(UsageTableHeaderTest, LruLastUsedTime_CreateUsageInfoEntry) { + // General setup. + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUpgradedUsageTableHeader), + SetArgPointee<1>(kUpgradedUsageEntryInfoList), + SetArgPointee<2>(/* lru_upgrade = */ false), + Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUpgradedUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); + + // Expected values. + const uint32_t expected_usage_entry_number = + kUpgradedUsageEntryInfoList.size(); + const CdmUsageEntryInfo expected_new_entry = { + kStorageUsageInfo, "secure_stop_key_set_5", "streaming_license_file_4", + kLruBaseTime, 0 /* No set for streaming license. */ + }; + std::vector expected_usage_info_list = + kUpgradedUsageEntryInfoList; + expected_usage_info_list.push_back(expected_new_entry); + + // AddKey expectations + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(expected_usage_entry_number), + Return(NO_ERROR))); + MockClock mock_clock; + usage_table_header_->SetClock(&mock_clock); + EXPECT_CALL(mock_clock, GetCurrentTime()).WillOnce(Return(kLruBaseTime)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kUpgradedUsageTableHeader, _)); + + // The Call. + uint32_t usage_entry_number = 0; + EXPECT_EQ(NO_ERROR, usage_table_header_->AddEntry( + crypto_session_, false /* persistent_license */, + expected_new_entry.key_set_id, + expected_new_entry.usage_info_file_name, kEmptyString, + &usage_entry_number)); + + EXPECT_EQ(expected_usage_entry_number, usage_entry_number); + EXPECT_EQ(expected_usage_info_list, usage_table_header_->usage_entry_info()); +} + +TEST_F(UsageTableHeaderTest, LruLastUsedTime_UpdateEntry) { + // General setup. + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUpgradedUsageTableHeader), + SetArgPointee<1>(kUpgradedUsageEntryInfoList), + SetArgPointee<2>(/* lru_upgrade = */ false), + Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUpgradedUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); + + std::vector expected_usage_info_list = + kUpgradedUsageEntryInfoList; + MockClock mock_clock; + usage_table_header_->SetClock(&mock_clock); + const int64_t expected_update_time = + kLruBaseTime + 60 * 60; // Any value larger than the original works. + for (uint32_t usage_entry_number = 0; + usage_entry_number < expected_usage_info_list.size(); + ++usage_entry_number) { + // Update expected values. + expected_usage_info_list[usage_entry_number].last_use_time = + expected_update_time; + + // Update expectations + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(mock_clock, GetCurrentTime()) + .WillOnce(Return(expected_update_time)); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kUpgradedUsageTableHeader, _)); + + // The Call. + CdmUsageEntry usage_entry; + EXPECT_EQ(NO_ERROR, usage_table_header_->UpdateEntry( + usage_entry_number, crypto_session_, &usage_entry)); + EXPECT_EQ(expected_usage_info_list, + usage_table_header_->usage_entry_info()); + } +} + +TEST_F(UsageTableHeaderTest, LruLastUsedTime_LoadEntry) { + // General setup. + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUpgradedUsageTableHeader), + SetArgPointee<1>(kUpgradedUsageEntryInfoList), + SetArgPointee<2>(/* lru_upgrade = */ false), + Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUpgradedUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); + + std::vector expected_usage_info_list = + kUpgradedUsageEntryInfoList; + MockClock mock_clock; + usage_table_header_->SetClock(&mock_clock); + const int64_t expected_update_time = + kLruBaseTime + 60 * 60; // Any value larger than the original works. + for (uint32_t usage_entry_number = 0; + usage_entry_number < expected_usage_info_list.size(); + ++usage_entry_number) { + // Update expected values. + expected_usage_info_list[usage_entry_number].last_use_time = + expected_update_time; + + // Update expectations + EXPECT_CALL(*crypto_session_, LoadUsageEntry(usage_entry_number, _)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(mock_clock, GetCurrentTime()) + .WillOnce(Return(expected_update_time)); + + // The Call. + CdmUsageEntry usage_entry; + EXPECT_EQ(NO_ERROR, usage_table_header_->LoadEntry( + crypto_session_, kEmptyString, usage_entry_number)); + EXPECT_EQ(expected_usage_info_list, + usage_table_header_->usage_entry_info()); + } +} + +// LRU DetermineLicenseToRemove tests. + +// This testcase is intended to push the boundaries of valid inputs to +// the LRU algorithm. None of these are expected inputs under normal +// operations. +TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_InvalidInput) { + constexpr size_t kUnexpiredThreshold = 50; // Arbitrary + constexpr size_t kRemovalCount = 3; // Also artbirary + std::vector usage_entry_info_list; + std::vector removal_candidates; + // Empty list. + EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, kRemovalCount, + &removal_candidates)); + // Output is null. + usage_entry_info_list = kUpgradedUsageEntryInfoList; + EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, kRemovalCount, + nullptr)); + // Zero size requests. + EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, 0, + &removal_candidates)); + // Request more than is available. Not invalid, but an unlikely use + // case. + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, + /* removal_count = */ usage_entry_info_list.size() * 2, + &removal_candidates)); + // Only unexpired offline license, but threshold is not met. + usage_entry_info_list.resize(kRemovalCount); + for (size_t i = 0; i < kRemovalCount; ++i) { + usage_entry_info_list[i].storage_type = kStorageLicense; + usage_entry_info_list[i].last_use_time = kLruBaseTime; + usage_entry_info_list[i].offline_license_expiry_time = kLruBaseTime + 1; + } + EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, kRemovalCount, + &removal_candidates)); +} + +// Check that the major priority buckets are respected. +// Expects that unknown entries to be chosen above all others. +// Unexpired licenses should only be considered if the threshold +// is met. +TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) { + constexpr int64_t kOneDay = 24 * 60 * 60; + constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay; + std::vector usage_entry_info_list; + + // Unexpired offline license. + CdmUsageEntryInfo unexpired_entry_info; + unexpired_entry_info.storage_type = kStorageLicense; + unexpired_entry_info.last_use_time = kLruBaseTime; + unexpired_entry_info.offline_license_expiry_time = kLruBaseTime + 2 * kOneDay; + usage_entry_info_list.push_back(unexpired_entry_info); + constexpr uint32_t unexpired_entry_number = 0; + + // Expired offline license. + CdmUsageEntryInfo expired_entry_info; + expired_entry_info.storage_type = kStorageLicense; + expired_entry_info.last_use_time = kLruBaseTime; + expired_entry_info.offline_license_expiry_time = kLruBaseTime; + usage_entry_info_list.push_back(expired_entry_info); + constexpr uint32_t expired_entry_number = 1; + + // Streaming license. + CdmUsageEntryInfo streaming_entry_info; + streaming_entry_info.storage_type = kStorageUsageInfo; + streaming_entry_info.last_use_time = kLruBaseTime; + usage_entry_info_list.push_back(streaming_entry_info); + constexpr uint32_t streaming_entry_number = 2; + + // Unknown entry. + CdmUsageEntryInfo unknown_entry_info; + unknown_entry_info.storage_type = kStorageTypeUnknown; + // Should be chosen regardless of |last_use_time|. + unknown_entry_info.last_use_time = kCurrentTime; + usage_entry_info_list.push_back(unknown_entry_info); + constexpr uint32_t unknown_entry_number = 3; + + std::vector removal_candidates; + + // Expect the unknown entry. + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 3, + /* removal_count = */ 1, &removal_candidates)); + const std::vector unknown_entry_numbers = {unknown_entry_number}; + EXPECT_THAT(removal_candidates, ContainerEq(unknown_entry_numbers)); + + usage_entry_info_list.pop_back(); // Removing unknown. + + // Expect both expired offline and streaming license. + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 3, + /* removal_count = */ 3, &removal_candidates)); + const std::vector expired_and_streaming_entry_numbers = { + expired_entry_number, streaming_entry_number}; + EXPECT_THAT(removal_candidates, + ContainerEq(expired_and_streaming_entry_numbers)); + + // With threshold met, expect all three. + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 0, + /* removal_count = */ 3, &removal_candidates)); + const std::vector all_license_entry_numbers = { + expired_entry_number, streaming_entry_number, unexpired_entry_number}; + EXPECT_THAT(removal_candidates, ContainerEq(all_license_entry_numbers)); + + usage_entry_info_list.pop_back(); // Removing streaming. + usage_entry_info_list.pop_back(); // Removing expired offline. + + // Sanity check: while below threshold there will not be any possible + // candidates, expect failure. This is an unexpected case in normal + // operation. + EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 3, + /* removal_count = */ 1, &removal_candidates)); + + // Expect the unexpired license. + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 0, + /* removal_count = */ 1, &removal_candidates)); + const std::vector unexpired_entry_numbers = { + unexpired_entry_number}; + EXPECT_THAT(removal_candidates, ContainerEq(unexpired_entry_numbers)); +} + +// Testing algorithm with unexpired offline and streaming license. The +// sum of offline licenses is below the threshold for consideration. +// Only the streaming license should be considered for removal. +TEST_F(UsageTableHeaderTest, + DetermineLicenseToRemove_NoExpiredAndBelowThreshold) { + constexpr int64_t kOneDay = 24 * 60 * 60; + std::vector usage_entry_info_list = + kUpgradedUsageEntryInfoList; + const size_t offline_threshold = usage_entry_info_list.size() + 1; + + size_t usage_info_count = 0; + for (auto& usage_entry_info : usage_entry_info_list) { + if (usage_entry_info.storage_type == kStorageUsageInfo) { + // Make usage info entries less stale than offline. + usage_entry_info.last_use_time += 100; + ++usage_info_count; + } else { + // Make offline licenses unexpired. + usage_entry_info.offline_license_expiry_time = + kLruBaseTime + kOneDay * 30; + } + } + + // Must exist at least one streaming license for test to work. + ASSERT_LT(0ull, usage_info_count); + + std::vector removal_candidates; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kLruBaseTime, offline_threshold, + /* removal_count = */ 3, &removal_candidates)); + + EXPECT_EQ(usage_info_count, removal_candidates.size()); + // Ensure that the proposed removal candidates are streaming. + for (uint32_t usage_entry_number : removal_candidates) { + EXPECT_EQ(kStorageUsageInfo, + usage_entry_info_list[usage_entry_number].storage_type); + } +} + +// When the number of unexpired offline licenses are below the +// threshold, only streaming licenses and expired offline licenses +// should be considered. +// Providing only offline licenses, only the expired license should be +// considered for removal. +TEST_F(UsageTableHeaderTest, + DetermineLicenseToRemove_SomeExpiredAndBelowThreshold) { + constexpr int64_t kOneDay = 24 * 60 * 60; + constexpr size_t kSetSize = 10; + constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2; + // A threshold larger than the possible number of offline entries. + constexpr size_t kUnexpiredThreshold = kSetSize + 1; + + std::vector usage_entry_info_list; + usage_entry_info_list.resize(kSetSize); + + // Create a set of all offline licenses. + for (uint32_t i = 0; i < kSetSize; ++i) { + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; + usage_entry_info.storage_type = kStorageLicense; + usage_entry_info.key_set_id = "nothing_unusual"; + usage_entry_info.last_use_time = kLruBaseTime; + usage_entry_info.offline_license_expiry_time = + kCurrentTime + kDefaultExpireDuration; + } + + // Mark 3 as expired. + std::vector expired_license_numbers; + while (expired_license_numbers.size() < 3) { + const uint32_t i = CdmRandom::RandomInRange(kSetSize - 1); + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; + // Skip already expired ones + if (usage_entry_info.key_set_id != "nothing_unusual") continue; + // Make these less stale than the unexpired licenses. + usage_entry_info.last_use_time = kLruBaseTime + kOneDay; + usage_entry_info.offline_license_expiry_time = kCurrentTime - kOneDay; + usage_entry_info.key_set_id = "expired_offline"; + expired_license_numbers.push_back(i); + } + + std::vector removal_candidates; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, kUnexpiredThreshold, 3, + &removal_candidates)); + + // Sorting to ensure equality will work. + std::sort(removal_candidates.begin(), removal_candidates.end()); + std::sort(expired_license_numbers.begin(), expired_license_numbers.end()); + + EXPECT_EQ(expired_license_numbers, removal_candidates); +} + +// Test that if all of the license are unknown. For unknown licenses, +// the last use time has no effect on their selection. +TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_AllUnknown) { + constexpr int64_t kOneDay = 24 * 60 * 60; + constexpr size_t kSetSize = 10; + constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2; + constexpr size_t kRemovalCount = 3; + + std::vector usage_entry_info_list; + usage_entry_info_list.resize(kSetSize); + + // Create a set of all unknown licenses. + for (uint32_t i = 0; i < kSetSize; ++i) { + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; + usage_entry_info.storage_type = kStorageTypeUnknown; + usage_entry_info.key_set_id = "unknown_key_set_id"; + usage_entry_info.last_use_time = + CdmRandom::RandomInRange(kLruBaseTime, kCurrentTime); + } + + std::vector removal_candidates; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, 0, kRemovalCount, + &removal_candidates)); + // There should always be 3 (assuming kSetSize >= 3). + EXPECT_EQ(kRemovalCount, removal_candidates.size()); +} + +// Should there be two or more license which have the same +// |last_use_time| but only 1 of them are to be selected (due to the +// number being requested), then the algorithm should randomly select +// between the ones available. +TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_RandomnessOfCutoff) { + constexpr int64_t kOneDay = 24 * 60 * 60; + constexpr size_t kSetSize = 10; + constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2; + constexpr size_t kTrials = 25; + std::vector usage_entry_info_list; + + // All will be streaming licenses. + usage_entry_info_list.resize(kSetSize); + for (size_t i = 0; i < kSetSize; ++i) { + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; + usage_entry_info.storage_type = kStorageUsageInfo; + usage_entry_info.last_use_time = kLruBaseTime + kOneDay; + usage_entry_info.key_set_id = "nothing_unusual"; + } + + std::vector expected_removals; + // Select two to be the most stale. + while (expected_removals.size() < 2) { + const uint32_t i = CdmRandom::RandomInRange(kSetSize - 1); + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; + if (usage_entry_info.key_set_id != "nothing_unusual") continue; + usage_entry_info_list[i].last_use_time = kLruBaseTime; + usage_entry_info.key_set_id = "most_stale"; + expected_removals.push_back(i); + } + + // Select another two to be slightly less stale. + std::vector random_removals; + while (random_removals.size() < 2) { + const uint32_t i = CdmRandom::RandomInRange(kSetSize - 1); + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; + if (usage_entry_info.key_set_id != "nothing_unusual") continue; + usage_entry_info_list[i].last_use_time = kLruBaseTime + 1; + usage_entry_info.key_set_id = "somewhat_stale"; + random_removals.push_back(i); + } + + // Create two sets for each possible outcome (one for each random + // removal). + std::vector possible_removal_1 = expected_removals; + possible_removal_1.push_back(random_removals[0]); + std::sort(possible_removal_1.begin(), possible_removal_1.end()); + std::vector possible_removal_2 = expected_removals; + possible_removal_2.push_back(random_removals[1]); + std::sort(possible_removal_2.begin(), possible_removal_2.end()); + std::vector possible_removal_3 = expected_removals; + + // Flags to check that both outcomes have occurred. + bool occurrence_1 = false; + bool occurrence_2 = false; + + // Each set should be equally likely. Possible false-negative every + // 2^kTrials time. + for (size_t i = 0; i < kTrials; ++i) { + // Remove 3 out of the 4 possible candidates. + std::vector removal_candidates; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, 0, 3, &removal_candidates)); + + std::sort(removal_candidates.begin(), removal_candidates.end()); + if (removal_candidates == possible_removal_1) { + occurrence_1 = true; + } else if (removal_candidates == possible_removal_2) { + occurrence_2 = true; + } else { + EXPECT_TRUE(false) << "Unexpected removal set"; + } + } + + EXPECT_TRUE(occurrence_1); + EXPECT_TRUE(occurrence_2); +} + +// This test primarily tests the robustness of the algorithm for a full +// set of entries (200). Creates 3 stale streaming license and 3 +// offline licenses which are more stale than the streaming. +// +// First, with the stale offline licenses unexpired, checks that the +// streaming are always selected, so long as |unexpired_threshold| +// is not met. +// +// Second, with the stale offline license marked as expired, checks that +// offline licenses are selected over the streaming. +TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_LargeMixedSet) { + constexpr int64_t kOneDay = 24 * 60 * 60; + constexpr size_t kLargeSetSize = 200; + constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2; + std::vector usage_entry_info_list; + usage_entry_info_list.resize(kLargeSetSize); + + // Create a set of usage entries. + for (uint32_t i = 0; i < kLargeSetSize; ++i) { + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; + usage_entry_info.key_set_id = "nothing_unusual"; + usage_entry_info.last_use_time = kLruBaseTime + kOneDay; + if ((i % 4) == 0) { + // Roughly 25% are offline, the rest are streaming. + usage_entry_info.storage_type = kStorageLicense; + usage_entry_info.offline_license_expiry_time = + kCurrentTime + kDefaultExpireDuration; + } else { + usage_entry_info.storage_type = kStorageUsageInfo; + } + } + + // Select 3 streaming license to be more stale than the rest. + std::vector modified_usage_info_numbers; + while (modified_usage_info_numbers.size() < 3) { + const uint32_t i = CdmRandom::RandomInRange(kLargeSetSize - 1); + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; + // Skip streaming license that have already been modified and offline + // licenses. + if (usage_entry_info.storage_type != kStorageUsageInfo || + usage_entry_info.key_set_id != "nothing_unusual") + continue; + usage_entry_info.last_use_time = + kLruBaseTime + 10 + modified_usage_info_numbers.size(); + usage_entry_info.key_set_id = "stale_streaming"; + modified_usage_info_numbers.push_back(i); + } + std::sort(modified_usage_info_numbers.begin(), + modified_usage_info_numbers.end()); + + // Select 3 offline license to be even more stale, but unexpired. + std::vector modified_offline_license_numbers; + while (modified_offline_license_numbers.size() < 3) { + const uint32_t i = CdmRandom::RandomInRange(kLargeSetSize - 1); + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; + // Skip offline license that have already been modified and streaming + // licenses. + if (usage_entry_info.storage_type != kStorageLicense || + usage_entry_info.key_set_id != "nothing_unusual") + continue; + usage_entry_info.last_use_time = kLruBaseTime; + usage_entry_info.key_set_id = "stale_offline"; + modified_offline_license_numbers.push_back(i); + } + std::sort(modified_offline_license_numbers.begin(), + modified_offline_license_numbers.end()); + + // Test using only streaming and expired offline licenses + // (which there are none). + std::vector removal_candidates; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ kLargeSetSize, 3, &removal_candidates)); + EXPECT_THAT(removal_candidates, + UnorderedElementsAreArray(modified_usage_info_numbers)); + + // Test where the equality threshold is met, now the 3 unexpired + // licenses should be selected. + removal_candidates.clear(); + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 0, 3, &removal_candidates)); + EXPECT_THAT(removal_candidates, + UnorderedElementsAreArray(modified_offline_license_numbers)); + + // Make the 3 offline licenses expired. + for (uint32_t i : modified_offline_license_numbers) { + CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; + usage_entry_info.offline_license_expiry_time = kLruBaseTime; + } + + // Test again, expecting that the expired license should be considered. + removal_candidates.clear(); + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ kLargeSetSize, 3, &removal_candidates)); + EXPECT_THAT(removal_candidates, + UnorderedElementsAreArray(modified_offline_license_numbers)); +} + } // namespace wvcdm diff --git a/libwvdrmengine/include/WVErrors.h b/libwvdrmengine/include/WVErrors.h index cbb47713..352890c8 100644 --- a/libwvdrmengine/include/WVErrors.h +++ b/libwvdrmengine/include/WVErrors.h @@ -281,10 +281,11 @@ enum { kInvalidLicenseType2 = ERROR_DRM_VENDOR_MIN + 296, kSignatureNotFound2 = ERROR_DRM_VENDOR_MIN + 297, kSessionKeysNotFound2 = ERROR_DRM_VENDOR_MIN + 298, + kUsageInvalidParameters2 = ERROR_DRM_VENDOR_MIN + 299, // This should always follow the last error code. // The offset value should be updated each time a new error code is added. - kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 298, + kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 299, // Used by crypto test mode kErrorTestMode = ERROR_DRM_VENDOR_MAX, diff --git a/libwvdrmengine/include/mapErrors-inl.h b/libwvdrmengine/include/mapErrors-inl.h index 3e9e0ebb..c37b39a9 100644 --- a/libwvdrmengine/include/mapErrors-inl.h +++ b/libwvdrmengine/include/mapErrors-inl.h @@ -531,6 +531,8 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) { return kUsageInvalidNewEntry; case wvcdm::USAGE_INVALID_PARAMETERS_1: return kUsageInvalidParameters1; + case wvcdm::USAGE_INVALID_PARAMETERS_2: + return kUsageInvalidParameters2; case wvcdm::USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE: return kUsageStoreEntryRetrieveInvalidStorageType; case wvcdm::USAGE_STORE_ENTRY_RETRIEVE_LICENSE_FAILED: diff --git a/libwvdrmengine/include_hidl/mapErrors-inl.h b/libwvdrmengine/include_hidl/mapErrors-inl.h index 2898dee1..76302033 100644 --- a/libwvdrmengine/include_hidl/mapErrors-inl.h +++ b/libwvdrmengine/include_hidl/mapErrors-inl.h @@ -349,7 +349,7 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::REWRAP_DEVICE_RSA_KEY_ERROR: case wvcdm::REWRAP_DEVICE_RSA_KEY_30_ERROR: case wvcdm::INVALID_SRM_LIST: - + case wvcdm::USAGE_INVALID_PARAMETERS_2: ALOGW("Returns UNKNOWN error for legacy status: %d", res); return Status::ERROR_DRM_UNKNOWN;