// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine Master // License Agreement. #ifndef WVCDM_CORE_USAGE_TABLE_HEADER_H_ #define WVCDM_CORE_USAGE_TABLE_HEADER_H_ #include #include #include #include #include "clock.h" #include "crypto_session.h" #include "device_files.h" #include "disallow_copy_and_assign.h" #include "file_store.h" #include "metrics_collections.h" #include "wv_cdm_types.h" #if defined(UNIT_TEST) # include #endif namespace wvcdm { // Offline licenses/secure stops may be securely tracked using usage // tables (OEMCrypto v9-12) or usage table headers+usage entries // (OEMCrypto v13+). This class assists with the latter, synchronizing // access to usage table header and associated data-structures and controlling // when they are read in or written out to non-secure persistent storage. // // Each OEMCrypto (for each security level) will maintain its own usage table // header. Each license will have an associated usage entry that is also // stored in persistent memory and is noted in the usage table header. // Usage entry information will be verified when licenses are loaded. // // OEMCrypto for each security level have their own usage table // headers. They are loaded on initialization and written out periodically. // The lifecycle of this class is tied to when OEMCrypto is // initialized/terminated. // // Sessions and licenses are however handled by CdmSession and so most // calls to maniplate the usage table header related to usage entries // are by CdmSession. // // Upgrades from a fixed size usage table (supported by previous // versions of the OEMCrypto API v9-12) are handled by this class. // |usage_entry| and |usage_entry_number|s need to be saved in the license // and usage info records by the caller. class UsageTableHeader { public: UsageTableHeader(); virtual ~UsageTableHeader() {} // |crypto_session| is used to create or load a usage master table and // not cached beyound this call. bool Init(CdmSecurityLevel security_level, CryptoSession* crypto_session); // |persistent_license| false indicates usage info record virtual CdmResponseType AddEntry(CryptoSession* crypto_session, 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(uint32_t usage_entry_number, CryptoSession* crypto_session, CdmUsageEntry* usage_entry); // The licenses or usage info records specified by |usage_entry_number| // should not be in use by any open CryptoSession objects when calls // to DeleteEntry and MoveEntry are made. virtual CdmResponseType DeleteEntry(uint32_t usage_entry_number, DeviceFiles* handle, metrics::CryptoMetrics* metrics); // Test only method. This method emulates the behavior of DeleteEntry // without actually invoking OEMCrypto (through CryptoSession) // or storage (through DeviceFiles). It modifies internal data structures // when DeleteEntry is mocked. This allows one to test methods that are // dependent on DeleteEntry without having to set expectations // for the objects that DeleteEntry depends on. void DeleteEntryForTest(uint32_t usage_entry_number); size_t size() { return usage_entry_info_.size(); } size_t potential_table_capacity() const { return potential_table_capacity_; } 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, uint32_t to /* usage entry number */, DeviceFiles* handle, metrics::CryptoMetrics* metrics); CdmResponseType GetEntry(uint32_t usage_entry_number, DeviceFiles* handle, CdmUsageEntry* usage_entry); CdmResponseType StoreEntry(uint32_t usage_entry_number, DeviceFiles* handle, const CdmUsageEntry& usage_entry); CdmResponseType Shrink(metrics::CryptoMetrics* metrics, uint32_t number_of_usage_entries_to_delete); virtual bool is_inited() { return is_inited_; } // 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. std::unique_ptr file_handle_; std::unique_ptr file_system_; CdmSecurityLevel security_level_; SecurityLevel requested_security_level_; CdmUsageTableHeader usage_table_header_; std::vector usage_entry_info_; // Lock to ensure that a single object is created for each security level // and data member to represent whether an object has been correctly // initialized. bool is_inited_; // Synchonizes access to the Usage Table Header and bookkeeping // data-structures std::mutex usage_table_header_lock_; 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_; // The maximum number of entries that the underlying OEMCrypto // implementation can support. Some implementations might not // support reporting the table capacity, if so, then this value is // assumed to be |kMinimumUsageTableEntriesSupported|. size_t potential_table_capacity_ = 0u; #if defined(UNIT_TEST) // Test related declarations friend class UsageTableHeaderTest; FRIEND_TEST(UsageTableHeaderTest, Shrink_NoneOfTable); FRIEND_TEST(UsageTableHeaderTest, Shrink_PartOfTable); FRIEND_TEST(UsageTableHeaderTest, Shrink_AllOfTable); FRIEND_TEST(UsageTableHeaderTest, Shrink_MoreThanTable); #endif // These setters are for testing only. Takes ownership of the pointers. void SetDeviceFiles(DeviceFiles* device_files) { file_handle_.reset(device_files); } void SetCryptoSession(CryptoSession* crypto_session) { test_crypto_session_.reset(crypto_session); } // Test related data members std::unique_ptr test_crypto_session_; CORE_DISALLOW_COPY_AND_ASSIGN(UsageTableHeader); }; } // namespace wvcdm #endif // WVCDM_CORE_USAGE_TABLE_HEADER_H_