// 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 InvalidateEntry and MoveEntry are made. // If |defrag_table| is true, the table will be defragmented after // the entry has been invalidated. virtual CdmResponseType InvalidateEntry(uint32_t usage_entry_number, bool defrag_table, DeviceFiles* device_files, metrics::CryptoMetrics* metrics); // Test only method. This method emulates the behavior of InvalidateEntry // without actually invoking OEMCrypto (through CryptoSession) // or storage (through DeviceFiles). It modifies internal data structures // when InvalidateEntry is mocked. This allows one to test methods that are // dependent on InvalidateEntry without having to set expectations // for the objects that InvalidateEntry depends on. void InvalidateEntryForTest(uint32_t usage_entry_number); size_t size() { return usage_entry_info_.size(); } size_t potential_table_capacity() const { return potential_table_capacity_; } bool HasUnlimitedTableCapacity() const { return potential_table_capacity_ == 0; } // Returns the number of entries currently tracked by the CDM that // are related to usage info (streaming licenses). size_t UsageInfoCount() const; // Returns the number of entries currently tracked by the CDM that // are related to offline licenses. size_t OfflineEntryCount() const; 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, uint32_t* entry_to_remove) { return DetermineLicenseToRemove(usage_entry_info_list, current_time, unexpired_threshold, entry_to_remove); } private: CdmResponseType MoveEntry(uint32_t from /* usage entry number */, const CdmUsageEntry& from_usage_entry, uint32_t to /* usage entry number */, DeviceFiles* device_files, metrics::CryptoMetrics* metrics); CdmResponseType GetEntry(uint32_t usage_entry_number, DeviceFiles* device_files, CdmUsageEntry* usage_entry); CdmResponseType StoreEntry(uint32_t usage_entry_number, DeviceFiles* device_files, const CdmUsageEntry& usage_entry); // Stores the usage table and it's info. This will increment // |store_table_counter_| if successful. bool StoreTable(DeviceFiles* device_files); CdmResponseType Shrink(metrics::CryptoMetrics* metrics, uint32_t number_of_usage_entries_to_delete); // Must lock table before calling. CdmResponseType DefragTable(DeviceFiles* device_files, metrics::CryptoMetrics* metrics); // This will use the LRU algorithm to decide which entry is to be // evicted. CdmResponseType ReleaseOldestEntry(metrics::CryptoMetrics* metrics); 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 GetRemovalCandidate(uint32_t* entry_to_remove); int64_t GetCurrentTime() { return clock_ref_->GetCurrentTime(); } // Sets LRU related metrics based on the provided |staleness| (in // seconds) and |storage_type| of the entry removed. void RecordLruEventMetrics(metrics::CryptoMetrics* metrics, uint64_t staleness, CdmUsageEntryStorageType storage_type); // Uses an LRU-base algorithm to determine which license 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|. // // 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. // [out] entry_to_remove: Usage entry index of the entry selected // to be removed. Assume to be unaffected if the // function returns |false|. // // Returns: // |true| if an entry has been determined to be removed. // Otherwise returns |false|. static bool DetermineLicenseToRemove( const std::vector& usage_entry_info_list, int64_t current_time, size_t unexpired_threshold, uint32_t* entry_to_remove); // 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 device_files_; 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 mutable 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; // Counts the number of successful calls to |StoreTable()|. Used // to reduce the number of calls to device files for certain // table operations. uint32_t store_table_counter_ = 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) { device_files_.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_