1844 lines
60 KiB
C++
1844 lines
60 KiB
C++
// 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.
|
|
#include "cdm.h"
|
|
|
|
#include <assert.h>
|
|
#include <limits.h> // LLONG_MAX
|
|
#include <string.h> // memcpy
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
// core:
|
|
#include "cdm_client_property_set.h"
|
|
#include "cdm_engine.h"
|
|
#include "cdm_engine_factory.h"
|
|
#include "clock.h"
|
|
#include "crypto_session.h"
|
|
#include "device_files.h"
|
|
#include "file_store.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_cdm_types.h"
|
|
#include "wv_metrics.pb.h"
|
|
|
|
// CE:
|
|
#include "cdm_version.h"
|
|
#include "properties_ce.h"
|
|
|
|
namespace widevine {
|
|
|
|
using namespace wvcdm;
|
|
|
|
namespace {
|
|
|
|
constexpr const char* kNoSandboxId = "";
|
|
const int64_t kPolicyTimerDurationMilliseconds = 5000;
|
|
void* const kPolicyTimerContext = nullptr;
|
|
|
|
struct HostType {
|
|
Cdm::IStorage* storage;
|
|
Cdm::IClock* clock;
|
|
Cdm::ITimer* timer;
|
|
FileSystem* file_system;
|
|
bool initialized;
|
|
|
|
HostType()
|
|
: storage(nullptr),
|
|
clock(nullptr),
|
|
timer(nullptr),
|
|
file_system(nullptr),
|
|
initialized(false) {}
|
|
} host;
|
|
|
|
class PropertySet final : public CdmClientPropertySet {
|
|
public:
|
|
PropertySet() : use_privacy_mode_(false) {}
|
|
|
|
~PropertySet() override {}
|
|
|
|
const std::string& security_level() const override {
|
|
// Unused on CE platforms. Used by Android to switch to L3.
|
|
return empty_string_;
|
|
}
|
|
|
|
void set_use_privacy_mode(bool use) { use_privacy_mode_ = use; }
|
|
|
|
bool use_privacy_mode() const override { return use_privacy_mode_; }
|
|
|
|
const std::string& service_certificate() const override {
|
|
return licensing_service_certificate_;
|
|
}
|
|
|
|
void set_service_certificate(const std::string& cert) override {
|
|
licensing_service_certificate_ = cert;
|
|
}
|
|
|
|
bool is_session_sharing_enabled() const override {
|
|
// Unused on CE platforms.
|
|
return true;
|
|
}
|
|
|
|
uint32_t session_sharing_id() const override {
|
|
// Unused on CE platforms.
|
|
return 1;
|
|
}
|
|
|
|
void set_session_sharing_id(uint32_t) override {
|
|
// Unused on CE platforms.
|
|
return;
|
|
}
|
|
|
|
const std::string& app_id() const override {
|
|
// Unused on CE platforms.
|
|
return empty_string_;
|
|
}
|
|
|
|
bool use_atsc_mode() const override { return false; }
|
|
|
|
private:
|
|
bool use_privacy_mode_;
|
|
std::string licensing_service_certificate_;
|
|
|
|
// This is empty, but g++ 4.8 will not allow app_id() to return a string
|
|
// literal as a const reference to std::string.
|
|
const std::string empty_string_;
|
|
};
|
|
|
|
// A wrapper for another IStorage instance that intercepts attempts to write to
|
|
// that storage and blocks them. Acts as a last-ditch safeguard against the CDM
|
|
// trying to write erroneously to filesystems where it is critical that we never
|
|
// overwrite or delete the files. Specifically, preloaded files on ATSC 3.
|
|
//
|
|
// TODO(b/148693106): Right now, this wrapper fails writes silently because
|
|
// there are known bugs where the CDM will still try to write to it. Once these
|
|
// bugs are resolved, we can change this wrapper to return errors on write
|
|
// attempts. The wrapper itself will still be necessary, in order to prevent any
|
|
// future bugs from corrupting ATSC files.
|
|
class ReadOnlyStorage final : public Cdm::IStorage {
|
|
public:
|
|
explicit ReadOnlyStorage(Cdm::IStorage* storage) : storage_(storage) {}
|
|
~ReadOnlyStorage() override {}
|
|
|
|
bool read(const std::string& name, std::string* data) override {
|
|
return storage_->read(name, data);
|
|
}
|
|
|
|
bool write(const std::string& name, const std::string& data) override {
|
|
// TODO(b/148693106): Once we have resolved the bugs causing the CDM to
|
|
// erroneously write to read-only files, change this to an error instead of
|
|
// a silent failure.
|
|
return true;
|
|
}
|
|
|
|
bool exists(const std::string& name) override {
|
|
return storage_->exists(name);
|
|
}
|
|
|
|
bool remove(const std::string& name) override {
|
|
// TODO(b/148693106): Once we have resolved the bugs causing the CDM to
|
|
// erroneously write to read-only files, change this to an error instead of
|
|
// a silent failure.
|
|
return true;
|
|
}
|
|
|
|
int32_t size(const std::string& name) override {
|
|
return storage_->size(name);
|
|
}
|
|
|
|
bool list(std::vector<std::string>* file_names) override {
|
|
return storage_->list(file_names);
|
|
}
|
|
|
|
private:
|
|
Cdm::IStorage* storage_; // Lifetime is managed by the caller
|
|
};
|
|
|
|
// This class wraps a raw C array passed as a pointer and length in the
|
|
// appropriate interface to be used with functions that expect a C++11 range.
|
|
template <typename T>
|
|
class CArrayRangeView {
|
|
public:
|
|
CArrayRangeView(const T* data, size_t length)
|
|
: data_(data), length_(length) {}
|
|
|
|
const T* begin() const { return data_; }
|
|
const T* end() const { return data_ + length_; }
|
|
|
|
private:
|
|
const T* const data_;
|
|
const size_t length_;
|
|
};
|
|
|
|
class CdmImpl final : public Cdm, public WvCdmEventListener {
|
|
public:
|
|
CdmImpl(IEventListener* listener, IStorage* storage, bool privacy_mode,
|
|
const ReadOnlyStorage* owned_storage_object);
|
|
|
|
~CdmImpl() override;
|
|
|
|
// Cdm:
|
|
Status setServiceCertificate(ServiceRole role,
|
|
const std::string& certificate) override;
|
|
|
|
Status getServiceCertificateRequest(std::string* message) override;
|
|
|
|
Status parseAndLoadServiceCertificateResponse(
|
|
ServiceRole role, const std::string& response,
|
|
std::string* certificate) override;
|
|
|
|
Status getRobustnessLevel(RobustnessLevel* level) override;
|
|
|
|
Status getResourceRatingTier(uint32_t* tier) override;
|
|
|
|
Status getOemCryptoBuildInfo(std::string* build_info) override;
|
|
|
|
bool isProvisioned() override;
|
|
|
|
Status getProvisioningRequest(std::string* request) override;
|
|
|
|
Status handleProvisioningResponse(const std::string& response) override;
|
|
|
|
Status removeProvisioning() override;
|
|
|
|
Status listStoredLicenses(std::vector<std::string>* key_set_ids) override;
|
|
|
|
Status listUsageRecords(std::vector<std::string>* ksids) override;
|
|
|
|
Status deleteUsageRecord(const std::string& key_set_id) override;
|
|
|
|
Status deleteAllUsageRecords() override;
|
|
|
|
Status getStatusForHdcpVersion(HdcpVersion hdcp,
|
|
KeyStatus* key_status) override;
|
|
|
|
Status createSession(SessionType session_type,
|
|
std::string* session_id) override;
|
|
|
|
Status generateRequest(const std::string& session_id,
|
|
InitDataType init_data_type,
|
|
const std::string& init_data) override;
|
|
|
|
Status load(const std::string& session_id) override;
|
|
|
|
Status update(const std::string& session_id,
|
|
const std::string& response) override;
|
|
|
|
Status loadEmbeddedKeys(const std::string& session_id,
|
|
InitDataType init_data_type,
|
|
const std::string& init_data) override;
|
|
|
|
Status getExpiration(const std::string& session_id,
|
|
int64_t* expiration) override;
|
|
|
|
Status getKeyStatuses(const std::string& session_id,
|
|
KeyStatusMap* key_statuses) override;
|
|
|
|
Status getKeyAllowedUsages(const std::string& session_id,
|
|
const std::string& key_id,
|
|
KeyAllowedUsageFlags* usage_flags) override;
|
|
|
|
Status getKeyAllowedUsages(const std::string& key_id,
|
|
KeyAllowedUsageFlags* usage_flags) override;
|
|
|
|
Status setAppParameter(const std::string& key,
|
|
const std::string& value) override;
|
|
|
|
Status getAppParameter(const std::string& key, std::string* result) override;
|
|
|
|
Status removeAppParameter(const std::string& key) override;
|
|
|
|
Status clearAppParameters() override;
|
|
|
|
Status close(const std::string& session_id) override;
|
|
|
|
Status remove(const std::string& session_id) override;
|
|
|
|
Status forceRemove(const std::string& session_id) override;
|
|
|
|
Status decrypt(const DecryptionBatch& batch) override;
|
|
|
|
Status decrypt(const std::string& session_id,
|
|
const DecryptionBatch& batch) override;
|
|
|
|
Status genericEncrypt(const std::string& session_id,
|
|
const std::string& in_buffer, const std::string& key_id,
|
|
const std::string& iv,
|
|
GenericEncryptionAlgorithmType algorithm,
|
|
std::string* out_buffer) override;
|
|
Status genericDecrypt(const std::string& session_id,
|
|
const std::string& in_buffer, const std::string& key_id,
|
|
const std::string& iv,
|
|
GenericEncryptionAlgorithmType algorithm,
|
|
std::string* out_buffer) override;
|
|
Status genericSign(const std::string& session_id, const std::string& message,
|
|
const std::string& key_id,
|
|
GenericSigningAlgorithmType algorithm,
|
|
std::string* signature) override;
|
|
Status genericVerify(const std::string& session_id,
|
|
const std::string& message, const std::string& key_id,
|
|
GenericSigningAlgorithmType algorithm,
|
|
const std::string& signature) override;
|
|
|
|
Status setVideoResolution(const std::string& session_id, uint32_t width,
|
|
uint32_t height) override;
|
|
|
|
Status getMetrics(std::string* serialized_metrics) override;
|
|
|
|
// ITimerClient:
|
|
void onTimerExpired(void* context) override;
|
|
|
|
// WvCdmEventListener:
|
|
void OnSessionRenewalNeeded(const CdmSessionId& session_id) override;
|
|
|
|
void OnSessionKeysChange(const CdmSessionId& session_id,
|
|
const CdmKeyStatusMap& keys_status,
|
|
bool /* has_new_usable_key */) override;
|
|
|
|
void OnExpirationUpdate(const CdmSessionId& session_id,
|
|
int64_t new_expiry_time_seconds) override;
|
|
|
|
private:
|
|
KeyAllowedUsageFlags KeyAllowedFlags(const CdmKeyAllowedUsage& usages);
|
|
CdmEncryptionAlgorithm ConvertEncryptionAlgorithm(
|
|
GenericEncryptionAlgorithmType algorithm);
|
|
CdmSigningAlgorithm ConvertSigningAlgorithm(
|
|
GenericSigningAlgorithmType algorithm);
|
|
Cdm::Status ConvertHdcpLevel(const std::string& query_value,
|
|
Cdm::HdcpVersion* result);
|
|
|
|
IEventListener* listener_;
|
|
bool policy_timer_enabled_;
|
|
PropertySet property_set_;
|
|
std::string provisioning_service_certificate_;
|
|
|
|
FileSystem file_system_;
|
|
std::unique_ptr<CdmEngine> cdm_engine_;
|
|
CdmAppParameterMap app_parameters_;
|
|
|
|
struct SessionMetadata {
|
|
bool callable; // EME terminology: request generated or session loaded
|
|
SessionType type;
|
|
int64_t expiration;
|
|
KeyStatusMap key_statuses;
|
|
|
|
SessionMetadata() : callable(false), type((SessionType)-1), expiration(0) {}
|
|
};
|
|
|
|
std::map<std::string, SessionMetadata> sessions_;
|
|
|
|
// This field will be nullptr unless the create() function had to allocate
|
|
// memory for a read-only storage wrapper that needs to be cleaned up when
|
|
// this CDM instance is deleted. The storage should always be accessed via
|
|
// file_system_ and not via this parameter directly. However, it must be
|
|
// stored so it can be cleaned up later.
|
|
const std::unique_ptr<const ReadOnlyStorage> owned_storage_object_;
|
|
};
|
|
|
|
CdmImpl::CdmImpl(IEventListener* listener, IStorage* storage, bool privacy_mode,
|
|
const ReadOnlyStorage* owned_storage_object)
|
|
: listener_(listener),
|
|
policy_timer_enabled_(false),
|
|
provisioning_service_certificate_(),
|
|
file_system_("", storage),
|
|
cdm_engine_(CdmEngineFactory::CreateCdmEngine(&file_system_)),
|
|
owned_storage_object_(owned_storage_object) {
|
|
assert(nullptr != listener_);
|
|
property_set_.set_use_privacy_mode(privacy_mode);
|
|
}
|
|
|
|
CdmImpl::~CdmImpl() { host.timer->cancel(this); }
|
|
|
|
Cdm::Status CdmImpl::setServiceCertificate(ServiceRole role,
|
|
const std::string& certificate) {
|
|
if (certificate.empty()) {
|
|
LOGE("Service certificate string is empty.");
|
|
return kTypeError;
|
|
}
|
|
|
|
// Verify that the certificate is properly signed and well-formed.
|
|
CdmResponseType status = cdm_engine_->ValidateServiceCertificate(certificate);
|
|
if (status != NO_ERROR) {
|
|
LOGE("Invalid service certificate! Error code = %d",
|
|
static_cast<int>(status));
|
|
return kTypeError;
|
|
}
|
|
|
|
if (role == kLicensingService || role == kAllServices) {
|
|
property_set_.set_service_certificate(certificate);
|
|
|
|
// Update all open sessions with the new certificate.
|
|
for (const auto& session_pair : sessions_) {
|
|
cdm_engine_->SetSessionServiceCertificate(session_pair.first,
|
|
certificate);
|
|
}
|
|
}
|
|
|
|
if (role == kProvisioningService || role == kAllServices) {
|
|
provisioning_service_certificate_ = certificate;
|
|
}
|
|
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::getServiceCertificateRequest(std::string* message) {
|
|
if (!message) {
|
|
LOGE(
|
|
"Unable to return service certificate request - "
|
|
"string return parameter not supplied");
|
|
return kTypeError;
|
|
}
|
|
if (!ServiceCertificate::GetRequest(message)) {
|
|
LOGE("Unable to return service certificate request!");
|
|
message->clear();
|
|
return kTypeError;
|
|
}
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::parseAndLoadServiceCertificateResponse(
|
|
ServiceRole role, const std::string& response, std::string* certificate) {
|
|
std::string parsed_cert;
|
|
if (ServiceCertificate::ParseResponse(response, &parsed_cert) != NO_ERROR) {
|
|
LOGE("Failure parsing service certificate response!");
|
|
return kTypeError;
|
|
}
|
|
if (certificate) *certificate = parsed_cert;
|
|
return setServiceCertificate(role, parsed_cert);
|
|
}
|
|
|
|
Cdm::Status CdmImpl::getRobustnessLevel(RobustnessLevel* level) {
|
|
if (level == nullptr) {
|
|
LOGE("Missing level parameter to receive robustness level.");
|
|
return kTypeError;
|
|
}
|
|
|
|
std::string level_string;
|
|
const CdmResponseType result = cdm_engine_->QueryStatus(
|
|
kLevelDefault, QUERY_KEY_SECURITY_LEVEL, &level_string);
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result != NO_ERROR) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
if (level_string == QUERY_VALUE_SECURITY_LEVEL_L1) {
|
|
*level = kL1;
|
|
} else if (level_string == QUERY_VALUE_SECURITY_LEVEL_L2) {
|
|
*level = kL2;
|
|
} else if (level_string == QUERY_VALUE_SECURITY_LEVEL_L3) {
|
|
*level = kL3;
|
|
} else {
|
|
LOGE("Unknown robustness level: %s", level_string.c_str());
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::getResourceRatingTier(uint32_t* tier) {
|
|
if (tier == nullptr) {
|
|
LOGE("Missing tier parameter to receive resource rating tier.");
|
|
return kTypeError;
|
|
}
|
|
|
|
std::string tier_string;
|
|
const CdmResponseType result = cdm_engine_->QueryStatus(
|
|
kLevelDefault, QUERY_KEY_RESOURCE_RATING_TIER, &tier_string);
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result != NO_ERROR) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
const uint32_t parsed_tier = static_cast<uint32_t>(std::stoul(tier_string));
|
|
if (parsed_tier <= 0) {
|
|
LOGE("Invalid resource rating tier %u", parsed_tier);
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
*tier = parsed_tier;
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::getOemCryptoBuildInfo(std::string* build_info) {
|
|
if (build_info == nullptr) {
|
|
LOGE("Missing build_info parameter to receive build info.");
|
|
return kTypeError;
|
|
}
|
|
|
|
CdmResponseType result = cdm_engine_->QueryStatus(
|
|
kLevelDefault, QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION, build_info);
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result != NO_ERROR) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
return kSuccess;
|
|
}
|
|
|
|
bool CdmImpl::isProvisioned() {
|
|
return cdm_engine_->IsProvisioned(kSecurityLevelL1);
|
|
}
|
|
|
|
Cdm::Status CdmImpl::getProvisioningRequest(std::string* request) {
|
|
std::string empty_authority;
|
|
std::string ignored_base_url;
|
|
CdmResponseType result = cdm_engine_->GetProvisioningRequest(
|
|
kCertificateWidevine, empty_authority, provisioning_service_certificate_,
|
|
kLevelDefault, request, &ignored_base_url);
|
|
if (result == CERT_PROVISIONING_NONCE_GENERATION_ERROR) {
|
|
LOGE("Nonce quota exceeded");
|
|
return kResourceContention;
|
|
} else if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result != NO_ERROR) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::handleProvisioningResponse(const std::string& response) {
|
|
std::string ignored_cert;
|
|
std::string ignored_wrapped_key;
|
|
|
|
CdmResponseType result = cdm_engine_->HandleProvisioningResponse(
|
|
response, kLevelDefault, &ignored_cert, &ignored_wrapped_key);
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result != NO_ERROR) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::removeProvisioning() {
|
|
CdmResponseType result = cdm_engine_->Unprovision(kSecurityLevelL1);
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result != NO_ERROR) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::listStoredLicenses(std::vector<std::string>* key_set_ids) {
|
|
if (key_set_ids == nullptr) {
|
|
LOGE("Missing vector parameter to receive key_set_ids.");
|
|
return kTypeError;
|
|
}
|
|
CdmResponseType result =
|
|
cdm_engine_->ListStoredLicenses(kSecurityLevelL1, key_set_ids);
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result != NO_ERROR) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::listUsageRecords(std::vector<std::string>* ksids) {
|
|
if (ksids == nullptr) {
|
|
LOGE("Missing vector parameter to receive KSIDs.");
|
|
return kTypeError;
|
|
}
|
|
CdmResponseType result = cdm_engine_->ListUsageIds(
|
|
property_set_.app_id(), kSecurityLevelL1, ksids, nullptr);
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result != NO_ERROR) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::deleteUsageRecord(const std::string& key_set_id) {
|
|
CdmResponseType result = cdm_engine_->DeleteUsageRecord(
|
|
property_set_.app_id(), kSecurityLevelL1, key_set_id);
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result != NO_ERROR) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::deleteAllUsageRecords() {
|
|
CdmResponseType result =
|
|
cdm_engine_->RemoveAllUsageInfo(property_set_.app_id(), kSecurityLevelL1);
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result != NO_ERROR) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::getStatusForHdcpVersion(Cdm::HdcpVersion hdcp,
|
|
Cdm::KeyStatus* key_status) {
|
|
std::string query_value;
|
|
if (cdm_engine_->QueryStatus(kLevelDefault, QUERY_KEY_MAX_HDCP_LEVEL,
|
|
&query_value) != NO_ERROR) {
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
if (query_value == QUERY_VALUE_HDCP_NONE ||
|
|
query_value == QUERY_VALUE_HDCP_LEVEL_UNKNOWN) {
|
|
*key_status = Cdm::kOutputRestricted;
|
|
} else if (query_value == QUERY_VALUE_HDCP_NO_DIGITAL_OUTPUT) {
|
|
*key_status = Cdm::kUsable;
|
|
} else {
|
|
Cdm::HdcpVersion max_hdcp;
|
|
if (ConvertHdcpLevel(query_value, &max_hdcp) != kSuccess) {
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
*key_status = (hdcp <= max_hdcp ? Cdm::kUsable : Cdm::kOutputRestricted);
|
|
}
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::createSession(SessionType session_type,
|
|
std::string* session_id) {
|
|
if (session_id == nullptr) {
|
|
LOGE("Missing session ID pointer.");
|
|
return kTypeError;
|
|
}
|
|
// Important! The caller may pass a pre-filled string, which must be cleared
|
|
// before being given to CdmEngine.
|
|
session_id->clear();
|
|
|
|
switch (session_type) {
|
|
case kTemporary:
|
|
case kPersistentLicense:
|
|
case kPersistentUsageRecord:
|
|
break;
|
|
default:
|
|
LOGE("Unsupported session type: %d", static_cast<int>(session_type));
|
|
return kNotSupported;
|
|
}
|
|
|
|
CdmResponseType result = cdm_engine_->OpenSession(
|
|
"com.widevine.alpha", &property_set_, this, session_id);
|
|
switch (result) {
|
|
case NO_ERROR:
|
|
sessions_[*session_id].type = session_type;
|
|
return kSuccess;
|
|
case NEED_PROVISIONING:
|
|
// The Session ID may have been set by the CDM Engine despite this
|
|
// failure. We clear the ID here to ensure that we don't communicate a
|
|
// misleading ID to the application.
|
|
session_id->clear();
|
|
return kNeedsDeviceCertificate;
|
|
case SYSTEM_INVALIDATED_ERROR:
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
default:
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
}
|
|
|
|
Cdm::Status CdmImpl::generateRequest(const std::string& session_id,
|
|
InitDataType init_data_type,
|
|
const std::string& init_data) {
|
|
if (!cdm_engine_->IsOpenSession(session_id)) {
|
|
LOGE("No such session: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
|
|
if (sessions_[session_id].callable) {
|
|
LOGE("Request already generated: %s", session_id.c_str());
|
|
return kInvalidState;
|
|
}
|
|
|
|
const SessionType session_type = sessions_[session_id].type;
|
|
CdmLicenseType license_type;
|
|
switch (session_type) {
|
|
case kTemporary:
|
|
license_type = kLicenseTypeTemporary;
|
|
break;
|
|
case kPersistentLicense:
|
|
license_type = kLicenseTypeOffline;
|
|
break;
|
|
case kPersistentUsageRecord:
|
|
license_type = kLicenseTypeStreaming;
|
|
break;
|
|
default:
|
|
LOGE("Unexpected session type: %d", static_cast<int>(session_type));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
std::string init_data_type_name;
|
|
switch (init_data_type) {
|
|
case kCenc:
|
|
init_data_type_name = CENC_INIT_DATA_FORMAT;
|
|
break;
|
|
case kKeyIds:
|
|
LOGE("Key IDs init data type is not supported.");
|
|
return kNotSupported;
|
|
case kWebM:
|
|
init_data_type_name = WEBM_INIT_DATA_FORMAT;
|
|
break;
|
|
case kHls:
|
|
init_data_type_name = HLS_INIT_DATA_FORMAT;
|
|
break;
|
|
default:
|
|
LOGE("Invalid init data type: %d", static_cast<int>(init_data_type));
|
|
return kTypeError;
|
|
}
|
|
|
|
if (init_data.empty()) {
|
|
LOGE("Empty init data is not valid.");
|
|
return kTypeError;
|
|
}
|
|
|
|
std::string oec_version;
|
|
if (cdm_engine_->QueryStatus(wvcdm::kLevelDefault,
|
|
QUERY_KEY_OEMCRYPTO_API_VERSION,
|
|
&oec_version) != NO_ERROR) {
|
|
return kUnexpectedError;
|
|
}
|
|
InitializationData init_data_obj(init_data_type_name, init_data, oec_version);
|
|
if (init_data_obj.IsEmpty()) {
|
|
// Note that InitializationData's idea of "empty" includes "failed to find
|
|
// and parse a Widevine PSSH". This should not happen for WebM init data,
|
|
// which requires no parsing.
|
|
LOGE("Failed to parse init data, may not contain a Widevine PSSH.");
|
|
return kNotSupported;
|
|
}
|
|
|
|
CdmKeyRequest key_request;
|
|
|
|
const CdmResponseType result = cdm_engine_->GenerateKeyRequest(
|
|
session_id, session_id, init_data_obj, license_type, app_parameters_,
|
|
&key_request);
|
|
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result == SESSION_LOST_STATE_ERROR) {
|
|
LOGE("Session invalidated");
|
|
return kSessionStateLost;
|
|
} else if (result == LICENSE_REQUEST_NONCE_GENERATION_ERROR) {
|
|
LOGE("Nonce quota exceeded");
|
|
return kResourceContention;
|
|
} else if (result == NEED_PROVISIONING) {
|
|
LOGE("Device not provisioned");
|
|
return kNeedsDeviceCertificate;
|
|
} else if (result == PRIVACY_MODE_ERROR_1 || result == PRIVACY_MODE_ERROR_2 ||
|
|
result == PRIVACY_MODE_ERROR_3) {
|
|
LOGE("No licensing service certificate installed");
|
|
return kNeedsServiceCertificate;
|
|
} else if (result != KEY_MESSAGE) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
sessions_[session_id].callable = true;
|
|
assert(key_request.type == kKeyRequestTypeInitial);
|
|
|
|
LOGI("A license request has been generated");
|
|
listener_->onMessage(session_id, kLicenseRequest, key_request.message);
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::load(const std::string& session_id) {
|
|
if (session_id.empty()) {
|
|
LOGE("Empty session ID.");
|
|
return kTypeError;
|
|
}
|
|
|
|
if (cdm_engine_->IsOpenSession(session_id)) {
|
|
LOGE("Session ID already loaded.");
|
|
return kQuotaExceeded;
|
|
}
|
|
|
|
CdmResponseType result = cdm_engine_->OpenSession(
|
|
"com.widevine.alpha", &property_set_, session_id, this);
|
|
switch (result) {
|
|
case NO_ERROR:
|
|
break;
|
|
case SYSTEM_INVALIDATED_ERROR:
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
case NEED_PROVISIONING:
|
|
return kNeedsDeviceCertificate;
|
|
default:
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
DeviceFiles f(&file_system_);
|
|
if (!f.Init(kSecurityLevelUnknown)) {
|
|
LOGE("Unexpected error, failed to init DeviceFiles");
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
if (!f.LicenseExists(session_id)) {
|
|
// This might be a usage record session which needs to be loaded.
|
|
CdmKeyMessage ignored_release_message;
|
|
result =
|
|
cdm_engine_->LoadUsageSession(session_id, &ignored_release_message);
|
|
if (result != KEY_MESSAGE) {
|
|
cdm_engine_->CloseSession(session_id);
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result == LOAD_USAGE_INFO_MISSING) {
|
|
LOGE("Unable to load license: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
} else {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
}
|
|
|
|
sessions_[session_id].type = kPersistentUsageRecord;
|
|
sessions_[session_id].callable = true;
|
|
return kSuccess;
|
|
}
|
|
|
|
result = cdm_engine_->RestoreKey(session_id, session_id);
|
|
if (result == GET_RELEASED_LICENSE_ERROR) {
|
|
// This was partially removed already.
|
|
// The EME spec states that we should be able to load it, but not use it.
|
|
} else if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result != KEY_ADDED) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
if (!policy_timer_enabled_) {
|
|
policy_timer_enabled_ = true;
|
|
host.timer->setTimeout(kPolicyTimerDurationMilliseconds, this,
|
|
kPolicyTimerContext);
|
|
}
|
|
|
|
sessions_[session_id].type = kPersistentLicense;
|
|
sessions_[session_id].callable = true;
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::update(const std::string& session_id,
|
|
const std::string& response) {
|
|
if (!cdm_engine_->IsOpenSession(session_id)) {
|
|
LOGE("No such session: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
|
|
if (!sessions_[session_id].callable) {
|
|
LOGE("Request not yet generated: %s", session_id.c_str());
|
|
return kInvalidState;
|
|
}
|
|
|
|
if (response.empty()) {
|
|
LOGE("Empty response.");
|
|
return kTypeError;
|
|
}
|
|
|
|
// NOTE: If the CdmSession object recognizes that this is not the first
|
|
// AddKey(), it will internally delegate to RenewKey().
|
|
CdmKeySetId key_set_id = session_id;
|
|
CdmLicenseType license_type = {}; // Required for AddKey. Unused otherwise.
|
|
const CdmResponseType result =
|
|
cdm_engine_->AddKey(session_id, response, &license_type, &key_set_id);
|
|
|
|
// result should only be NEED_KEY after server certificate provisioning, which
|
|
// should no longer happen in this version of the CDM.
|
|
assert(result != NEED_KEY);
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result == SESSION_LOST_STATE_ERROR) {
|
|
LOGE("Session invalidated");
|
|
return kSessionStateLost;
|
|
} else if (result == OFFLINE_LICENSE_PROHIBITED) {
|
|
LOGE("A temporary session cannot be used for a persistent license.");
|
|
return kRangeError;
|
|
} else if (result == STORAGE_PROHIBITED) {
|
|
LOGE("A temporary session cannot be used for a persistent usage records.");
|
|
return kRangeError;
|
|
} else if (result == NEED_PROVISIONING) {
|
|
LOGE("The device needs to reprovision.");
|
|
return kNeedsDeviceCertificate;
|
|
} else if (result != KEY_ADDED) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
if (!policy_timer_enabled_) {
|
|
policy_timer_enabled_ = true;
|
|
host.timer->setTimeout(kPolicyTimerDurationMilliseconds, this,
|
|
kPolicyTimerContext);
|
|
}
|
|
|
|
if (cdm_engine_->IsReleaseSession(session_id)) {
|
|
sessions_.erase(session_id);
|
|
cdm_engine_->CloseSession(session_id);
|
|
listener_->onRemoveComplete(session_id);
|
|
}
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::loadEmbeddedKeys(const std::string& session_id,
|
|
InitDataType init_data_type,
|
|
const std::string& init_data) {
|
|
if (!cdm_engine_->IsOpenSession(session_id)) {
|
|
LOGE("Session %s does not exist.", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
|
|
if (init_data_type != kCenc) {
|
|
LOGE("Invalid init data type: %d", static_cast<int>(init_data_type));
|
|
return kTypeError;
|
|
}
|
|
|
|
std::string oec_version;
|
|
if (cdm_engine_->QueryStatus(wvcdm::kLevelDefault,
|
|
QUERY_KEY_OEMCRYPTO_API_VERSION,
|
|
&oec_version) != NO_ERROR) {
|
|
return kUnexpectedError;
|
|
}
|
|
InitializationData init_data_obj(CENC_INIT_DATA_FORMAT, init_data,
|
|
oec_version);
|
|
if (init_data_obj.IsEmpty()) {
|
|
// Note that InitializationData's idea of "empty" includes "failed to find
|
|
// and parse a Widevine PSSH". This should not happen for WebM init data,
|
|
// which requires no parsing.
|
|
LOGE("Failed to parse init data, may not contain a Widevine PSSH.");
|
|
return kNotSupported;
|
|
}
|
|
|
|
CdmKeyRequest key_request;
|
|
const CdmResponseType result = cdm_engine_->GenerateKeyRequest(
|
|
session_id, session_id, init_data_obj, kLicenseTypeEmbeddedKeyData,
|
|
app_parameters_, &key_request);
|
|
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result == SESSION_LOST_STATE_ERROR) {
|
|
LOGE("Session invalidated");
|
|
return kSessionStateLost;
|
|
} else if (result == LICENSE_REQUEST_NONCE_GENERATION_ERROR) {
|
|
LOGE("Nonce quota exceeded");
|
|
return kResourceContention;
|
|
} else if (result != KEY_ADDED) {
|
|
LOGE("Unexpected Failure: GenerateKeyRequest() returned %d",
|
|
static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::getExpiration(const std::string& session_id,
|
|
int64_t* expiration) {
|
|
if (!cdm_engine_->IsOpenSession(session_id)) {
|
|
LOGE("No such session: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
if (nullptr == expiration) {
|
|
LOGE("Missing pointer to expiration result.");
|
|
return kTypeError;
|
|
}
|
|
|
|
*expiration = sessions_[session_id].expiration;
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::getKeyStatuses(const std::string& session_id,
|
|
KeyStatusMap* key_statuses) {
|
|
if (!cdm_engine_->IsOpenSession(session_id)) {
|
|
LOGE("No such session: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
if (nullptr == key_statuses) {
|
|
LOGE("Missing pointer to KeyStatusMap result.");
|
|
return kTypeError;
|
|
}
|
|
|
|
*key_statuses = sessions_[session_id].key_statuses;
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::getKeyAllowedUsages(const std::string& session_id,
|
|
const std::string& key_id,
|
|
KeyAllowedUsageFlags* usage_flags) {
|
|
if (!cdm_engine_->IsOpenSession(session_id)) {
|
|
LOGE("No such session: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
if (nullptr == usage_flags) {
|
|
LOGE("Missing pointer to KeyAllowedUsageFlags result.");
|
|
return kTypeError;
|
|
}
|
|
|
|
CdmKeyAllowedUsage usage_for_key;
|
|
CdmResponseType result =
|
|
cdm_engine_->QueryKeyAllowedUsage(session_id, key_id, &usage_for_key);
|
|
if (result != NO_ERROR) {
|
|
// TODO(b/114435278): There are multiple KEY_NOT_FOUND_* errors that should
|
|
// probably all turn into kNoKey. Here, and below, and everywhere.
|
|
if (result == KEY_NOT_FOUND_1) {
|
|
return kNoKey;
|
|
} else if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result == SESSION_LOST_STATE_ERROR) {
|
|
LOGE("Session invalidated");
|
|
return kSessionStateLost;
|
|
} else {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
}
|
|
|
|
*usage_flags = KeyAllowedFlags(usage_for_key);
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::getKeyAllowedUsages(const std::string& key_id,
|
|
KeyAllowedUsageFlags* usage_flags) {
|
|
if (nullptr == usage_flags) {
|
|
LOGE("Missing pointer to KeyAllowedUsageFlags result.");
|
|
return kTypeError;
|
|
}
|
|
|
|
CdmKeyAllowedUsage usage_for_key;
|
|
const CdmResponseType result =
|
|
cdm_engine_->QueryKeyAllowedUsage(key_id, &usage_for_key);
|
|
if (result != NO_ERROR) {
|
|
if (result == KEY_NOT_FOUND_1 || result == KEY_NOT_FOUND_2) {
|
|
return kNoKey;
|
|
} else if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result == SESSION_LOST_STATE_ERROR) {
|
|
LOGE("Session invalidated");
|
|
return kSessionStateLost;
|
|
} else if (result == KEY_CONFLICT_1) {
|
|
return kTypeError;
|
|
} else {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
}
|
|
|
|
*usage_flags = KeyAllowedFlags(usage_for_key);
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::setAppParameter(const std::string& key,
|
|
const std::string& value) {
|
|
if (key.empty()) {
|
|
return kTypeError;
|
|
}
|
|
app_parameters_[key] = value;
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::getAppParameter(const std::string& key,
|
|
std::string* result) {
|
|
if (nullptr == result || key.empty() ||
|
|
app_parameters_.find(key) == app_parameters_.end()) {
|
|
return kTypeError;
|
|
}
|
|
*result = app_parameters_[key];
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::removeAppParameter(const std::string& key) {
|
|
if (key.empty()) {
|
|
return kTypeError;
|
|
}
|
|
CdmAppParameterMap::iterator it = app_parameters_.find(key);
|
|
if (it == app_parameters_.end()) {
|
|
return kTypeError;
|
|
}
|
|
app_parameters_.erase(it);
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::clearAppParameters() {
|
|
app_parameters_.clear();
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::close(const std::string& session_id) {
|
|
if (!cdm_engine_->IsOpenSession(session_id)) {
|
|
LOGE("No such session: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
|
|
const CdmResponseType result = cdm_engine_->CloseSession(session_id);
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result != NO_ERROR) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
sessions_.erase(session_id);
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::remove(const std::string& session_id) {
|
|
if (!cdm_engine_->IsOpenSession(session_id)) {
|
|
LOGE("No such session: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
|
|
if (!sessions_[session_id].callable) {
|
|
LOGE("Request not yet generated: %s", session_id.c_str());
|
|
return kInvalidState;
|
|
}
|
|
|
|
if (sessions_[session_id].type == kTemporary) {
|
|
LOGE("Not a persistent session: %s", session_id.c_str());
|
|
return kRangeError;
|
|
}
|
|
|
|
InitializationData empty_initialization_data;
|
|
CdmKeyRequest key_request;
|
|
|
|
// Mark all keys as released ahead of generating the release request.
|
|
// When released, cdm_engine_ will mark all keys as expired, which we will
|
|
// ignore in this interface.
|
|
KeyStatusMap& map = sessions_[session_id].key_statuses;
|
|
for (KeyStatusMap::iterator it = map.begin(); it != map.end(); ++it) {
|
|
it->second = kReleased;
|
|
}
|
|
|
|
const CdmResponseType result = cdm_engine_->GenerateKeyRequest(
|
|
session_id, session_id, empty_initialization_data, kLicenseTypeRelease,
|
|
app_parameters_, &key_request);
|
|
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result == SESSION_LOST_STATE_ERROR) {
|
|
LOGE("Session invalidated");
|
|
return kSessionStateLost;
|
|
} else if (result == LICENSE_REQUEST_NONCE_GENERATION_ERROR) {
|
|
LOGE("Nonce quota exceeded");
|
|
return kResourceContention;
|
|
} else if (result != KEY_MESSAGE) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
cdm_engine_->CloseSession(session_id);
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
LOGI("A license release has been generated.");
|
|
MessageType message_type = kLicenseRelease;
|
|
listener_->onMessage(session_id, message_type, key_request.message);
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::forceRemove(const std::string& session_id) {
|
|
if (!cdm_engine_->IsOpenSession(session_id)) {
|
|
LOGE("No such session: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
|
|
if (!sessions_[session_id].callable) {
|
|
LOGE("Request not yet generated: %s", session_id.c_str());
|
|
return kInvalidState;
|
|
}
|
|
|
|
if (sessions_[session_id].type == kTemporary) {
|
|
LOGE("Not a persistent session: %s", session_id.c_str());
|
|
return kRangeError;
|
|
}
|
|
|
|
const CdmResponseType result = cdm_engine_->RemoveLicense(session_id);
|
|
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
} else if (result == SESSION_LOST_STATE_ERROR) {
|
|
LOGE("Session invalidated");
|
|
return kSessionStateLost;
|
|
} else if (result != NO_ERROR) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
sessions_.erase(session_id);
|
|
cdm_engine_->CloseSession(session_id);
|
|
listener_->onRemoveComplete(session_id);
|
|
|
|
LOGI("A session has been forcibly deleted.");
|
|
return kSuccess;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::decrypt(const DecryptionBatch& batch) {
|
|
std::string empty_session_id;
|
|
return decrypt(empty_session_id, batch);
|
|
}
|
|
|
|
Cdm::Status CdmImpl::decrypt(const std::string& session_id,
|
|
const DecryptionBatch& batch) {
|
|
if (batch.encryption_scheme == kAesCtr &&
|
|
(batch.pattern.encrypted_blocks != 0 ||
|
|
batch.pattern.clear_blocks != 0)) {
|
|
LOGE("The 'cens' schema is not supported.");
|
|
return kNotSupported;
|
|
}
|
|
|
|
if (PropertiesCE::GetSecureOutputType() == kNoSecureOutput &&
|
|
batch.is_secure) {
|
|
LOGE("The CDM is configured without secure output support.");
|
|
return kNotSupported;
|
|
}
|
|
|
|
const CArrayRangeView<Sample> samples(batch.samples, batch.samples_length);
|
|
for (const Sample& sample : samples) {
|
|
if (sample.input.data_length >
|
|
(sample.output.data_length - sample.output.data_offset)) {
|
|
LOGE("The output buffer is too small to contain the input buffer.");
|
|
return kTypeError;
|
|
}
|
|
|
|
const CArrayRangeView<Subsample> subsamples(sample.input.subsamples,
|
|
sample.input.subsamples_length);
|
|
const bool is_encrypted =
|
|
(batch.encryption_scheme != kClear) &&
|
|
std::any_of(std::begin(subsamples), std::end(subsamples),
|
|
[](const Subsample& subsample) -> bool {
|
|
return subsample.protected_bytes > 0;
|
|
});
|
|
if (is_encrypted && sample.input.iv_length != 16) {
|
|
LOGE("The IV must be 16 bytes long.");
|
|
return kTypeError;
|
|
}
|
|
}
|
|
|
|
// With the input validated, we may copy it into a CdmDecryptionParametersV16
|
|
CdmDecryptionParametersV16 parameters;
|
|
parameters.key_id.assign(batch.key_id, batch.key_id + batch.key_id_length);
|
|
parameters.is_secure = batch.is_secure;
|
|
if (batch.encryption_scheme == kAesCtr) {
|
|
parameters.cipher_mode = kCipherModeCtr;
|
|
} else if (batch.encryption_scheme == kAesCbc) {
|
|
parameters.cipher_mode = kCipherModeCbc;
|
|
}
|
|
parameters.is_video = batch.is_video;
|
|
parameters.pattern.encrypt_blocks = batch.pattern.encrypted_blocks;
|
|
parameters.pattern.skip_blocks = batch.pattern.clear_blocks;
|
|
|
|
parameters.samples.reserve(batch.samples_length);
|
|
std::transform(
|
|
std::begin(samples), std::end(samples),
|
|
std::back_inserter(parameters.samples),
|
|
[](const Sample& sample) -> CdmDecryptionSample {
|
|
CdmDecryptionSample cdm_sample;
|
|
cdm_sample.encrypt_buffer = sample.input.data;
|
|
cdm_sample.encrypt_buffer_length = sample.input.data_length;
|
|
cdm_sample.decrypt_buffer = sample.output.data;
|
|
cdm_sample.decrypt_buffer_size = sample.output.data_length;
|
|
cdm_sample.decrypt_buffer_offset = sample.output.data_offset;
|
|
cdm_sample.iv.assign(sample.input.iv,
|
|
sample.input.iv + sample.input.iv_length);
|
|
|
|
const CArrayRangeView<Subsample> subsamples(
|
|
sample.input.subsamples, sample.input.subsamples_length);
|
|
cdm_sample.subsamples.reserve(sample.input.subsamples_length);
|
|
std::transform(
|
|
std::begin(subsamples), std::end(subsamples),
|
|
std::back_inserter(cdm_sample.subsamples),
|
|
[](const Subsample& subsample) -> CdmDecryptionSubsample {
|
|
return CdmDecryptionSubsample(subsample.clear_bytes,
|
|
subsample.protected_bytes);
|
|
});
|
|
|
|
return cdm_sample;
|
|
});
|
|
|
|
const CdmResponseType result =
|
|
cdm_engine_->DecryptV16(session_id, parameters);
|
|
|
|
if (result == NO_ERROR) {
|
|
return kSuccess;
|
|
}
|
|
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
}
|
|
|
|
if (result == SESSION_LOST_STATE_ERROR) {
|
|
LOGE("Session invalidated");
|
|
return kSessionStateLost;
|
|
}
|
|
|
|
if (result == OUTPUT_TOO_LARGE_ERROR) {
|
|
LOGE("Output too large");
|
|
return kOutputTooLarge;
|
|
}
|
|
|
|
if (result == NEED_KEY || result == KEY_NOT_FOUND_3 ||
|
|
result == SESSION_NOT_FOUND_FOR_DECRYPT) {
|
|
LOGE("Key not available.");
|
|
return kNoKey;
|
|
}
|
|
|
|
if (result == INSUFFICIENT_OUTPUT_PROTECTION) {
|
|
LOGE("Key usage blocked due to HDCP or display resolution constraints.");
|
|
return kKeyUsageBlockedByPolicy;
|
|
}
|
|
|
|
LOGE("Decrypt error: %d", static_cast<int>(result));
|
|
return kDecryptError;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::genericEncrypt(const std::string& session_id,
|
|
const std::string& in_buffer,
|
|
const std::string& key_id,
|
|
const std::string& iv,
|
|
GenericEncryptionAlgorithmType algorithm,
|
|
std::string* out_buffer) {
|
|
const CdmEncryptionAlgorithm cdm_algorithm =
|
|
ConvertEncryptionAlgorithm(algorithm);
|
|
if (cdm_algorithm == wvcdm::kEncryptionAlgorithmUnknown) {
|
|
LOGE("Unrecognized encryption algorithm: %d.",
|
|
static_cast<int>(cdm_algorithm));
|
|
return kNotSupported;
|
|
}
|
|
|
|
CdmResponseType result = cdm_engine_->GenericEncrypt(
|
|
session_id, in_buffer, key_id, iv, cdm_algorithm, out_buffer);
|
|
if (result == NO_ERROR) {
|
|
return kSuccess;
|
|
}
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
}
|
|
if (result == SESSION_LOST_STATE_ERROR) {
|
|
LOGE("Session invalidated");
|
|
return kSessionStateLost;
|
|
}
|
|
if (result == OUTPUT_TOO_LARGE_ERROR) {
|
|
LOGE("Output too large");
|
|
return kOutputTooLarge;
|
|
}
|
|
if (result == SESSION_NOT_FOUND_13) {
|
|
LOGE("No such session: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
if (result == KEY_NOT_FOUND_3 || result == NEED_KEY) {
|
|
LOGE("Key Error: %s", session_id.c_str());
|
|
return kNoKey;
|
|
}
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::genericDecrypt(const std::string& session_id,
|
|
const std::string& in_buffer,
|
|
const std::string& key_id,
|
|
const std::string& iv,
|
|
GenericEncryptionAlgorithmType algorithm,
|
|
std::string* out_buffer) {
|
|
const CdmEncryptionAlgorithm cdm_algorithm =
|
|
ConvertEncryptionAlgorithm(algorithm);
|
|
if (cdm_algorithm == wvcdm::kEncryptionAlgorithmUnknown) {
|
|
LOGE("Unrecognized encryption algorithm: %d.",
|
|
static_cast<int>(cdm_algorithm));
|
|
return kNotSupported;
|
|
}
|
|
|
|
const CdmResponseType result = cdm_engine_->GenericDecrypt(
|
|
session_id, in_buffer, key_id, iv, cdm_algorithm, out_buffer);
|
|
if (result == NO_ERROR) {
|
|
return kSuccess;
|
|
}
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
}
|
|
if (result == SESSION_LOST_STATE_ERROR) {
|
|
LOGE("Session invalidated");
|
|
return kSessionStateLost;
|
|
}
|
|
if (result == OUTPUT_TOO_LARGE_ERROR) {
|
|
LOGE("Output too large");
|
|
return kOutputTooLarge;
|
|
}
|
|
if (result == SESSION_NOT_FOUND_14) {
|
|
LOGE("No such session: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
if (result == KEY_NOT_FOUND_4 || result == NEED_KEY) {
|
|
LOGE("Key Error: %s", session_id.c_str());
|
|
return kNoKey;
|
|
}
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::genericSign(const std::string& session_id,
|
|
const std::string& message,
|
|
const std::string& key_id,
|
|
GenericSigningAlgorithmType algorithm,
|
|
std::string* signature) {
|
|
const CdmSigningAlgorithm cdm_algorithm = ConvertSigningAlgorithm(algorithm);
|
|
if (cdm_algorithm == wvcdm::kSigningAlgorithmUnknown) {
|
|
LOGE("Unrecognized signing algorithm: %d", static_cast<int>(cdm_algorithm));
|
|
return kNotSupported;
|
|
}
|
|
|
|
const CdmResponseType result = cdm_engine_->GenericSign(
|
|
session_id, message, key_id, cdm_algorithm, signature);
|
|
if (result == NO_ERROR) {
|
|
return kSuccess;
|
|
}
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
}
|
|
if (result == SESSION_LOST_STATE_ERROR) {
|
|
LOGE("Session invalidated");
|
|
return kSessionStateLost;
|
|
}
|
|
if (result == SESSION_NOT_FOUND_15) {
|
|
LOGE("No such session: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
if (result == KEY_NOT_FOUND_5 || result == NEED_KEY) {
|
|
LOGE("Key Error: %s", session_id.c_str());
|
|
return kNoKey;
|
|
}
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::genericVerify(const std::string& session_id,
|
|
const std::string& message,
|
|
const std::string& key_id,
|
|
GenericSigningAlgorithmType algorithm,
|
|
const std::string& signature) {
|
|
const CdmSigningAlgorithm cdm_algorithm = ConvertSigningAlgorithm(algorithm);
|
|
if (cdm_algorithm == wvcdm::kSigningAlgorithmUnknown) {
|
|
LOGE("Unrecognized signing algorithm: %d", static_cast<int>(cdm_algorithm));
|
|
return kNotSupported;
|
|
}
|
|
|
|
const CdmResponseType result = cdm_engine_->GenericVerify(
|
|
session_id, message, key_id, cdm_algorithm, signature);
|
|
if (result == NO_ERROR) {
|
|
return kSuccess;
|
|
}
|
|
if (result == SYSTEM_INVALIDATED_ERROR) {
|
|
LOGE("System invalidated");
|
|
return kSystemStateLost;
|
|
}
|
|
if (result == SESSION_LOST_STATE_ERROR) {
|
|
LOGE("Session invalidated");
|
|
return kSessionStateLost;
|
|
}
|
|
if (result == SESSION_NOT_FOUND_16) {
|
|
LOGE("No such session: %s", session_id.c_str());
|
|
return kSessionNotFound;
|
|
}
|
|
if (result == KEY_NOT_FOUND_6 || result == NEED_KEY) {
|
|
LOGE("Key Error: %s", session_id.c_str());
|
|
return kNoKey;
|
|
}
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return kUnexpectedError;
|
|
}
|
|
|
|
Cdm::Status CdmImpl::setVideoResolution(const std::string& session_id,
|
|
uint32_t width, uint32_t height) {
|
|
constexpr uint64_t kMaxUint32 = std::numeric_limits<uint32_t>::max();
|
|
// Verify that width * height will fit into a 32-bit quantity.
|
|
// This is done to be compatible with the video resolution in the
|
|
// license policy settings.
|
|
const uint64_t pixels =
|
|
static_cast<uint64_t>(width) * static_cast<uint64_t>(height);
|
|
if (pixels > kMaxUint32) return kRangeError;
|
|
if (cdm_engine_->NotifyResolution(session_id, width, height)) {
|
|
return kSuccess;
|
|
} else {
|
|
return kSessionNotFound;
|
|
}
|
|
}
|
|
|
|
Cdm::Status CdmImpl::getMetrics(std::string* serialized_metrics) {
|
|
if (serialized_metrics == nullptr) {
|
|
LOGE("Missing string parameter to receive serialized metrics.");
|
|
return kTypeError;
|
|
}
|
|
|
|
drm_metrics::WvCdmMetrics metrics;
|
|
if (!cdm_engine_->GetMetricsSnapshot(&metrics) ||
|
|
!metrics.SerializeToString(serialized_metrics)) {
|
|
return kUnexpectedError;
|
|
}
|
|
return kSuccess;
|
|
}
|
|
|
|
void CdmImpl::onTimerExpired(void* context) {
|
|
if (context == kPolicyTimerContext) {
|
|
if (policy_timer_enabled_) {
|
|
cdm_engine_->OnTimerEvent();
|
|
host.timer->setTimeout(kPolicyTimerDurationMilliseconds, this,
|
|
kPolicyTimerContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CdmImpl::OnSessionRenewalNeeded(const CdmSessionId& session_id) {
|
|
CdmKeyRequest key_request;
|
|
const CdmResponseType result =
|
|
cdm_engine_->GenerateRenewalRequest(session_id, &key_request);
|
|
if (result == LICENSE_RENEWAL_NONCE_GENERATION_ERROR) {
|
|
// TODO(b/73606893): this error should be recoverable. Rather
|
|
// than just giving up here, we should reset the event timer for
|
|
// a second later and try again.
|
|
LOGE("Nonce quota exceeded");
|
|
return;
|
|
}
|
|
if (result != KEY_MESSAGE) {
|
|
LOGE("Unexpected error %d", static_cast<int>(result));
|
|
return;
|
|
}
|
|
|
|
LOGI("A license renewal has been generated.");
|
|
MessageType message_type = kLicenseRenewal;
|
|
listener_->onMessage(session_id, message_type, key_request.message);
|
|
}
|
|
|
|
void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id,
|
|
const CdmKeyStatusMap& keys_status,
|
|
bool has_new_usable_key) {
|
|
KeyStatusMap& map = sessions_[session_id].key_statuses;
|
|
|
|
CdmKeyStatusMap::const_iterator it;
|
|
for (it = keys_status.begin(); it != keys_status.end(); ++it) {
|
|
switch (it->second) {
|
|
case kKeyStatusUsable:
|
|
map[it->first] = kUsable;
|
|
continue;
|
|
case kKeyStatusExpired: {
|
|
KeyStatusMap::const_iterator it_old = map.find(it->first);
|
|
if (it_old != map.end() && it_old->second == kReleased) {
|
|
// This key has already been marked as "released".
|
|
// Ignore the internal "expired" status.
|
|
} else {
|
|
map[it->first] = kExpired;
|
|
}
|
|
continue;
|
|
}
|
|
case kKeyStatusOutputNotAllowed:
|
|
map[it->first] = kOutputRestricted;
|
|
continue;
|
|
case kKeyStatusUsableInFuture:
|
|
case kKeyStatusPending:
|
|
map[it->first] = kStatusPending;
|
|
continue;
|
|
case kKeyStatusKeyUnknown:
|
|
case kKeyStatusInternalError:
|
|
map[it->first] = kInternalError;
|
|
continue;
|
|
}
|
|
|
|
// If we get this far, the switch statement fell through, which is an error.
|
|
// We do this instead of having a default case in the switch so that the
|
|
// compiler will flag if someone fails to add a case for new enum members,
|
|
// while still handling the possibility of an invalid value sneaking into
|
|
// the map at runtime.
|
|
LOGE("Internal error: Invalid key status: %d",
|
|
static_cast<int>(it->second));
|
|
map[it->first] = kInternalError;
|
|
}
|
|
|
|
listener_->onKeyStatusesChange(session_id, has_new_usable_key);
|
|
}
|
|
|
|
void CdmImpl::OnExpirationUpdate(const CdmSessionId& session_id,
|
|
int64_t new_expiry_time_seconds) {
|
|
// "Never expires" in core is NEVER_EXPIRES. In the CDM API, it's -1.
|
|
if (new_expiry_time_seconds == NEVER_EXPIRES) {
|
|
sessions_[session_id].expiration = -1;
|
|
} else {
|
|
sessions_[session_id].expiration = new_expiry_time_seconds * 1000;
|
|
}
|
|
}
|
|
|
|
Cdm::KeyAllowedUsageFlags CdmImpl::KeyAllowedFlags(
|
|
const CdmKeyAllowedUsage& usages) {
|
|
KeyAllowedUsageFlags flags = kAllowNone;
|
|
flags |= (usages.decrypt_to_clear_buffer) ? kAllowDecryptToClearBuffer : 0;
|
|
flags |= (usages.decrypt_to_secure_buffer) ? kAllowDecryptToSecureBuffer : 0;
|
|
flags |= (usages.generic_encrypt) ? kAllowGenericEncrypt : 0;
|
|
flags |= (usages.generic_decrypt) ? kAllowGenericDecrypt : 0;
|
|
flags |= (usages.generic_sign) ? kAllowGenericSign : 0;
|
|
flags |= (usages.generic_verify) ? kAllowGenericSignatureVerify : 0;
|
|
return flags;
|
|
}
|
|
|
|
CdmEncryptionAlgorithm CdmImpl::ConvertEncryptionAlgorithm(
|
|
GenericEncryptionAlgorithmType algorithm) {
|
|
if (algorithm == Cdm::kEncryptionAlgorithmAesCbc128) {
|
|
return wvcdm::kEncryptionAlgorithmAesCbc128;
|
|
} else {
|
|
LOGW("Unknown encryption algorithm: %d", static_cast<int>(algorithm));
|
|
return wvcdm::kEncryptionAlgorithmUnknown;
|
|
}
|
|
}
|
|
|
|
CdmSigningAlgorithm CdmImpl::ConvertSigningAlgorithm(
|
|
GenericSigningAlgorithmType algorithm) {
|
|
if (algorithm == Cdm::kSigningAlgorithmHmacSha256) {
|
|
return wvcdm::kSigningAlgorithmHmacSha256;
|
|
} else {
|
|
LOGW("Unknown signing algorithm: %d", static_cast<int>(algorithm));
|
|
return wvcdm::kSigningAlgorithmUnknown;
|
|
}
|
|
}
|
|
|
|
Cdm::Status CdmImpl::ConvertHdcpLevel(const std::string& query_value,
|
|
Cdm::HdcpVersion* result) {
|
|
if (query_value == QUERY_VALUE_HDCP_V1) {
|
|
*result = kHdcp1_x;
|
|
} else if (query_value == QUERY_VALUE_HDCP_V2_0) {
|
|
*result = kHdcp2_0;
|
|
} else if (query_value == QUERY_VALUE_HDCP_V2_1) {
|
|
*result = kHdcp2_1;
|
|
} else if (query_value == QUERY_VALUE_HDCP_V2_2) {
|
|
*result = kHdcp2_2;
|
|
} else if (query_value == QUERY_VALUE_HDCP_V2_3) {
|
|
*result = kHdcp2_3;
|
|
} else {
|
|
return kUnexpectedError;
|
|
}
|
|
return kSuccess;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// static
|
|
Cdm::Status Cdm::initialize(SecureOutputType secure_output_type,
|
|
const ClientInfo& client_info, IStorage* storage,
|
|
IClock* clock, ITimer* timer, LogLevel verbosity) {
|
|
return initialize(secure_output_type, client_info, storage, clock, timer,
|
|
verbosity, kNoSandboxId);
|
|
}
|
|
|
|
// static
|
|
Cdm::Status Cdm::initialize(SecureOutputType secure_output_type,
|
|
const ClientInfo& client_info, IStorage* storage,
|
|
IClock* clock, ITimer* timer, LogLevel verbosity,
|
|
const std::string& sandbox_id) {
|
|
// Specify the maximum severity of message that will be output to
|
|
// the console. See core/include/log.h for the valid priority values.
|
|
g_cutoff = static_cast<LogPriority>(verbosity);
|
|
|
|
switch (secure_output_type) {
|
|
case kOpaqueHandle:
|
|
case kDirectRender:
|
|
case kNoSecureOutput:
|
|
break;
|
|
default:
|
|
LOGE("Invalid output type!");
|
|
return kTypeError;
|
|
}
|
|
|
|
if (client_info.product_name.empty() || client_info.company_name.empty() ||
|
|
client_info.model_name.empty()) {
|
|
LOGE("Client info requires product_name, company_name, model_name!");
|
|
return kTypeError;
|
|
}
|
|
|
|
if (!storage || !clock || !timer) {
|
|
LOGE("All interfaces are required!");
|
|
return kTypeError;
|
|
}
|
|
|
|
PropertiesCE::SetSecureOutputType(secure_output_type);
|
|
PropertiesCE::SetClientInfo(client_info);
|
|
if (sandbox_id != kNoSandboxId) PropertiesCE::SetSandboxId(sandbox_id);
|
|
Properties::Init();
|
|
host.storage = storage;
|
|
host.clock = clock;
|
|
host.timer = timer;
|
|
host.initialized = true;
|
|
return kSuccess;
|
|
}
|
|
|
|
// static
|
|
const char* Cdm::version() { return CDM_VERSION; }
|
|
|
|
// static
|
|
Cdm* Cdm::create(IEventListener* listener, IStorage* storage,
|
|
bool privacy_mode) {
|
|
return create(listener, storage, privacy_mode,
|
|
false /* storage_is_read_only */);
|
|
}
|
|
|
|
// static
|
|
Cdm* Cdm::create(IEventListener* listener, IStorage* storage, bool privacy_mode,
|
|
bool storage_is_read_only) {
|
|
if (!host.initialized) {
|
|
LOGE("Not initialized!");
|
|
return nullptr;
|
|
}
|
|
if (!listener) {
|
|
LOGE("No listener!");
|
|
return nullptr;
|
|
}
|
|
if (!storage) {
|
|
LOGE("No storage!");
|
|
return nullptr;
|
|
}
|
|
|
|
if (storage_is_read_only) {
|
|
ReadOnlyStorage* read_only_storage = new ReadOnlyStorage(storage);
|
|
return new CdmImpl(listener, read_only_storage, privacy_mode,
|
|
read_only_storage);
|
|
} else {
|
|
return new CdmImpl(listener, storage, privacy_mode, nullptr);
|
|
}
|
|
}
|
|
|
|
} // namespace widevine
|
|
|
|
// Missing symbols from core:
|
|
namespace wvcdm {
|
|
|
|
using namespace widevine;
|
|
|
|
int64_t Clock::GetCurrentTime() { return host.clock->now() / 1000; }
|
|
|
|
class FileImpl final : public File {
|
|
public:
|
|
FileImpl(Cdm::IStorage* storage, const std::string& path, int flags)
|
|
: storage_(storage),
|
|
name_(path),
|
|
read_only_(flags & FileSystem::kReadOnly),
|
|
truncate_(flags & FileSystem::kTruncate) {
|
|
assert(storage_);
|
|
}
|
|
|
|
ssize_t Read(char* buffer, size_t bytes) override {
|
|
if (!buffer) {
|
|
LOGW("File::Read: buffer is empty");
|
|
return -1;
|
|
}
|
|
std::string data;
|
|
if (!storage_->read(name_, &data)) {
|
|
return -1;
|
|
}
|
|
|
|
size_t to_copy = std::min(bytes, data.size());
|
|
memcpy(buffer, data.data(), to_copy);
|
|
return to_copy;
|
|
}
|
|
|
|
ssize_t Write(const char* buffer, size_t bytes) override {
|
|
if (!buffer) {
|
|
LOGW("File::Write: buffer is empty");
|
|
return -1;
|
|
}
|
|
if (read_only_) {
|
|
LOGE("File::Write: file is read-only.");
|
|
return -1;
|
|
}
|
|
if (!truncate_) {
|
|
LOGE("File::Write: files cannot be appended to.");
|
|
return -1;
|
|
}
|
|
std::string data(buffer, bytes);
|
|
if (!storage_->write(name_, data)) {
|
|
return -1;
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
private:
|
|
Cdm::IStorage* const storage_;
|
|
const std::string name_;
|
|
const bool read_only_;
|
|
const bool truncate_;
|
|
};
|
|
|
|
class FileSystem::Impl {
|
|
public:
|
|
Impl(widevine::Cdm::IStorage* storage) : storage_(storage) {
|
|
assert(storage);
|
|
}
|
|
|
|
widevine::Cdm::IStorage* const storage_;
|
|
};
|
|
|
|
FileSystem::FileSystem() : impl_(new Impl(host.storage)) {}
|
|
|
|
FileSystem::FileSystem(const std::string& origin, void* extra_data)
|
|
: impl_(new Impl(reinterpret_cast<widevine::Cdm::IStorage*>(extra_data))),
|
|
origin_(origin) {}
|
|
|
|
FileSystem::~FileSystem() {}
|
|
|
|
std::unique_ptr<File> FileSystem::Open(const std::string& file_path,
|
|
int flags) {
|
|
if (!(flags & kCreate) && !impl_->storage_->exists(file_path)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return std::unique_ptr<File>(new FileImpl(impl_->storage_, file_path, flags));
|
|
}
|
|
|
|
bool FileSystem::Exists(const std::string& file_path) {
|
|
return !file_path.empty() && impl_->storage_->exists(file_path);
|
|
}
|
|
|
|
bool FileSystem::Remove(const std::string& file_path) {
|
|
return impl_->storage_->remove(file_path);
|
|
}
|
|
|
|
ssize_t FileSystem::FileSize(const std::string& file_path) {
|
|
return impl_->storage_->size(file_path);
|
|
}
|
|
|
|
bool FileSystem::List(const std::string&,
|
|
std::vector<std::string>* file_names) {
|
|
return file_names && impl_->storage_->list(file_names);
|
|
}
|
|
|
|
} // namespace wvcdm
|