Source release v3.0.4
This commit is contained in:
170
cdm/src/cdm.cpp
170
cdm/src/cdm.cpp
@@ -2,6 +2,7 @@
|
||||
#include "cdm.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h> // LLONG_MAX
|
||||
#include <string.h> // memcpy
|
||||
|
||||
#include <vector>
|
||||
@@ -11,6 +12,7 @@
|
||||
#include "cdm_engine.h"
|
||||
#include "clock.h"
|
||||
#include "crypto_session.h"
|
||||
#include "device_files.h"
|
||||
#include "file_store.h"
|
||||
#include "license.h"
|
||||
#include "log.h"
|
||||
@@ -171,9 +173,18 @@ class CdmImpl : public Cdm,
|
||||
PropertySet property_set_;
|
||||
CdmAppParameterMap app_parameters_;
|
||||
|
||||
std::map<std::string, SessionType> new_session_types_;
|
||||
std::map<std::string, int64_t> session_expirations_;
|
||||
std::map<std::string, KeyStatusMap> session_key_statuses_;
|
||||
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_;
|
||||
};
|
||||
|
||||
CdmImpl::CdmImpl(IEventListener* listener,
|
||||
@@ -195,12 +206,12 @@ Cdm::Status CdmImpl::setServerCertificate(const std::string& certificate) {
|
||||
|
||||
if (certificate.empty()) {
|
||||
LOGE("An empty server certificate is invalid.");
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
|
||||
if (CdmLicense::VerifySignedServiceCertificate(certificate) != NO_ERROR) {
|
||||
LOGE("Invalid server certificate!");
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
|
||||
property_set_.set_service_certificate(certificate);
|
||||
@@ -211,7 +222,7 @@ Cdm::Status CdmImpl::createSession(SessionType session_type,
|
||||
std::string* session_id) {
|
||||
if (!session_id) {
|
||||
LOGE("Missing session ID pointer.");
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
// Important! The caller may pass a pre-filled string, which must be cleared
|
||||
// before being given to CdmEngine.
|
||||
@@ -219,7 +230,8 @@ Cdm::Status CdmImpl::createSession(SessionType session_type,
|
||||
|
||||
switch (session_type) {
|
||||
case kTemporary:
|
||||
case kPersistent:
|
||||
case kPersistentLicense:
|
||||
case kPersistentUsageRecord:
|
||||
break;
|
||||
default:
|
||||
LOGE("Unsupported session type: %d", session_type);
|
||||
@@ -232,7 +244,7 @@ Cdm::Status CdmImpl::createSession(SessionType session_type,
|
||||
NULL, session_id);
|
||||
switch (result) {
|
||||
case NO_ERROR:
|
||||
new_session_types_[*session_id] = session_type;
|
||||
sessions_[*session_id].type = session_type;
|
||||
return kSuccess;
|
||||
case NEED_PROVISIONING:
|
||||
LOGE("A device certificate is needed.");
|
||||
@@ -251,22 +263,23 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id,
|
||||
return kSessionNotFound;
|
||||
}
|
||||
|
||||
std::map<std::string, SessionType>::iterator it =
|
||||
new_session_types_.find(session_id);
|
||||
if (it == new_session_types_.end()) {
|
||||
if (sessions_[session_id].callable) {
|
||||
LOGE("Request already generated: %s", session_id.c_str());
|
||||
return kInvalidState;
|
||||
}
|
||||
|
||||
SessionType session_type = it->second;
|
||||
SessionType session_type = sessions_[session_id].type;
|
||||
CdmLicenseType license_type;
|
||||
switch (session_type) {
|
||||
case kTemporary:
|
||||
license_type = kLicenseTypeStreaming;
|
||||
license_type = kLicenseTypeTemporary;
|
||||
break;
|
||||
case kPersistent:
|
||||
case kPersistentLicense:
|
||||
license_type = kLicenseTypeOffline;
|
||||
break;
|
||||
case kPersistentUsageRecord:
|
||||
license_type = kLicenseTypeStreaming;
|
||||
break;
|
||||
default:
|
||||
LOGE("Unexpected session type: %d", session_type);
|
||||
return kUnexpectedError;
|
||||
@@ -285,12 +298,12 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id,
|
||||
break;
|
||||
default:
|
||||
LOGE("Invalid init data type: %d", init_data_type);
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
|
||||
if (init_data.empty()) {
|
||||
LOGE("Empty init data is not valid.");
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
|
||||
InitializationData init_data_obj(init_data_type_name, init_data);
|
||||
@@ -316,26 +329,27 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id,
|
||||
return kUnexpectedError;
|
||||
}
|
||||
|
||||
new_session_types_.erase(it);
|
||||
sessions_[session_id].callable = true;
|
||||
assert(key_request_type == kKeyRequestTypeInitial);
|
||||
MessageType message_type = kLicenseRequest;
|
||||
if (property_set_.use_privacy_mode() &&
|
||||
property_set_.service_certificate().empty()) {
|
||||
// We can deduce that this is a server cert request, even though CdmEgine
|
||||
// cannot currently inform us of this.
|
||||
message_type = kIndividualizationRequest;
|
||||
// Previously, we used message type kIndividiualizationRequest for this.
|
||||
// The EME editor has clarified that this was a misinterpretation of the
|
||||
// spec, and that this should also be kLicenseRequest.
|
||||
LOGI("A server certificate request has been generated.");
|
||||
} else {
|
||||
LOGI("A license request has been generated.");
|
||||
}
|
||||
listener_->onMessage(session_id, message_type, key_request);
|
||||
listener_->onMessage(session_id, kLicenseRequest, key_request);
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Cdm::Status CdmImpl::load(const std::string& session_id) {
|
||||
if (session_id.empty()) {
|
||||
LOGE("Empty session ID.");
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
|
||||
if (cdm_engine_.IsOpenSession(session_id)) {
|
||||
@@ -359,11 +373,37 @@ Cdm::Status CdmImpl::load(const std::string& session_id) {
|
||||
return kUnexpectedError;
|
||||
}
|
||||
|
||||
DeviceFiles f;
|
||||
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 release_message;
|
||||
result = cdm_engine_.LoadUsageSession(session_id, &release_message);
|
||||
if (result == LOAD_USAGE_INFO_MISSING) {
|
||||
LOGE("Unable to load license: %s", session_id.c_str());
|
||||
cdm_engine_.CloseSession(session_id);
|
||||
return kSessionNotFound;
|
||||
} else if (result != KEY_MESSAGE) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
cdm_engine_.CloseSession(session_id);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
|
||||
LOGI("A usage record release has been generated.");
|
||||
MessageType message_type = kLicenseRelease;
|
||||
listener_->onMessage(session_id, message_type, release_message);
|
||||
|
||||
sessions_[session_id].type = kPersistentUsageRecord;
|
||||
sessions_[session_id].callable = true;
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
result = cdm_engine_.RestoreKey(session_id, session_id);
|
||||
if (result == GET_LICENSE_ERROR) {
|
||||
LOGE("Unable to load license: %s", session_id.c_str());
|
||||
return kSessionNotFound;
|
||||
} else if (result == GET_RELEASED_LICENSE_ERROR) {
|
||||
if (result == GET_RELEASED_LICENSE_ERROR) {
|
||||
// This was partially removed already.
|
||||
// The EME spec states that we should send a release message right away.
|
||||
InitializationData empty_initialization_data;
|
||||
@@ -388,6 +428,8 @@ Cdm::Status CdmImpl::load(const std::string& session_id) {
|
||||
return kUnexpectedError;
|
||||
}
|
||||
|
||||
sessions_[session_id].type = kPersistentLicense;
|
||||
sessions_[session_id].callable = true;
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
@@ -398,14 +440,14 @@ Cdm::Status CdmImpl::update(const std::string& session_id,
|
||||
return kSessionNotFound;
|
||||
}
|
||||
|
||||
if (new_session_types_.find(session_id) != new_session_types_.end()) {
|
||||
if (!sessions_[session_id].callable) {
|
||||
LOGE("Request not yet generated: %s", session_id.c_str());
|
||||
return kInvalidState;
|
||||
}
|
||||
|
||||
if (response.empty()) {
|
||||
LOGE("Empty response.");
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
|
||||
// NOTE: If the CdmSession object recognizes that this is not the first
|
||||
@@ -444,6 +486,12 @@ Cdm::Status CdmImpl::update(const std::string& session_id,
|
||||
MessageType message_type = kLicenseRequest;
|
||||
listener_->onMessage(session_id, message_type, key_request);
|
||||
return kSuccess;
|
||||
} 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 != KEY_ADDED) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
return kUnexpectedError;
|
||||
@@ -457,6 +505,7 @@ Cdm::Status CdmImpl::update(const std::string& session_id,
|
||||
}
|
||||
|
||||
if (cdm_engine_.IsReleaseSession(session_id)) {
|
||||
sessions_.erase(session_id);
|
||||
cdm_engine_.CloseSession(session_id);
|
||||
listener_->onRemoveComplete(session_id);
|
||||
}
|
||||
@@ -470,7 +519,7 @@ Cdm::Status CdmImpl::getExpiration(const std::string& session_id,
|
||||
return kSessionNotFound;
|
||||
}
|
||||
|
||||
*expiration = session_expirations_[session_id];
|
||||
*expiration = sessions_[session_id].expiration;
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
@@ -481,14 +530,14 @@ Cdm::Status CdmImpl::getKeyStatuses(const std::string& session_id,
|
||||
return kSessionNotFound;
|
||||
}
|
||||
|
||||
*key_statuses = session_key_statuses_[session_id];
|
||||
*key_statuses = sessions_[session_id].key_statuses;
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Cdm::Status CdmImpl::setAppParameter(const std::string& key,
|
||||
const std::string& value) {
|
||||
if (key.empty()) {
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
app_parameters_[key] = value;
|
||||
return kSuccess;
|
||||
@@ -498,7 +547,7 @@ Cdm::Status CdmImpl::getAppParameter(const std::string& key,
|
||||
std::string* result) {
|
||||
if (NULL == result || key.empty() ||
|
||||
app_parameters_.find(key) == app_parameters_.end()) {
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
*result = app_parameters_[key];
|
||||
return kSuccess;
|
||||
@@ -506,11 +555,11 @@ Cdm::Status CdmImpl::getAppParameter(const std::string& key,
|
||||
|
||||
Cdm::Status CdmImpl::removeAppParameter(const std::string& key) {
|
||||
if (key.empty()) {
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
CdmAppParameterMap::iterator it = app_parameters_.find(key);
|
||||
if (it == app_parameters_.end()) {
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
app_parameters_.erase(it);
|
||||
return kSuccess;
|
||||
@@ -532,6 +581,7 @@ Cdm::Status CdmImpl::close(const std::string& session_id) {
|
||||
LOGE("Unexpected error %d", result);
|
||||
return kUnexpectedError;
|
||||
}
|
||||
sessions_.erase(session_id);
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
@@ -541,20 +591,28 @@ Cdm::Status CdmImpl::remove(const std::string& session_id) {
|
||||
return kSessionNotFound;
|
||||
}
|
||||
|
||||
if (new_session_types_.find(session_id) != new_session_types_.end()) {
|
||||
if (!sessions_[session_id].callable) {
|
||||
LOGE("Request not yet generated: %s", session_id.c_str());
|
||||
return kInvalidState;
|
||||
}
|
||||
|
||||
if (!cdm_engine_.IsOfflineSession(session_id)) {
|
||||
if (sessions_[session_id].type == kTemporary) {
|
||||
LOGE("Not a persistent session: %s", session_id.c_str());
|
||||
return kInvalidAccess;
|
||||
return kRangeError;
|
||||
}
|
||||
|
||||
InitializationData empty_initialization_data;
|
||||
CdmKeyMessage key_request;
|
||||
std::string ignored_server_url;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
|
||||
session_id, session_id, empty_initialization_data,
|
||||
kLicenseTypeRelease, app_parameters_, &key_request, NULL,
|
||||
@@ -575,7 +633,7 @@ Cdm::Status CdmImpl::decrypt(const InputBuffer& input,
|
||||
const OutputBuffer& output) {
|
||||
if (input.is_encrypted && input.iv_length != 16) {
|
||||
LOGE("The IV must be 16 bytes long.");
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
if (PropertiesCE::GetSecureOutputType() == kNoSecureOutput &&
|
||||
output.is_secure) {
|
||||
@@ -651,7 +709,7 @@ void CdmImpl::OnSessionRenewalNeeded(const CdmSessionId& session_id) {
|
||||
void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id,
|
||||
const CdmKeyStatusMap& keys_status,
|
||||
bool has_new_usable_key) {
|
||||
KeyStatusMap& map = session_key_statuses_[session_id];
|
||||
KeyStatusMap& map = sessions_[session_id].key_statuses;
|
||||
|
||||
CdmKeyStatusMap::const_iterator it;
|
||||
for (it = keys_status.begin(); it != keys_status.end(); ++it) {
|
||||
@@ -659,11 +717,18 @@ void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id,
|
||||
case kKeyStatusUsable:
|
||||
map[it->first] = kUsable;
|
||||
break;
|
||||
case kKeyStatusExpired:
|
||||
map[it->first] = kExpired;
|
||||
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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kKeyStatusOutputNotAllowed:
|
||||
map[it->first] = kOutputNotAllowed;
|
||||
map[it->first] = kOutputRestricted;
|
||||
break;
|
||||
case kKeyStatusPending:
|
||||
map[it->first] = kStatusPending;
|
||||
@@ -683,7 +748,12 @@ void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id,
|
||||
|
||||
void CdmImpl::OnExpirationUpdate(const CdmSessionId& session_id,
|
||||
int64_t new_expiry_time_seconds) {
|
||||
session_expirations_[session_id] = new_expiry_time_seconds * 1000;
|
||||
// "Never expires" in core is LLONG_MAX. In the CDM API, it's -1.
|
||||
if (new_expiry_time_seconds == LLONG_MAX) {
|
||||
sessions_[session_id].expiration = -1;
|
||||
} else {
|
||||
sessions_[session_id].expiration = new_expiry_time_seconds * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -728,24 +798,24 @@ Cdm::Status Cdm::initialize(
|
||||
break;
|
||||
default:
|
||||
LOGE("Invalid output type!");
|
||||
return kInvalidAccess;
|
||||
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 kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
|
||||
if (!storage || !clock || !timer) {
|
||||
LOGE("All interfaces are required!");
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
|
||||
if (!device_certificate_request) {
|
||||
LOGE("Device certificate request pointer is required!");
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
|
||||
// Our enum values match those in core/include/log.h
|
||||
@@ -811,7 +881,7 @@ Cdm::Status Cdm::DeviceCertificateRequest::acceptReply(
|
||||
const std::string& reply) {
|
||||
if (!host.provisioning_engine) {
|
||||
LOGE("Provisioning reply received while not in a provisioning state!");
|
||||
return kInvalidAccess;
|
||||
return kTypeError;
|
||||
}
|
||||
|
||||
std::string empty_origin;
|
||||
@@ -901,7 +971,11 @@ void File::Close() {
|
||||
}
|
||||
|
||||
bool File::Exists(const std::string& file_path) {
|
||||
return host.storage->exists(file_path);
|
||||
// An empty path is the "base directory" for CE CDM's file storage.
|
||||
// Therefore, it should always be seen as existing.
|
||||
// If it ever does not exist, CdmEngine detects this as a "factory reset"
|
||||
// and wipes out all usage table data.
|
||||
return file_path.empty() || host.storage->exists(file_path);
|
||||
}
|
||||
|
||||
bool File::Remove(const std::string& file_path) {
|
||||
|
||||
Reference in New Issue
Block a user