blob: 31e4118dbc1db50250592c8ad1dc5b91d6eaef13 [file] [log] [blame]
// Copyright 2020 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 "chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.h"
#include <utility>
#include "base/time/time.h"
#include "media/base/cdm_promise.h"
#include "media/base/decoder_buffer.h"
#include "media/base/eme_constants.h"
#include "media/base/subsample_entry.h"
namespace {
// Copy the cypher bytes as specified by |subsamples| from |src| to |dst|.
// Clear bytes contained in |src| that are specified by |subsamples| will be
// skipped. This is used when copying all the protected data out of a sample.
//
// NOTE: Before invoking this call the |subsamples| data must have been verified
// against the length of the |src| array to ensure we won't go out of bounds or
// have overflow. This can be done with media::VerifySubsamplesMatchSize.
void ExtractSubsampleCypherBytes(
const std::vector<media::SubsampleEntry>& subsamples,
const uint8_t* src,
uint8_t* dst) {
for (const auto& subsample : subsamples) {
src += subsample.clear_bytes;
memcpy(dst, src, subsample.cypher_bytes);
src += subsample.cypher_bytes;
dst += subsample.cypher_bytes;
}
}
// Copy the cypher bytes as specified by |subsamples| from |src| to |dst|.
// Any clear bytes mentioned in |subsamples| will be skipped in |dst|. This is
// used when copying the decrypted bytes back into the buffer, replacing the
// encrypted portions.
//
// NOTE: Before invoking this call the |subsamples| data must have been verified
// against the length of the |src| array to ensure we won't go out of bounds or
// have overflow. This can be done with media::VerifySubsamplesMatchSize.
void InsertSubsampleCypherBytes(
const std::vector<media::SubsampleEntry>& subsamples,
const uint8_t* src,
uint8_t* dst) {
for (const auto& subsample : subsamples) {
dst += subsample.clear_bytes;
memcpy(dst, src, subsample.cypher_bytes);
src += subsample.cypher_bytes;
dst += subsample.cypher_bytes;
}
}
// Copy the decrypted data into the output buffer. The buffer will contain
// all of the data if there was no subsampling or if we were doing CBCS with
// multiple subsamples. Otherwise we need to copy based on the subsampling.
scoped_refptr<media::DecoderBuffer> CopyDecryptedDataToDecoderBuffer(
scoped_refptr<media::DecoderBuffer> encrypted,
const std::vector<uint8_t>& decrypted_data) {
scoped_refptr<media::DecoderBuffer> decrypted;
if (encrypted->decrypt_config()->subsamples().empty() ||
(encrypted->decrypt_config()->encryption_scheme() ==
media::EncryptionScheme::kCbcs &&
encrypted->decrypt_config()->subsamples().size() > 1)) {
decrypted = media::DecoderBuffer::CopyFrom(decrypted_data.data(),
decrypted_data.size());
} else {
decrypted = media::DecoderBuffer::CopyFrom(encrypted->data(),
encrypted->data_size());
InsertSubsampleCypherBytes(encrypted->decrypt_config()->subsamples(),
decrypted_data.data(),
decrypted->writable_data());
}
// Copy the auxiliary fields.
decrypted->set_timestamp(encrypted->timestamp());
decrypted->set_duration(encrypted->duration());
decrypted->set_is_key_frame(encrypted->is_key_frame());
decrypted->CopySideDataFrom(encrypted->side_data(),
encrypted->side_data_size());
return decrypted;
}
void RejectPromiseConnectionLost(std::unique_ptr<media::CdmPromise> promise) {
promise->reject(media::CdmPromise::Exception::INVALID_STATE_ERROR, 0,
"Mojo connection lost");
}
} // namespace
namespace chromeos {
ContentDecryptionModuleAdapter::ContentDecryptionModuleAdapter(
std::unique_ptr<CdmStorageAdapter> storage,
mojo::AssociatedRemote<cdm::mojom::ContentDecryptionModule> cros_cdm_remote,
const media::SessionMessageCB& session_message_cb,
const media::SessionClosedCB& session_closed_cb,
const media::SessionKeysChangeCB& session_keys_change_cb,
const media::SessionExpirationUpdateCB& session_expiration_update_cb)
: storage_(std::move(storage)),
cros_cdm_remote_(std::move(cros_cdm_remote)),
session_message_cb_(session_message_cb),
session_closed_cb_(session_closed_cb),
session_keys_change_cb_(session_keys_change_cb),
session_expiration_update_cb_(session_expiration_update_cb),
mojo_task_runner_(base::SequencedTaskRunnerHandle::Get()) {
DVLOG(1) << "Created ContentDecryptionModuleAdapter";
cros_cdm_remote_.set_disconnect_handler(
base::BindOnce(&ContentDecryptionModuleAdapter::OnConnectionError,
base::Unretained(this)));
}
mojo::PendingAssociatedRemote<cdm::mojom::ContentDecryptionModuleClient>
ContentDecryptionModuleAdapter::GetClientInterface() {
CHECK(!cros_client_receiver_.is_bound());
auto ret = cros_client_receiver_.BindNewEndpointAndPassRemote();
cros_client_receiver_.set_disconnect_handler(
base::BindOnce(&ContentDecryptionModuleAdapter::OnConnectionError,
base::Unretained(this)));
return ret;
}
void ContentDecryptionModuleAdapter::SetServerCertificate(
const std::vector<uint8_t>& certificate_data,
std::unique_ptr<media::SimpleCdmPromise> promise) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(2) << __func__;
if (!cros_cdm_remote_) {
RejectPromiseConnectionLost(std::move(promise));
return;
}
uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
cros_cdm_remote_->SetServerCertificate(
certificate_data,
base::BindOnce(&ContentDecryptionModuleAdapter::OnSimplePromiseResult,
base::Unretained(this), promise_id));
}
void ContentDecryptionModuleAdapter::GetStatusForPolicy(
media::HdcpVersion min_hdcp_version,
std::unique_ptr<media::KeyStatusCdmPromise> promise) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(2) << __func__;
if (!cros_cdm_remote_) {
RejectPromiseConnectionLost(std::move(promise));
return;
}
uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
cros_cdm_remote_->GetStatusForPolicy(
min_hdcp_version,
base::BindOnce(&ContentDecryptionModuleAdapter::OnGetStatusForPolicy,
base::Unretained(this), promise_id));
}
void ContentDecryptionModuleAdapter::CreateSessionAndGenerateRequest(
media::CdmSessionType session_type,
media::EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data,
std::unique_ptr<media::NewSessionCdmPromise> promise) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(2) << __func__;
if (!cros_cdm_remote_) {
RejectPromiseConnectionLost(std::move(promise));
return;
}
uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
cros_cdm_remote_->CreateSessionAndGenerateRequest(
session_type, init_data_type, init_data,
base::BindOnce(&ContentDecryptionModuleAdapter::OnSessionPromiseResult,
base::Unretained(this), promise_id));
}
void ContentDecryptionModuleAdapter::LoadSession(
media::CdmSessionType session_type,
const std::string& session_id,
std::unique_ptr<media::NewSessionCdmPromise> promise) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(2) << __func__;
if (!cros_cdm_remote_) {
RejectPromiseConnectionLost(std::move(promise));
return;
}
uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
cros_cdm_remote_->LoadSession(
session_type, session_id,
base::BindOnce(&ContentDecryptionModuleAdapter::OnSessionPromiseResult,
base::Unretained(this), promise_id));
}
void ContentDecryptionModuleAdapter::UpdateSession(
const std::string& session_id,
const std::vector<uint8_t>& response,
std::unique_ptr<media::SimpleCdmPromise> promise) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(2) << __func__;
if (!cros_cdm_remote_) {
RejectPromiseConnectionLost(std::move(promise));
return;
}
uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
cros_cdm_remote_->UpdateSession(
session_id, response,
base::BindOnce(&ContentDecryptionModuleAdapter::OnSimplePromiseResult,
base::Unretained(this), promise_id));
}
void ContentDecryptionModuleAdapter::CloseSession(
const std::string& session_id,
std::unique_ptr<media::SimpleCdmPromise> promise) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(2) << __func__;
if (!cros_cdm_remote_) {
RejectPromiseConnectionLost(std::move(promise));
return;
}
uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
cros_cdm_remote_->CloseSession(
session_id,
base::BindOnce(&ContentDecryptionModuleAdapter::OnSimplePromiseResult,
base::Unretained(this), promise_id));
}
void ContentDecryptionModuleAdapter::RemoveSession(
const std::string& session_id,
std::unique_ptr<media::SimpleCdmPromise> promise) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(2) << __func__;
if (!cros_cdm_remote_) {
RejectPromiseConnectionLost(std::move(promise));
return;
}
uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
cros_cdm_remote_->RemoveSession(
session_id,
base::BindOnce(&ContentDecryptionModuleAdapter::OnSimplePromiseResult,
base::Unretained(this), promise_id));
}
media::CdmContext* ContentDecryptionModuleAdapter::GetCdmContext() {
return this;
}
media::Decryptor* ContentDecryptionModuleAdapter::GetDecryptor() {
return this;
}
void ContentDecryptionModuleAdapter::OnSessionMessage(
const std::string& session_id,
media::CdmMessageType message_type,
const std::vector<uint8_t>& message) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(2) << __func__;
session_message_cb_.Run(session_id, message_type, message);
}
void ContentDecryptionModuleAdapter::OnSessionClosed(
const std::string& session_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(2) << __func__;
cdm_session_tracker_.RemoveSession(session_id);
session_closed_cb_.Run(session_id);
}
void ContentDecryptionModuleAdapter::OnSessionKeysChange(
const std::string& session_id,
bool has_additional_usable_key,
std::vector<std::unique_ptr<media::CdmKeyInformation>> keys_info) {
DVLOG(2) << __func__
<< " has_additional_usable_key: " << has_additional_usable_key;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (has_additional_usable_key) {
base::AutoLock auto_lock(new_key_cb_lock_);
if (new_audio_key_cb_)
new_audio_key_cb_.Run();
if (new_video_key_cb_)
new_video_key_cb_.Run();
}
session_keys_change_cb_.Run(session_id, has_additional_usable_key,
std::move(keys_info));
}
void ContentDecryptionModuleAdapter::OnSessionExpirationUpdate(
const std::string& session_id,
double new_expiry_time_sec) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
session_expiration_update_cb_.Run(
session_id, base::Time::FromDoubleT(new_expiry_time_sec));
}
void ContentDecryptionModuleAdapter::RegisterNewKeyCB(StreamType stream_type,
NewKeyCB new_key_cb) {
base::AutoLock auto_lock(new_key_cb_lock_);
switch (stream_type) {
case kAudio:
new_audio_key_cb_ = std::move(new_key_cb);
break;
case kVideo:
new_video_key_cb_ = std::move(new_key_cb);
break;
}
}
void ContentDecryptionModuleAdapter::Decrypt(
StreamType stream_type,
scoped_refptr<media::DecoderBuffer> encrypted,
DecryptCB decrypt_cb) {
// This can get called from decoder threads or mojo threads, so we may need
// to repost the task.
if (!mojo_task_runner_->RunsTasksInCurrentSequence()) {
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&ContentDecryptionModuleAdapter::Decrypt,
weak_factory_.GetWeakPtr(), stream_type,
encrypted, std::move(decrypt_cb)));
return;
}
DVLOG(2) << __func__ << ": " << encrypted->AsHumanReadableString(true);
if (!cros_cdm_remote_) {
std::move(decrypt_cb).Run(media::Decryptor::kError, nullptr);
return;
}
const media::DecryptConfig* decrypt_config = encrypted->decrypt_config();
if (!decrypt_config) {
// If there is no DecryptConfig, then the data is unencrypted so return it
// immediately.
std::move(decrypt_cb).Run(kSuccess, encrypted);
return;
}
cdm::mojom::DecryptConfigPtr cros_decrypt_config(
cdm::mojom::DecryptConfig::New());
cros_decrypt_config->key_id = decrypt_config->key_id();
cros_decrypt_config->iv = decrypt_config->iv();
if (decrypt_config->HasPattern()) {
cros_decrypt_config->encryption_pattern =
decrypt_config->encryption_pattern().value();
}
cros_decrypt_config->encryption_scheme = decrypt_config->encryption_scheme();
const std::vector<media::SubsampleEntry>& subsamples =
decrypt_config->subsamples();
if (subsamples.empty()) {
StoreDecryptCallback(stream_type, std::move(decrypt_cb));
// No subsamples specified, request decryption of entire block.
// TODO(jkardatzke): Evaluate the performance cost here of copying the data
// and see if want to use something like MojoDecoderBufferWriter instead.
cros_cdm_remote_->Decrypt(
std::vector<uint8_t>(encrypted->data(),
encrypted->data() + encrypted->data_size()),
std::move(cros_decrypt_config),
base::BindOnce(&ContentDecryptionModuleAdapter::OnDecrypt,
base::Unretained(this), stream_type, encrypted,
encrypted->data_size()));
return;
}
if (!VerifySubsamplesMatchSize(subsamples, encrypted->data_size())) {
LOG(ERROR) << "Subsample sizes do not match input size";
std::move(decrypt_cb).Run(kError, nullptr);
return;
}
// Compute the size of the encrypted portion. Overflow, etc. checked by
// the call to VerifySubsamplesMatchSize().
size_t total_encrypted_size = 0;
for (const auto& subsample : subsamples)
total_encrypted_size += subsample.cypher_bytes;
// No need to decrypt if there is no encrypted data.
if (total_encrypted_size == 0) {
encrypted->set_decrypt_config(nullptr);
std::move(decrypt_cb).Run(kSuccess, encrypted);
return;
}
StoreDecryptCallback(stream_type, std::move(decrypt_cb));
// For CENC, the encrypted portions of all subsamples must form a contiguous
// block, such that an encrypted subsample that ends away from a block
// boundary is immediately followed by the start of the next encrypted
// subsample. We copy all encrypted subsamples to a contiguous buffer, decrypt
// them, then copy the decrypted bytes over the encrypted bytes in the output.
// For CBCS, if there is more than one sample, then we need to pass the
// subsample information or otherwise we would need to call decrypt for each
// individual subsample since each subsample uses the same IV and it can't be
// decrypted as one large block like CENC.
if (decrypt_config->encryption_scheme() == media::EncryptionScheme::kCenc ||
subsamples.size() == 1) {
std::vector<uint8_t> encrypted_bytes(total_encrypted_size);
ExtractSubsampleCypherBytes(subsamples, encrypted->data(),
encrypted_bytes.data());
cros_cdm_remote_->Decrypt(
std::move(encrypted_bytes), std::move(cros_decrypt_config),
base::BindOnce(&ContentDecryptionModuleAdapter::OnDecrypt,
base::Unretained(this), stream_type, encrypted,
total_encrypted_size));
return;
}
// We need to specify the subsampling and put that in the decrypt config.
for (const auto& sample : subsamples) {
cros_decrypt_config->subsamples.push_back(cdm::mojom::SubsampleEntry::New(
sample.clear_bytes, sample.cypher_bytes));
}
// TODO(jkardatzke): Evaluate the performance cost here of copying the data
// and see if want to use something like MojoDecoderBufferWriter instead.
cros_cdm_remote_->Decrypt(
std::vector<uint8_t>(encrypted->data(),
encrypted->data() + encrypted->data_size()),
std::move(cros_decrypt_config),
base::BindOnce(&ContentDecryptionModuleAdapter::OnDecrypt,
base::Unretained(this), stream_type, encrypted,
encrypted->data_size()));
}
void ContentDecryptionModuleAdapter::CancelDecrypt(StreamType stream_type) {
// This can get called from decoder threads or mojo threads, so we may need
// to repost the task.
if (!mojo_task_runner_->RunsTasksInCurrentSequence()) {
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ContentDecryptionModuleAdapter::CancelDecrypt,
weak_factory_.GetWeakPtr(), stream_type));
return;
}
media::Decryptor::DecryptCB callback =
std::move(stream_type == kVideo ? pending_video_decrypt_cb_
: pending_audio_decrypt_cb_);
if (callback)
std::move(callback).Run(media::Decryptor::kSuccess, nullptr);
}
void ContentDecryptionModuleAdapter::InitializeAudioDecoder(
const media::AudioDecoderConfig& config,
DecoderInitCB init_cb) {
// ContentDecryptionModuleAdapter does not support audio decoding.
std::move(init_cb).Run(false);
}
void ContentDecryptionModuleAdapter::InitializeVideoDecoder(
const media::VideoDecoderConfig& config,
DecoderInitCB init_cb) {
// ContentDecryptionModuleAdapter does not support video decoding.
std::move(init_cb).Run(false);
}
void ContentDecryptionModuleAdapter::DecryptAndDecodeAudio(
scoped_refptr<media::DecoderBuffer> encrypted,
const AudioDecodeCB& audio_decode_cb) {
NOTREACHED()
<< "ContentDecryptionModuleAdapter does not support audio decoding";
}
void ContentDecryptionModuleAdapter::DecryptAndDecodeVideo(
scoped_refptr<media::DecoderBuffer> encrypted,
const VideoDecodeCB& video_decode_cb) {
NOTREACHED()
<< "ContentDecryptionModuleAdapter does not support video decoding";
}
void ContentDecryptionModuleAdapter::ResetDecoder(StreamType stream_type) {
NOTREACHED() << "ContentDecryptionModuleAdapter does not support decoding";
}
void ContentDecryptionModuleAdapter::DeinitializeDecoder(
StreamType stream_type) {
// We do not support audio/video decoding, but since this can be called any
// time after InitializeAudioDecoder/InitializeVideoDecoder, nothing to be
// done here.
}
bool ContentDecryptionModuleAdapter::CanAlwaysDecrypt() {
return false;
}
ContentDecryptionModuleAdapter::~ContentDecryptionModuleAdapter() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(2) << __func__;
cdm_session_tracker_.CloseRemainingSessions(session_closed_cb_);
}
void ContentDecryptionModuleAdapter::OnConnectionError() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(1) << __func__;
// The mojo connection doesn't manage our lifecycle, that's handled by the
// owner of the media::ContentDecryptionModule implementation we provide; so
// only drop the bindings and notify the other end about all of the closed
// sessions; don't destruct here.
cros_client_receiver_.reset();
cros_cdm_remote_.reset();
// We've lost our communication, so reject all outstanding promises and close
// any open sessions.
cdm_promise_adapter_.Clear();
cdm_session_tracker_.CloseRemainingSessions(session_closed_cb_);
}
void ContentDecryptionModuleAdapter::RejectTrackedPromise(
uint32_t promise_id,
cdm::mojom::CdmPromiseResultPtr promise_result) {
cdm_promise_adapter_.RejectPromise(promise_id, promise_result->exception,
promise_result->system_code,
promise_result->error_message);
}
void ContentDecryptionModuleAdapter::OnSimplePromiseResult(
uint32_t promise_id,
cdm::mojom::CdmPromiseResultPtr promise_result) {
DVLOG(1) << __func__ << " received result: " << promise_result->success;
if (!promise_result->success) {
RejectTrackedPromise(promise_id, std::move(promise_result));
return;
}
cdm_promise_adapter_.ResolvePromise(promise_id);
}
void ContentDecryptionModuleAdapter::OnGetStatusForPolicy(
uint32_t promise_id,
cdm::mojom::CdmPromiseResultPtr promise_result,
media::CdmKeyInformation::KeyStatus key_status) {
if (!promise_result->success) {
RejectTrackedPromise(promise_id, std::move(promise_result));
return;
}
cdm_promise_adapter_.ResolvePromise(promise_id, key_status);
}
void ContentDecryptionModuleAdapter::OnSessionPromiseResult(
uint32_t promise_id,
cdm::mojom::CdmPromiseResultPtr promise_result,
const std::string& session_id) {
DVLOG(1) << __func__ << " received result: " << promise_result->success
<< " session: " << session_id;
if (!promise_result->success) {
RejectTrackedPromise(promise_id, std::move(promise_result));
return;
}
cdm_session_tracker_.AddSession(session_id);
cdm_promise_adapter_.ResolvePromise(promise_id, session_id);
}
void ContentDecryptionModuleAdapter::StoreDecryptCallback(
StreamType stream_type,
DecryptCB decrypt_cb) {
if (stream_type == kVideo) {
DCHECK(!pending_video_decrypt_cb_);
pending_video_decrypt_cb_ = std::move(decrypt_cb);
} else {
DCHECK(!pending_audio_decrypt_cb_);
pending_audio_decrypt_cb_ = std::move(decrypt_cb);
}
}
void ContentDecryptionModuleAdapter::OnDecrypt(
StreamType stream_type,
scoped_refptr<media::DecoderBuffer> encrypted,
size_t expected_decrypt_size,
media::Decryptor::Status status,
const std::vector<uint8_t>& decrypted_data) {
media::Decryptor::DecryptCB callback =
std::move(stream_type == kVideo ? pending_video_decrypt_cb_
: pending_audio_decrypt_cb_);
if (!callback) {
// This happens if CancelDecrypt was called.
DVLOG(1) << __func__ << " decrypt callback empty";
return;
}
if (status != media::Decryptor::kSuccess) {
if (status == media::Decryptor::kNoKey) {
DVLOG(1) << "Decryption failed due to no key";
} else {
LOG(ERROR) << "Failure decrypting data: " << status;
}
std::move(callback).Run(status, nullptr);
return;
}
if (decrypted_data.size() != expected_decrypt_size) {
LOG(ERROR) << "Decrypted data size mismatch got: " << decrypted_data.size()
<< " expected: " << expected_decrypt_size;
std::move(callback).Run(media::Decryptor::kError, nullptr);
return;
}
std::move(callback).Run(
media::Decryptor::kSuccess,
CopyDecryptedDataToDecoderBuffer(std::move(encrypted), decrypted_data));
}
} // namespace chromeos