[ Merge of http://go/wvgerrit/171271 ] There is a need to maintain a short history of metrics from CDMs which have been deleted. This CL adds this ability to the Android version of the WV CDM. The history cannot yet be maintained for long, as the WV CDM instance is destroyed if unused. Further changes are required to the plugin to maintain the history beyond the life-cycle of the CDM instance, and to properly format its output. Bug: 239462891 Bug: 270166158 Test: adb shell dumpsys android.hardware.drm.IDrmFactory/widevine -m Test: atest GtsMediaTestCases Change-Id: I81c0996602722a9795fc3951030d20bb39b5816b
796 lines
31 KiB
C++
796 lines
31 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.
|
|
|
|
#include "wv_content_decryption_module.h"
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
|
|
#include "cdm_client_property_set.h"
|
|
#include "cdm_engine.h"
|
|
#include "cdm_engine_factory.h"
|
|
#include "initialization_data.h"
|
|
#include "license.h"
|
|
#include "log.h"
|
|
#include "properties.h"
|
|
#include "service_certificate.h"
|
|
#include "string_conversions.h"
|
|
#include "wv_cdm_constants.h"
|
|
#include "wv_cdm_event_listener.h"
|
|
#include "wv_metrics.pb.h"
|
|
|
|
namespace wvcdm {
|
|
|
|
using wvutil::FileSystem;
|
|
using wvutil::LoggingUidSetter;
|
|
|
|
namespace {
|
|
const int kCdmTimerDurationSeconds = 1;
|
|
|
|
// In the interest of limiting memory usage, only a limited number of
|
|
// the most recent metrics snapshots whould be stored.
|
|
constexpr size_t kMaxSavedMetricsSnapshotCount = 50;
|
|
|
|
// Value returned by time() if the operation fails.
|
|
constexpr time_t kTimeError = static_cast<time_t>(-1);
|
|
|
|
// Removes certain field from the provided CdmIdentifier as to make
|
|
// it better usable for tracking the same engine across different
|
|
// calls into the DRM plugin service.
|
|
CdmIdentifier ScrubIdentifierForMetrics(const CdmIdentifier& identifier) {
|
|
return CdmIdentifier{identifier.spoid, identifier.origin,
|
|
identifier.app_package_name, identifier.unique_id,
|
|
/* user_id */ wvutil::UNKNOWN_UID};
|
|
}
|
|
} // namespace
|
|
|
|
WvMetricsSnapshot::WvMetricsSnapshot(const CdmIdentifier& identifier,
|
|
drm_metrics::WvCdmMetrics&& metrics,
|
|
time_t ts)
|
|
: cdm_id_(ScrubIdentifierForMetrics(identifier)),
|
|
metrics_(std::move(metrics)),
|
|
timestamp_(ts) {}
|
|
|
|
WvMetricsSnapshot WvMetricsSnapshot::MakeSnapshot(
|
|
const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics&& metrics) {
|
|
const time_t now = time(nullptr);
|
|
if (now == kTimeError) {
|
|
LOGW("Failed to get current time");
|
|
return WvMetricsSnapshot(identifier, std::move(metrics), 0);
|
|
}
|
|
return WvMetricsSnapshot(identifier, std::move(metrics), now);
|
|
}
|
|
|
|
std::string WvMetricsSnapshot::GetFormattedTimestamp() const {
|
|
if (timestamp_ == 0) return "<unset>";
|
|
struct tm time_info = {};
|
|
if (localtime_r(×tamp_, &time_info) == nullptr) {
|
|
LOGE("Failed to convert to local time");
|
|
return "<conversion error(" + std::to_string(timestamp_) + ")>";
|
|
}
|
|
char timestamp_buffer[64]; // asctime_r() requires at least 29 bytes.
|
|
memset(timestamp_buffer, 0, sizeof(timestamp_buffer));
|
|
if (asctime_r(&time_info, timestamp_buffer) == nullptr) {
|
|
LOGE("Failed to format localtime to ASC time");
|
|
return "<format error(" + std::to_string(timestamp_) + ")>";
|
|
}
|
|
std::string result(timestamp_buffer);
|
|
// The Linux manual illustrates asctime_r() as returning a value
|
|
// with a new line character at the end. However, it does not
|
|
// specify this in the description. This is to account for different
|
|
// behavior on different systems.
|
|
if (!result.empty() && result.back() == '\n') {
|
|
result.pop_back();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::mutex WvContentDecryptionModule::session_sharing_id_generation_lock_;
|
|
|
|
WvContentDecryptionModule::WvContentDecryptionModule() {}
|
|
|
|
WvContentDecryptionModule::~WvContentDecryptionModule() {
|
|
CryptoSession::DisableDelayedTermination();
|
|
CloseAllCdms();
|
|
CryptoSession::TryTerminate();
|
|
DisableTimerAndWaitForExit();
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsSupported(const std::string& init_data_type) {
|
|
return InitializationData(init_data_type).is_supported();
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsCenc(const std::string& init_data_type) {
|
|
return InitializationData(init_data_type).is_cenc();
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsWebm(const std::string& init_data_type) {
|
|
return InitializationData(init_data_type).is_webm();
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsHls(const std::string& init_data_type) {
|
|
return InitializationData(init_data_type).is_hls();
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsAudio(const std::string& init_data_type) {
|
|
return InitializationData(init_data_type).is_audio();
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::OpenSession(
|
|
const CdmKeySystem& key_system, CdmClientPropertySet* property_set,
|
|
const CdmIdentifier& identifier, WvCdmEventListener* event_listener,
|
|
CdmSessionId* session_id) {
|
|
if (property_set && property_set->is_session_sharing_enabled()) {
|
|
std::unique_lock<std::mutex> auto_lock(session_sharing_id_generation_lock_);
|
|
if (property_set->session_sharing_id() == 0)
|
|
property_set->set_session_sharing_id(GenerateSessionSharingId());
|
|
}
|
|
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
CdmResponseType sts = cdm_engine->OpenSession(key_system, property_set,
|
|
event_listener, session_id);
|
|
if (sts == NO_ERROR) {
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
cdm_by_session_id_[*session_id] = cdm_engine;
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::CloseSession(
|
|
const CdmSessionId& session_id) {
|
|
LOGV("Closing session ID: %s", session_id.c_str());
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
// TODO(rfrias): Avoid reusing the error codes from CdmEngine.
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_1);
|
|
const CdmResponseType sts = cdm_engine->CloseSession(session_id);
|
|
if (sts == NO_ERROR) {
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
cdm_by_session_id_.erase(session_id);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsOpenSession(const CdmSessionId& session_id) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
return cdm_engine && cdm_engine->IsOpenSession(session_id);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GenerateKeyRequest(
|
|
const CdmSessionId& session_id, const CdmKeySetId& key_set_id,
|
|
const std::string& init_data_type, const CdmInitData& init_data,
|
|
const CdmLicenseType license_type, CdmAppParameterMap& app_parameters,
|
|
CdmClientPropertySet* property_set, const CdmIdentifier& identifier,
|
|
CdmKeyRequest* key_request) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
CdmResponseType sts;
|
|
if (license_type == kLicenseTypeRelease) {
|
|
sts = cdm_engine->OpenKeySetSession(key_set_id, property_set, nullptr);
|
|
if (sts != NO_ERROR) return sts;
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
cdm_by_session_id_[key_set_id] = cdm_engine;
|
|
}
|
|
|
|
const wvcdm::RequestedSecurityLevel requested_security_level =
|
|
property_set && property_set->security_level().compare(
|
|
wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3) == 0
|
|
? wvcdm::kLevel3
|
|
: wvcdm::kLevelDefault;
|
|
|
|
std::string oec_version;
|
|
sts = cdm_engine->QueryStatus(requested_security_level,
|
|
QUERY_KEY_OEMCRYPTO_API_VERSION, &oec_version);
|
|
if (sts != NO_ERROR) {
|
|
return sts;
|
|
}
|
|
InitializationData initialization_data(init_data_type, init_data,
|
|
oec_version);
|
|
|
|
sts = cdm_engine->GenerateKeyRequest(session_id, key_set_id,
|
|
initialization_data, license_type,
|
|
app_parameters, key_request);
|
|
if (license_type == kLicenseTypeRelease && sts != KEY_MESSAGE) {
|
|
cdm_engine->CloseKeySetSession(key_set_id);
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
cdm_by_session_id_.erase(key_set_id);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::AddKey(
|
|
const CdmSessionId& session_id, const CdmKeyResponse& key_data,
|
|
CdmKeySetId* key_set_id) {
|
|
CdmEngine* cdm_engine = session_id.empty() ? GetCdmForSessionId(*key_set_id)
|
|
: GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_3);
|
|
// Save key_set_id, as CDM will return an empty key_set_id on release
|
|
CdmKeySetId release_key_set_id;
|
|
if (session_id.empty() && key_set_id != nullptr) {
|
|
release_key_set_id = *key_set_id;
|
|
}
|
|
CdmResponseType sts;
|
|
CdmLicenseType license_type;
|
|
sts = cdm_engine->AddKey(session_id, key_data, &license_type, key_set_id);
|
|
// Empty session id indicates license type release.
|
|
if (sts == KEY_ADDED && session_id.empty()) {
|
|
cdm_engine->CloseKeySetSession(release_key_set_id);
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
cdm_by_session_id_.erase(release_key_set_id);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::RestoreKey(
|
|
const CdmSessionId& session_id, const CdmKeySetId& key_set_id) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_4);
|
|
CdmResponseType sts;
|
|
sts = cdm_engine->RestoreKey(session_id, key_set_id);
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::RemoveKeys(
|
|
const CdmSessionId& session_id) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_5);
|
|
CdmResponseType sts = cdm_engine->RemoveKeys(session_id);
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::QueryStatus(
|
|
RequestedSecurityLevel security_level, const std::string& key,
|
|
std::string* value) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(kDefaultCdmIdentifier);
|
|
return cdm_engine->QueryStatus(security_level, key, value);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::QuerySessionStatus(
|
|
const CdmSessionId& session_id, CdmQueryMap* key_info) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_8);
|
|
return cdm_engine->QuerySessionStatus(session_id, key_info);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::QueryKeyStatus(
|
|
const CdmSessionId& session_id, CdmQueryMap* key_info) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_9);
|
|
CdmResponseType sts;
|
|
sts = cdm_engine->QueryKeyStatus(session_id, key_info);
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::QueryOemCryptoSessionId(
|
|
const CdmSessionId& session_id, CdmQueryMap* response) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_10);
|
|
return cdm_engine->QueryOemCryptoSessionId(session_id, response);
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsSecurityLevelSupported(
|
|
CdmSecurityLevel level) {
|
|
return CdmEngine::IsSecurityLevelSupported(level);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetProvisioningRequest(
|
|
CdmCertificateType cert_type, const std::string& cert_authority,
|
|
const CdmIdentifier& identifier, const std::string& service_certificate,
|
|
RequestedSecurityLevel requested_security_level,
|
|
CdmProvisioningRequest* request, std::string* default_url) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->GetProvisioningRequest(
|
|
cert_type, cert_authority, service_certificate, requested_security_level,
|
|
request, default_url);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::HandleProvisioningResponse(
|
|
const CdmIdentifier& identifier, const CdmProvisioningResponse& response,
|
|
RequestedSecurityLevel requested_security_level, std::string* cert,
|
|
std::string* wrapped_key) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->HandleProvisioningResponse(
|
|
response, requested_security_level, cert, wrapped_key);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::Unprovision(
|
|
CdmSecurityLevel level, const CdmIdentifier& identifier) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
// Persistent state is deleted on unprovisioning. The L3 OEMCrypto device
|
|
// key may however remain in memory until |OEMCrypto_Terminate| is called.
|
|
// It is not regenerated until |OEMCrypto_Initialize| is called.
|
|
// Enable immediate OEMCrypto termination and re-initalization on
|
|
// unprovisioning.
|
|
CryptoSession::DisableDelayedTermination();
|
|
return cdm_engine->Unprovision(level);
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsProvisioned(CdmSecurityLevel security_level,
|
|
const std::string& origin,
|
|
const std::string& spoid,
|
|
bool atsc_mode_enabled) {
|
|
FileSystem file_system;
|
|
file_system.set_origin(origin);
|
|
file_system.set_identifier(spoid + origin);
|
|
DeviceFiles device_files(&file_system);
|
|
device_files.Init(security_level);
|
|
return device_files.HasCertificate(atsc_mode_enabled);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetUsageInfo(
|
|
const std::string& app_id, const CdmIdentifier& identifier,
|
|
CdmUsageReportList* usage_reports) {
|
|
if (usage_reports == nullptr) return CdmResponseType(PARAMETER_NULL);
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
int error_detail = NO_ERROR;
|
|
CdmUsageReport usage_report;
|
|
// Doesn't actually retrieve all reports, just a single random one.
|
|
const CdmResponseType status =
|
|
cdm_engine->GetUsageInfo(app_id, &error_detail, &usage_report);
|
|
usage_reports->clear();
|
|
// Possible that no report is available, still a successful call.
|
|
if ((status == NO_ERROR || status == KEY_MESSAGE) && !usage_report.empty()) {
|
|
usage_reports->push_back(std::move(usage_report));
|
|
}
|
|
return status;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetUsageInfo(
|
|
const std::string& app_id, const CdmSecureStopId& ssid,
|
|
const CdmIdentifier& identifier, CdmUsageReport* usage_report) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
int error_detail = NO_ERROR;
|
|
return cdm_engine->GetUsageInfo(app_id, ssid, &error_detail, usage_report);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::RemoveAllUsageInfo(
|
|
const std::string& app_id, const CdmIdentifier& identifier) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->RemoveAllUsageInfo(app_id);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::RemoveUsageInfo(
|
|
const std::string& app_id, const CdmIdentifier& identifier,
|
|
const CdmSecureStopId& secure_stop_id) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->RemoveUsageInfo(app_id, secure_stop_id);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::ReleaseUsageInfo(
|
|
const CdmKeyResponse& message, const CdmIdentifier& identifier) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->ReleaseUsageInfo(message);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetSecureStopIds(
|
|
const std::string& app_id, const CdmIdentifier& identifier,
|
|
std::vector<CdmSecureStopId>* ssids) {
|
|
if (ssids == nullptr) {
|
|
LOGE("Secure stop IDs destination not provided");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
const CdmResponseType sts =
|
|
cdm_engine->ListUsageIds(app_id, kSecurityLevelL1, nullptr, ssids);
|
|
std::vector<CdmSecureStopId> ssids_l3;
|
|
const CdmResponseType sts_l3 =
|
|
cdm_engine->ListUsageIds(app_id, kSecurityLevelL3, nullptr, &ssids_l3);
|
|
ssids->insert(ssids->end(), ssids_l3.begin(), ssids_l3.end());
|
|
return sts_l3 != NO_ERROR ? sts_l3 : sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::Decrypt(
|
|
const CdmSessionId& session_id, bool validate_key_id,
|
|
const CdmDecryptionParameters& parameters) {
|
|
return DecryptV16(session_id, validate_key_id,
|
|
CdmDecryptionParametersV16::from_v15(parameters));
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::DecryptV16(
|
|
const CdmSessionId& session_id, bool validate_key_id,
|
|
const CdmDecryptionParametersV16& parameters) {
|
|
// First find the CdmEngine that has the given session_id. If we are using
|
|
// key sharing, the shared session will still be in the same CdmEngine.
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) {
|
|
LOGE("Decrypt session ID not found: %s", session_id.c_str());
|
|
return CdmResponseType(SESSION_NOT_FOUND_18);
|
|
}
|
|
|
|
CdmSessionId local_session_id = session_id;
|
|
if (validate_key_id && Properties::GetSessionSharingId(session_id) != 0) {
|
|
bool status =
|
|
cdm_engine->FindSessionForKey(parameters.key_id, &local_session_id);
|
|
if (!status) {
|
|
bool is_any_protected = std::any_of(
|
|
std::begin(parameters.samples), std::end(parameters.samples),
|
|
[](const CdmDecryptionSample& sample) -> bool {
|
|
return std::any_of(
|
|
std::begin(sample.subsamples), std::end(sample.subsamples),
|
|
[](const CdmDecryptionSubsample& subsample) -> bool {
|
|
return subsample.protected_bytes > 0;
|
|
});
|
|
});
|
|
|
|
// A key does not need to be loaded if
|
|
// (a) the content consists of one or more samples and is entirely
|
|
// clear data
|
|
// (b) (legacy) the content consists of clear lead/frame in a single
|
|
// subsample. In earlier releases content was presented for decryption
|
|
// as individual subsamples. If the clear frame is broken up across
|
|
// multiple subsamples decryption, requests should be rejected
|
|
// (b/110251447)
|
|
// TODO(b/149524614): Remove support for the legacy case
|
|
if (is_any_protected ||
|
|
(parameters.observe_legacy_fields && parameters.samples.size() == 1 &&
|
|
parameters.samples[0].subsamples.size() == 1 &&
|
|
!(parameters.samples[0].subsamples[0].flags &
|
|
OEMCrypto_FirstSubsample &&
|
|
parameters.samples[0].subsamples[0].flags &
|
|
OEMCrypto_LastSubsample))) {
|
|
return CdmResponseType(KEY_NOT_FOUND_IN_SESSION);
|
|
}
|
|
}
|
|
}
|
|
return cdm_engine->DecryptV16(local_session_id, parameters);
|
|
}
|
|
|
|
void WvContentDecryptionModule::NotifyResolution(const CdmSessionId& session_id,
|
|
uint32_t width,
|
|
uint32_t height) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return;
|
|
cdm_engine->NotifyResolution(session_id, width, height);
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsValidServiceCertificate(
|
|
const std::string& certificate) {
|
|
ServiceCertificate cert;
|
|
CdmResponseType status = cert.Init(certificate);
|
|
if (status != NO_ERROR) return false;
|
|
return cert.has_certificate();
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetCurrentMetrics(
|
|
const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics* metrics) {
|
|
if (!metrics) {
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
return GetCurrentMetricsInternal(identifier, metrics);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetAllCurrentMetricsSnapshots(
|
|
std::vector<WvMetricsSnapshot>* snapshots, bool* full_list_returned) {
|
|
if (!snapshots || !full_list_returned) {
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
snapshots->clear();
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
for (const auto& cdm_info : cdms_) {
|
|
const CdmIdentifier& identifier = cdm_info.first;
|
|
drm_metrics::WvCdmMetrics metric;
|
|
const CdmResponseType status =
|
|
GetCurrentMetricsInternal(identifier, &metric);
|
|
if (status == NO_ERROR) {
|
|
snapshots->push_back(
|
|
WvMetricsSnapshot::MakeSnapshot(identifier, std::move(metric)));
|
|
} else {
|
|
LOGD("Failed to get metrics: cdm_identifier = %u, error = %s",
|
|
identifier.unique_id, status.ToString().c_str());
|
|
}
|
|
}
|
|
// With no streaming activities, |cdms_| size would be zero,
|
|
// treat it as a non full list in that case.
|
|
*full_list_returned = !cdms_.empty() && snapshots->size() == cdms_.size();
|
|
|
|
// We only return error if no metrics is returned when cdms_ is not empty.
|
|
// - metrics && cdms_ sizes can both be zero, returns NO_ERROR
|
|
// - metrics size <= cdms_, returns NO_ERROR
|
|
// - metrics is empty, but cdms_ is not, returns UNKNOWN_ERROR
|
|
return (snapshots->empty() && !cdms_.empty()) ? CdmResponseType(UNKNOWN_ERROR)
|
|
: CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetAllSavedMetricsSnapshots(
|
|
std::vector<WvMetricsSnapshot>* snapshots) {
|
|
if (snapshots == nullptr) {
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
// This has the potential to be an expensive operation. However,
|
|
// this function is only used during debug reporting.
|
|
snapshots->assign(saved_metrics_snapshots_.cbegin(),
|
|
saved_metrics_snapshots_.cend());
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetCurrentMetricsInternal(
|
|
const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics* metrics) {
|
|
// Note: Caller should lock before calling.
|
|
auto it = cdms_.find(identifier);
|
|
if (it == cdms_.end()) {
|
|
LOGE("Cdm Identifier not found");
|
|
// TODO(blueeyes): Add a better error.
|
|
return CdmResponseType(UNKNOWN_ERROR);
|
|
}
|
|
return it->second.cdm_engine->GetMetricsSnapshot(metrics)
|
|
? CdmResponseType(NO_ERROR)
|
|
: CdmResponseType(UNKNOWN_ERROR);
|
|
}
|
|
|
|
void WvContentDecryptionModule::SaveMetrics(
|
|
const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics&& metrics) {
|
|
// Caller should have acquired |cdms_lock_|.
|
|
saved_metrics_snapshots_.push_front(
|
|
WvMetricsSnapshot::MakeSnapshot(identifier, std::move(metrics)));
|
|
if (saved_metrics_snapshots_.size() > kMaxSavedMetricsSnapshotCount) {
|
|
saved_metrics_snapshots_.resize(kMaxSavedMetricsSnapshotCount);
|
|
}
|
|
}
|
|
|
|
WvContentDecryptionModule::CdmInfo::CdmInfo()
|
|
: cdm_engine(CdmEngineFactory::CreateCdmEngine(&file_system)) {}
|
|
|
|
CdmEngine* WvContentDecryptionModule::EnsureCdmForIdentifier(
|
|
const CdmIdentifier& identifier) {
|
|
CdmEngine* cdm_engine;
|
|
bool enable_timer = false;
|
|
{
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
if (cdms_.size() == 0) enable_timer = true;
|
|
if (cdms_.find(identifier) == cdms_.end()) {
|
|
// Accessing the map entry will create a new instance using the default
|
|
// constructor. We then need to provide it with two pieces of info: The
|
|
// origin provided by the app and an identifier that uniquely identifies
|
|
// this CDM. We concatenate all pieces of the CdmIdentifier in order to
|
|
// create an ID that is unique to that identifier.
|
|
cdms_[identifier].file_system.set_origin(identifier.origin);
|
|
cdms_[identifier].file_system.set_identifier(identifier.spoid +
|
|
identifier.origin);
|
|
cdms_[identifier].cdm_engine->SetAppPackageName(
|
|
identifier.app_package_name);
|
|
cdms_[identifier].cdm_engine->SetSpoid(identifier.spoid);
|
|
cdms_[identifier].cdm_engine->SetUserId(identifier.user_id);
|
|
}
|
|
cdm_engine = cdms_[identifier].cdm_engine.get();
|
|
}
|
|
// Do not enable timer while holding on to the |cdms_lock_|
|
|
if (enable_timer) EnableTimer();
|
|
|
|
return cdm_engine;
|
|
}
|
|
|
|
CdmEngine* WvContentDecryptionModule::GetCdmForSessionId(
|
|
const std::string& session_id) {
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
// Use find to avoid creating empty entries when not found.
|
|
auto it = cdm_by_session_id_.find(session_id);
|
|
if (it == cdm_by_session_id_.end()) return nullptr;
|
|
return it->second;
|
|
}
|
|
|
|
void WvContentDecryptionModule::CloseAllCdms() {
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
for (const auto& cdm_info : cdms_) {
|
|
const CdmIdentifier& identifier = cdm_info.first;
|
|
drm_metrics::WvCdmMetrics metrics;
|
|
const CdmResponseType status =
|
|
GetCurrentMetricsInternal(identifier, &metrics);
|
|
if (status == NO_ERROR) {
|
|
SaveMetrics(identifier, std::move(metrics));
|
|
} else {
|
|
LOGD("CloseAllCdms failed to save metrics");
|
|
}
|
|
}
|
|
cdm_by_session_id_.clear();
|
|
cdms_.clear();
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::CloseCdm(
|
|
const CdmIdentifier& cdm_identifier) {
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
auto cdm_it = cdms_.find(cdm_identifier);
|
|
if (cdm_it == cdms_.end()) {
|
|
LOGE("Cdm Identifier not found");
|
|
// TODO(blueeyes): Create a better error.
|
|
return CdmResponseType(UNKNOWN_ERROR);
|
|
}
|
|
CdmEngine* cdm_engine = cdm_it->second.cdm_engine.get();
|
|
// Save metrics snapshot.
|
|
drm_metrics::WvCdmMetrics metrics;
|
|
const bool success = cdm_engine->GetMetricsSnapshot(&metrics);
|
|
if (success) {
|
|
SaveMetrics(cdm_identifier, std::move(metrics));
|
|
} else {
|
|
LOGW("Failed to get metrics snapshot: unique_id = %" PRIu32,
|
|
cdm_identifier.unique_id);
|
|
}
|
|
// Remove any sessions that point to this engine.
|
|
for (auto session_it = cdm_by_session_id_.begin();
|
|
session_it != cdm_by_session_id_.end();) {
|
|
if (session_it->second == cdm_engine) {
|
|
session_it = cdm_by_session_id_.erase(session_it);
|
|
} else {
|
|
++session_it;
|
|
}
|
|
}
|
|
cdms_.erase(cdm_it);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::SetDebugIgnoreKeyboxCount(
|
|
uint32_t count) {
|
|
return CdmEngine::SetDebugIgnoreKeyboxCount(count);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::SetAllowTestKeybox(bool allow) {
|
|
return CdmEngine::SetAllowTestKeybox(allow);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::SetDecryptHash(
|
|
const std::string& hash_data, CdmSessionId* id) {
|
|
if (id == nullptr) {
|
|
LOGE("Cdm session ID not provided");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
|
|
uint32_t frame_number;
|
|
std::string hash;
|
|
CdmResponseType res =
|
|
CdmEngine::ParseDecryptHashString(hash_data, id, &frame_number, &hash);
|
|
|
|
if (res != NO_ERROR) return res;
|
|
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(*id);
|
|
|
|
if (!cdm_engine) {
|
|
LOGE("Unable to find CdmEngine");
|
|
return CdmResponseType(SESSION_NOT_FOUND_20);
|
|
}
|
|
|
|
res = cdm_engine->SetDecryptHash(*id, frame_number, hash);
|
|
return res;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetDecryptHashError(
|
|
const CdmSessionId& session_id, std::string* hash_error_string) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
|
|
if (!cdm_engine) {
|
|
LOGE("Unable to find CdmEngine");
|
|
return CdmResponseType(SESSION_NOT_FOUND_20);
|
|
}
|
|
return cdm_engine->GetDecryptHashError(session_id, hash_error_string);
|
|
}
|
|
|
|
void WvContentDecryptionModule::EnableTimer() {
|
|
std::unique_lock<std::mutex> auto_lock(timer_lock_);
|
|
if (!timer_.IsRunning()) timer_.Start(this, kCdmTimerDurationSeconds);
|
|
}
|
|
|
|
void WvContentDecryptionModule::DisableTimer() {
|
|
std::unique_lock<std::mutex> auto_lock(timer_lock_);
|
|
if (timer_.IsRunning()) {
|
|
timer_.Stop(false);
|
|
}
|
|
}
|
|
|
|
void WvContentDecryptionModule::DisableTimerAndWaitForExit() {
|
|
std::unique_lock<std::mutex> auto_lock(timer_lock_);
|
|
if (timer_.IsRunning()) {
|
|
timer_.Stop(true);
|
|
}
|
|
}
|
|
|
|
void WvContentDecryptionModule::OnTimerEvent() {
|
|
bool disable_timer = false;
|
|
{
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
for (auto it = cdms_.begin(); it != cdms_.end(); ++it) {
|
|
LoggingUidSetter set_uid(it->first.user_id);
|
|
it->second.cdm_engine->OnTimerEvent();
|
|
}
|
|
if (cdms_.empty()) {
|
|
// The following code cannot be attributed to any app uid.
|
|
LoggingUidSetter set_uid(wvutil::UNKNOWN_UID);
|
|
if (CryptoSession::TryTerminate()) {
|
|
// If CryptoSession is in a state to be terminated, try acquiring the
|
|
// |timer_lock_| before deciding whether to disable the timer. If the
|
|
// lock cannot be acquired, there is no need to disable the timer.
|
|
// The |timer_lock_| is either being held by
|
|
// * EnableTimer - This does not appear possible, but if it did
|
|
// happen |OnTimerEvent| will be called again. The timer can be
|
|
// disabled during a future call.
|
|
// * DisableTimer - If so, allow that call to disable the timer. No
|
|
// need to call to disable it here.
|
|
// * DisableTimerAndWaitForExit - If so, allow that call to disable
|
|
// the timer. No need to call to disable it here. Note that
|
|
// |DisableTimerAndWaitForExit| will try to stop the timer but
|
|
// wait for it to exit. This might kick off the timer thread one
|
|
// last time, which will call into OnTimerEvent. Calling
|
|
// |DisableTimer| here will result in deadlock.
|
|
if (timer_lock_.try_lock()) {
|
|
disable_timer = true;
|
|
timer_lock_.unlock();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release |cdms_lock_| before attempting to disable the timer
|
|
if (disable_timer) {
|
|
DisableTimer();
|
|
}
|
|
}
|
|
|
|
uint32_t WvContentDecryptionModule::GenerateSessionSharingId() {
|
|
static int next_session_sharing_id = 0;
|
|
return ++next_session_sharing_id;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::ListStoredLicenses(
|
|
CdmSecurityLevel security_level, const CdmIdentifier& identifier,
|
|
std::vector<CdmKeySetId>* key_set_ids) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->ListStoredLicenses(security_level, key_set_ids);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetOfflineLicenseState(
|
|
const CdmKeySetId& key_set_id, CdmSecurityLevel security_level,
|
|
const CdmIdentifier& identifier, CdmOfflineLicenseState* license_state) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->GetOfflineLicenseState(key_set_id, security_level,
|
|
license_state);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::RemoveOfflineLicense(
|
|
const CdmKeySetId& key_set_id, CdmSecurityLevel security_level,
|
|
const CdmIdentifier& identifier) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->RemoveOfflineLicense(key_set_id, security_level);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::SetPlaybackId(
|
|
const CdmSessionId& session_id, const std::string& playback_id) {
|
|
LOGV("Setting session ID %s playback ID %s", session_id.c_str(),
|
|
playback_id.c_str());
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_23);
|
|
return cdm_engine->SetPlaybackId(session_id, playback_id);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetSessionUserId(
|
|
const CdmSessionId& session_id, uint32_t* user_id) {
|
|
if (!user_id) return CdmResponseType(PARAMETER_NULL);
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_23);
|
|
*user_id = cdm_engine->GetUserId();
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::StoreAtscLicense(
|
|
const CdmIdentifier& identifier,
|
|
RequestedSecurityLevel requested_security_level,
|
|
const CdmKeySetId& key_set_id, const std::string& serialized_license_data) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->StoreAtscLicense(requested_security_level, key_set_id,
|
|
serialized_license_data);
|
|
}
|
|
|
|
bool WvContentDecryptionModule::SetDefaultOtaKeyboxFallbackDurationRules() {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(kDefaultCdmIdentifier);
|
|
if (!cdm_engine) return false;
|
|
cdm_engine->SetDefaultOtaKeyboxFallbackDurationRules();
|
|
return true;
|
|
}
|
|
|
|
bool WvContentDecryptionModule::SetFastOtaKeyboxFallbackDurationRules() {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(kDefaultCdmIdentifier);
|
|
if (!cdm_engine) return false;
|
|
cdm_engine->SetFastOtaKeyboxFallbackDurationRules();
|
|
return true;
|
|
}
|
|
|
|
} // namespace wvcdm
|