blob: 5ecba27e9cbabe567e24af5855fcdc9a63b61c47 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "webcontentdecryptionmodulesession_impl.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "media/base/cdm_key_information.h"
#include "media/base/cdm_promise.h"
#include "media/base/key_systems.h"
#include "media/base/limits.h"
#include "media/base/media_keys.h"
#include "media/blink/cdm_result_promise.h"
#include "media/blink/cdm_session_adapter.h"
#include "media/blink/new_session_cdm_result_promise.h"
#include "media/blink/webmediaplayer_util.h"
#include "media/cdm/cenc_utils.h"
#include "media/cdm/json_web_key.h"
#include "third_party/WebKit/public/platform/WebData.h"
#include "third_party/WebKit/public/platform/WebEncryptedMediaKeyInformation.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "third_party/WebKit/public/platform/WebURL.h"
#include "third_party/WebKit/public/platform/WebVector.h"
namespace media {
const char kCloseSessionUMAName[] = "CloseSession";
const char kGenerateRequestUMAName[] = "GenerateRequest";
const char kLoadSessionUMAName[] = "LoadSession";
const char kRemoveSessionUMAName[] = "RemoveSession";
const char kUpdateSessionUMAName[] = "UpdateSession";
static blink::WebContentDecryptionModuleSession::Client::MessageType
convertMessageType(MediaKeys::MessageType message_type) {
switch (message_type) {
case media::MediaKeys::LICENSE_REQUEST:
return blink::WebContentDecryptionModuleSession::Client::MessageType::
LicenseRequest;
case media::MediaKeys::LICENSE_RENEWAL:
return blink::WebContentDecryptionModuleSession::Client::MessageType::
LicenseRenewal;
case media::MediaKeys::LICENSE_RELEASE:
return blink::WebContentDecryptionModuleSession::Client::MessageType::
LicenseRelease;
}
NOTREACHED();
return blink::WebContentDecryptionModuleSession::Client::MessageType::
LicenseRequest;
}
static blink::WebEncryptedMediaKeyInformation::KeyStatus convertStatus(
media::CdmKeyInformation::KeyStatus status) {
switch (status) {
case media::CdmKeyInformation::USABLE:
return blink::WebEncryptedMediaKeyInformation::KeyStatus::Usable;
case media::CdmKeyInformation::INTERNAL_ERROR:
return blink::WebEncryptedMediaKeyInformation::KeyStatus::InternalError;
case media::CdmKeyInformation::EXPIRED:
return blink::WebEncryptedMediaKeyInformation::KeyStatus::Expired;
case media::CdmKeyInformation::OUTPUT_NOT_ALLOWED:
return blink::WebEncryptedMediaKeyInformation::KeyStatus::
OutputNotAllowed;
case media::CdmKeyInformation::OUTPUT_DOWNSCALED:
return blink::WebEncryptedMediaKeyInformation::KeyStatus::
OutputDownscaled;
case media::CdmKeyInformation::KEY_STATUS_PENDING:
return blink::WebEncryptedMediaKeyInformation::KeyStatus::StatusPending;
}
NOTREACHED();
return blink::WebEncryptedMediaKeyInformation::KeyStatus::InternalError;
}
static MediaKeys::SessionType convertSessionType(
blink::WebEncryptedMediaSessionType session_type) {
switch (session_type) {
case blink::WebEncryptedMediaSessionType::Temporary:
return MediaKeys::TEMPORARY_SESSION;
case blink::WebEncryptedMediaSessionType::PersistentLicense:
return MediaKeys::PERSISTENT_LICENSE_SESSION;
case blink::WebEncryptedMediaSessionType::PersistentReleaseMessage:
return MediaKeys::PERSISTENT_RELEASE_MESSAGE_SESSION;
case blink::WebEncryptedMediaSessionType::Unknown:
break;
}
NOTREACHED();
return MediaKeys::TEMPORARY_SESSION;
}
static bool SanitizeInitData(EmeInitDataType init_data_type,
const unsigned char* init_data,
size_t init_data_length,
std::vector<uint8>* sanitized_init_data,
std::string* error_message) {
if (init_data_length > limits::kMaxInitDataLength) {
error_message->assign("Initialization data too long.");
return false;
}
switch (init_data_type) {
case EmeInitDataType::WEBM:
sanitized_init_data->assign(init_data, init_data + init_data_length);
return true;
case EmeInitDataType::CENC:
sanitized_init_data->assign(init_data, init_data + init_data_length);
if (!ValidatePsshInput(*sanitized_init_data)) {
error_message->assign("Initialization data for CENC is incorrect.");
return false;
}
return true;
case EmeInitDataType::KEYIDS: {
// Extract the keys and then rebuild the message. This ensures that any
// extra data in the provided JSON is dropped.
std::string init_data_string(init_data, init_data + init_data_length);
KeyIdList key_ids;
if (!ExtractKeyIdsFromKeyIdsInitData(init_data_string, &key_ids,
error_message))
return false;
for (const auto& key_id : key_ids) {
if (key_id.size() < limits::kMinKeyIdLength ||
key_id.size() > limits::kMaxKeyIdLength) {
error_message->assign("Incorrect key size.");
return false;
}
}
CreateKeyIdsInitData(key_ids, sanitized_init_data);
return true;
}
case EmeInitDataType::UNKNOWN:
break;
}
NOTREACHED();
error_message->assign("Initialization data type is not supported.");
return false;
}
WebContentDecryptionModuleSessionImpl::WebContentDecryptionModuleSessionImpl(
const scoped_refptr<CdmSessionAdapter>& adapter)
: adapter_(adapter), is_closed_(false), weak_ptr_factory_(this) {
}
WebContentDecryptionModuleSessionImpl::
~WebContentDecryptionModuleSessionImpl() {
if (!session_id_.empty())
adapter_->UnregisterSession(session_id_);
}
void WebContentDecryptionModuleSessionImpl::setClientInterface(Client* client) {
client_ = client;
}
blink::WebString WebContentDecryptionModuleSessionImpl::sessionId() const {
return blink::WebString::fromUTF8(session_id_);
}
void WebContentDecryptionModuleSessionImpl::initializeNewSession(
blink::WebEncryptedMediaInitDataType init_data_type,
const unsigned char* init_data,
size_t init_data_length,
blink::WebEncryptedMediaSessionType session_type,
blink::WebContentDecryptionModuleResult result) {
DCHECK(init_data);
DCHECK(session_id_.empty());
// From https://w3c.github.io/encrypted-media/#generateRequest.
// 5. If the Key System implementation represented by this object's cdm
// implementation value does not support initDataType as an Initialization
// Data Type, return a promise rejected with a new DOMException whose name
// is NotSupportedError. String comparison is case-sensitive.
EmeInitDataType eme_init_data_type = ConvertToEmeInitDataType(init_data_type);
if (!IsSupportedKeySystemWithInitDataType(adapter_->GetKeySystem(),
eme_init_data_type)) {
std::string message =
"The initialization data type is not supported by the key system.";
result.completeWithError(
blink::WebContentDecryptionModuleExceptionNotSupportedError, 0,
blink::WebString::fromUTF8(message));
return;
}
// 9.1 If the init data is not valid for initDataType, reject promise with a
// new DOMException whose name is InvalidAccessError.
// 9.2 Let sanitized init data be a validated and sanitized version of init
// data. The user agent must thoroughly validate the Initialization Data
// before passing it to the CDM. This includes verifying that the length
// and values of fields are reasonable, verifying that values are within
// reasonable limits, and stripping irrelevant, unsupported, or unknown
// data or fields. It is recommended that user agents pre-parse, sanitize,
// and/or generate a fully sanitized version of the Initialization Data.
// If the Initialization Data format specified by initDataType support
// multiple entries, the user agent should remove entries that are not
// needed by the CDM.
// 9.3 If the previous step failed, reject promise with a new DOMException
// whose name is InvalidAccessError.
std::vector<uint8> sanitized_init_data;
std::string message;
if (!SanitizeInitData(eme_init_data_type, init_data, init_data_length,
&sanitized_init_data, &message)) {
result.completeWithError(
blink::WebContentDecryptionModuleExceptionInvalidAccessError, 0,
blink::WebString::fromUTF8(message));
return;
}
// 9.4 Let session id be the empty string.
// (Done in constructor.)
// 9.5 Let message be null.
// (Done by CDM.)
// 9.6 Let cdm be the CDM instance represented by this object's cdm
// instance value.
// 9.7 Use the cdm to execute the following steps:
adapter_->InitializeNewSession(
eme_init_data_type, sanitized_init_data, convertSessionType(session_type),
scoped_ptr<NewSessionCdmPromise>(new NewSessionCdmResultPromise(
result, adapter_->GetKeySystemUMAPrefix() + kGenerateRequestUMAName,
base::Bind(
&WebContentDecryptionModuleSessionImpl::OnSessionInitialized,
base::Unretained(this)))));
}
void WebContentDecryptionModuleSessionImpl::load(
const blink::WebString& session_id,
blink::WebContentDecryptionModuleResult result) {
DCHECK(!session_id.isEmpty());
DCHECK(session_id_.empty());
// TODO(jrummell): Now that there are 2 types of persistent sessions, the
// session type should be passed from blink. Type should also be passed in the
// constructor (and removed from initializeNewSession()).
adapter_->LoadSession(
MediaKeys::PERSISTENT_LICENSE_SESSION, base::UTF16ToASCII(session_id),
scoped_ptr<NewSessionCdmPromise>(new NewSessionCdmResultPromise(
result, adapter_->GetKeySystemUMAPrefix() + kLoadSessionUMAName,
base::Bind(
&WebContentDecryptionModuleSessionImpl::OnSessionInitialized,
base::Unretained(this)))));
}
void WebContentDecryptionModuleSessionImpl::update(
const uint8* response,
size_t response_length,
blink::WebContentDecryptionModuleResult result) {
DCHECK(response);
DCHECK(!session_id_.empty());
adapter_->UpdateSession(
session_id_, std::vector<uint8>(response, response + response_length),
scoped_ptr<SimpleCdmPromise>(new CdmResultPromise<>(
result, adapter_->GetKeySystemUMAPrefix() + kUpdateSessionUMAName)));
}
void WebContentDecryptionModuleSessionImpl::close(
blink::WebContentDecryptionModuleResult result) {
DCHECK(!session_id_.empty());
adapter_->CloseSession(
session_id_,
scoped_ptr<SimpleCdmPromise>(new CdmResultPromise<>(
result, adapter_->GetKeySystemUMAPrefix() + kCloseSessionUMAName)));
}
void WebContentDecryptionModuleSessionImpl::remove(
blink::WebContentDecryptionModuleResult result) {
DCHECK(!session_id_.empty());
adapter_->RemoveSession(
session_id_,
scoped_ptr<SimpleCdmPromise>(new CdmResultPromise<>(
result, adapter_->GetKeySystemUMAPrefix() + kRemoveSessionUMAName)));
}
void WebContentDecryptionModuleSessionImpl::OnSessionMessage(
MediaKeys::MessageType message_type,
const std::vector<uint8>& message) {
DCHECK(client_) << "Client not set before message event";
client_->message(convertMessageType(message_type), vector_as_array(&message),
message.size());
}
void WebContentDecryptionModuleSessionImpl::OnSessionKeysChange(
bool has_additional_usable_key,
CdmKeysInfo keys_info) {
blink::WebVector<blink::WebEncryptedMediaKeyInformation> keys(
keys_info.size());
for (size_t i = 0; i < keys_info.size(); ++i) {
const auto& key_info = keys_info[i];
keys[i].setId(blink::WebData(reinterpret_cast<char*>(&key_info->key_id[0]),
key_info->key_id.size()));
keys[i].setStatus(convertStatus(key_info->status));
keys[i].setSystemCode(key_info->system_code);
}
// Now send the event to blink.
client_->keysStatusesChange(keys, has_additional_usable_key);
}
void WebContentDecryptionModuleSessionImpl::OnSessionExpirationUpdate(
const base::Time& new_expiry_time) {
client_->expirationChanged(new_expiry_time.ToJsTime());
}
void WebContentDecryptionModuleSessionImpl::OnSessionClosed() {
if (is_closed_)
return;
is_closed_ = true;
client_->close();
}
blink::WebContentDecryptionModuleResult::SessionStatus
WebContentDecryptionModuleSessionImpl::OnSessionInitialized(
const std::string& session_id) {
// CDM will return NULL if the session to be loaded can't be found.
if (session_id.empty())
return blink::WebContentDecryptionModuleResult::SessionNotFound;
DCHECK(session_id_.empty()) << "Session ID may not be changed once set.";
session_id_ = session_id;
return adapter_->RegisterSession(session_id_, weak_ptr_factory_.GetWeakPtr())
? blink::WebContentDecryptionModuleResult::NewSession
: blink::WebContentDecryptionModuleResult::SessionAlreadyExists;
}
} // namespace media