Files
ce_cdm/core/include/cdm_usage_table.h
2024-09-05 07:02:36 +00:00

367 lines
15 KiB
C++

// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#ifndef WVCDM_CORE_CDM_USAGE_TABLE_H_
#define WVCDM_CORE_CDM_USAGE_TABLE_H_
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "clock.h"
#include "crypto_session.h"
#include "device_files.h"
#include "file_store.h"
#include "metrics_collections.h"
#include "wv_cdm_types.h"
#include "wv_class_utils.h"
#if defined(UNIT_TEST)
# include <gtest/gtest_prod.h>
#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 manipulate 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.
// |entry| and |entry_index|s need to be saved in the license
// and usage info records by the caller.
class CdmUsageTable {
public:
CdmUsageTable();
WVCDM_DISALLOW_COPY_AND_MOVE(CdmUsageTable);
virtual ~CdmUsageTable() {}
// |crypto_session| is used to create or load a usage master table
// and not cached beyound this call.
// First attempts to restore the usage table from the device files.
// If restoring fails, then a new usage table is created.
// Note: No OEMCrypto session for the same security level should be
// opened before calling.
// Threading: Method requires care of caller for exclusive access
// (ie. test setup or CryptoSession).
bool Init(CdmSecurityLevel security_level, CryptoSession* crypto_session);
// Adds a new entry to the OEMCrypto usage table header, and associates
// the entry with the provided |crypto_session|. The index of the new
// usage entry will be returned by |entry_index|.
//
// Type of entry depends on the value of |persistent_license|:
// false - usage info / secure stop record
// true - offline license
//
// Threading: Method takes exclusive use of |lock_|.
virtual CdmResponseType AddEntry(CryptoSession* crypto_session,
bool persistent_license,
const CdmKeySetId& key_set_id,
const std::string& usage_info_filename,
const CdmKeyResponse& license_message,
UsageEntryIndex* entry_index);
// Threading: Method takes exclusive use of |lock_|.
virtual CdmResponseType LoadEntry(CryptoSession* crypto_session,
const UsageEntry& entry,
UsageEntryIndex entry_index);
// Threading: Method takes exclusive use of |lock_|.
virtual CdmResponseType UpdateEntry(UsageEntryIndex entry_index,
CryptoSession* crypto_session,
UsageEntry* entry);
// The licenses or usage info records specified by |entry_index|
// 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.
//
// Threading: Method takes exclusive use of |lock_|.
virtual CdmResponseType InvalidateEntry(UsageEntryIndex entry_index,
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.
//
// Threading: Method requires care of caller for exclusive access.
void InvalidateEntryForTest(UsageEntryIndex entry_index);
// == Table information methods ==
// Threading: None of the following are thread safe. Intended for
// testing or internal use.
size_t size() { return entry_info_list_.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<CdmUsageEntryInfo>& entry_info_list() const {
return entry_info_list_;
}
// Set the reference clock used for the method GetCurrentTime().
void SetClock(wvutil::Clock* clock) {
if (clock != nullptr)
clock_ref_ = clock;
else
clock_ref_ = &clock_;
}
static bool DetermineLicenseToRemoveForTesting(
const std::vector<CdmUsageEntryInfo>& entry_info_list,
int64_t current_time, size_t unexpired_threshold,
UsageEntryIndex* entry_to_remove) {
return DetermineLicenseToRemove(entry_info_list, current_time,
unexpired_threshold, entry_to_remove);
}
private:
// == Initialization methods ==
// These should only be called during table initialization.
// All parameters are assumed valid.
// Creates a new, empty usage table. Any existing usage table files
// will be deleted.
// Threading: Method takes exclusive use of |lock_|
// when required.
bool CreateNewTable(CryptoSession* crypto_session);
// Attempts to restore the usage table from persistent storage, and
// loads the usage table header into OEMCrypto.
// Note: No other OEMCrypto session should be opened before calling.
// Threading: Method takes exclusive use of |lock_|
// when required.
bool RestoreTable(CryptoSession* crypto_session);
// Performs a check that there are no open OEMCrypto sessions for
// the current security level of the usage table.
// Threading: No special threading requirements.
bool OpenSessionCheck(CryptoSession* crypto_session);
// Performs a check that the OEMCrypto table can support at least
// one more entry if the table is at or near the reported capacity.
// If this check fails, a new usage table SHOULD be created.
// Threading: Method requires caller to take exclusive use of
// |lock_|.
bool CapacityCheck(CryptoSession* crypto_session);
// Attempts to determine the capacity of the OEMCrypto usage table.
// Sets the result to |potential_table_capacity_|.
// Threading: Method requires caller to take exclusive use of
// |lock_|.
bool DetermineTableCapacity(CryptoSession* crypto_session);
// == Table operation methods ==
// Threading: All of the following methods require caller to take
// exclusive use of |lock_|.
// Creates a new entry for the provided crypto session. If the
// entry is created successfully in OEMCrypto, then a new entry
// info is added to the table's vector of entry info.
CdmResponseType CreateEntry(CryptoSession* crypto_session,
UsageEntryIndex* entry_index);
// Attempts to relocate a newly created usage entry associated with
// the provided |crypto_session| to the lowest unoccupied position in
// the table.
// |entry_index| is treated as both an input and output.
// Returns NO_ERROR so long as no internal operation fails,
// regardless of whether the entry was moved or not.
CdmResponseType RelocateNewEntry(CryptoSession* crypto_session,
UsageEntryIndex* entry_index);
// Checks if the specified |entry_index| is known to be
// unoccupied (released).
bool IsEntryUnoccupied(UsageEntryIndex entry_index) const;
// SetOfflineEntryInfo() and SetUsageInfoEntryInfo() populate the
// entry meta-data with the required information based on the type
// of entry.
void SetOfflineEntryInfo(UsageEntryIndex entry_index,
const std::string& key_set_id,
const CdmKeyResponse& license_message);
void SetUsageInfoEntryInfo(UsageEntryIndex entry_index,
const std::string& key_set_id,
const std::string& usage_info_file_name);
// Shrinks the table, removing all trailing unoccupied entries.
// |entry_info_list_| will be resized appropriately.
// Caller must store the table after a successful call.
CdmResponseType RefitTable(CryptoSession* crypto_session);
virtual CdmResponseType InvalidateEntryInternal(
UsageEntryIndex entry_index, bool defrag_table, DeviceFiles* device_files,
metrics::CryptoMetrics* metrics);
CdmResponseType MoveEntry(UsageEntryIndex from, const UsageEntry& from_entry,
UsageEntryIndex to, DeviceFiles* device_files,
metrics::CryptoMetrics* metrics);
CdmResponseType GetEntry(UsageEntryIndex entry_index,
DeviceFiles* device_files, UsageEntry* entry);
CdmResponseType StoreEntry(UsageEntryIndex entry_index,
DeviceFiles* device_files,
const UsageEntry& entry);
// Stores the usage table and it's info. This will increment
// |store_table_counter_| if successful.
bool StoreTable();
CdmResponseType Shrink(metrics::CryptoMetrics* metrics,
uint32_t number_of_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);
// 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] 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<CdmUsageEntryInfo>& entry_info_list,
int64_t current_time, size_t unexpired_threshold,
UsageEntryIndex* entry_to_remove);
// This handle and file system is only to be used when accessing
// header. Usage entries should use the file system provided
// by CdmSession.
std::unique_ptr<DeviceFiles> device_files_;
std::unique_ptr<wvutil::FileSystem> file_system_;
CdmSecurityLevel security_level_ = kSecurityLevelUninitialized;
RequestedSecurityLevel requested_security_level_ = kLevelDefault;
UsageTableHeader header_;
std::vector<CdmUsageEntryInfo> entry_info_list_;
// Table is sync with persistent storage and can be used by the CDM
// to interact with OEMCrypto.
bool is_initialized_ = false;
// Synchonizes access to the Usage Table Header and bookkeeping
// data-structures
mutable std::mutex 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.
wvutil::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 overridden for testing purpose.
wvutil::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 CdmUsageTableTest;
FRIEND_TEST(CdmUsageTableTest, Shrink_NoneOfTable);
FRIEND_TEST(CdmUsageTableTest, Shrink_PartOfTable);
FRIEND_TEST(CdmUsageTableTest, Shrink_AllOfTable);
FRIEND_TEST(CdmUsageTableTest, 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<CryptoSession> test_crypto_session_;
}; // class CdmUsageTable
} // namespace wvcdm
#endif // WVCDM_CORE_CDM_USAGE_TABLE_H_