blob: 5baec5818095e7de7e7f564bc45091e0ad6ffcbb [file] [log] [blame]
// Copyright 2017 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 "media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "media/base/cdm_promise.h"
#include "media/cdm/json_web_key.h"
namespace media {
namespace {
// When creating a persistent session, we need to add the session ID to the
// set of open persistent sessions. However, since the session ID is not
// assigned until the end, this promise is needed to capture the resulting
// session ID.
class NewPersistentSessionCdmPromise : public NewSessionCdmPromise {
public:
using SessionCreatedCB =
base::OnceCallback<void(const std::string& session_id)>;
NewPersistentSessionCdmPromise(SessionCreatedCB session_created_cb,
std::unique_ptr<NewSessionCdmPromise> promise)
: session_created_cb_(std::move(session_created_cb)),
promise_(std::move(promise)) {}
NewPersistentSessionCdmPromise(const NewPersistentSessionCdmPromise&) =
delete;
NewPersistentSessionCdmPromise& operator=(
const NewPersistentSessionCdmPromise&) = delete;
~NewPersistentSessionCdmPromise() override = default;
// NewSessionCdmPromise implementation.
void resolve(const std::string& session_id) override {
MarkPromiseSettled();
std::move(session_created_cb_).Run(session_id);
promise_->resolve(session_id);
}
void reject(CdmPromise::Exception exception_code,
uint32_t system_code,
const std::string& error_message) override {
MarkPromiseSettled();
promise_->reject(exception_code, system_code, error_message);
}
private:
SessionCreatedCB session_created_cb_;
std::unique_ptr<NewSessionCdmPromise> promise_;
};
// When a session has been loaded, we need to call FinishUpdate() to complete
// the loading of the session (to resolve the promise, generate events, etc.
// in the correct order). UpdateSession() needs a SimpleCdmPromise, but
// LoadSession needs to be resolved with the session ID. This class simply
// maps the resolve/reject call to do the right thing for NewSessionCdmPromise.
class FinishLoadCdmPromise : public SimpleCdmPromise {
public:
FinishLoadCdmPromise(const std::string& session_id,
std::unique_ptr<NewSessionCdmPromise> promise)
: session_id_(session_id), promise_(std::move(promise)) {}
FinishLoadCdmPromise(const FinishLoadCdmPromise&) = delete;
FinishLoadCdmPromise& operator=(const FinishLoadCdmPromise&) = delete;
~FinishLoadCdmPromise() override = default;
// CdmSimplePromise implementation.
void resolve() override {
MarkPromiseSettled();
promise_->resolve(session_id_);
}
void reject(CdmPromise::Exception exception_code,
uint32_t system_code,
const std::string& error_message) override {
// Return an empty string to indicate that the session was not found.
MarkPromiseSettled();
promise_->resolve(std::string());
}
private:
std::string session_id_;
std::unique_ptr<NewSessionCdmPromise> promise_;
};
} // namespace
ClearKeyPersistentSessionCdm::ClearKeyPersistentSessionCdm(
CdmHostProxy* cdm_host_proxy,
const SessionMessageCB& session_message_cb,
const SessionClosedCB& session_closed_cb,
const SessionKeysChangeCB& session_keys_change_cb,
const SessionExpirationUpdateCB& session_expiration_update_cb)
: cdm_host_proxy_(cdm_host_proxy),
session_message_cb_(session_message_cb),
session_closed_cb_(session_closed_cb) {
cdm_ = base::MakeRefCounted<AesDecryptor>(
base::BindRepeating(&ClearKeyPersistentSessionCdm::OnSessionMessage,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&ClearKeyPersistentSessionCdm::OnSessionClosed,
weak_factory_.GetWeakPtr()),
session_keys_change_cb, session_expiration_update_cb);
}
ClearKeyPersistentSessionCdm::~ClearKeyPersistentSessionCdm() = default;
void ClearKeyPersistentSessionCdm::SetServerCertificate(
const std::vector<uint8_t>& certificate,
std::unique_ptr<SimpleCdmPromise> promise) {
cdm_->SetServerCertificate(certificate, std::move(promise));
}
void ClearKeyPersistentSessionCdm::CreateSessionAndGenerateRequest(
CdmSessionType session_type,
EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data,
std::unique_ptr<NewSessionCdmPromise> promise) {
std::unique_ptr<NewSessionCdmPromise> new_promise;
if (session_type == CdmSessionType::kTemporary) {
new_promise = std::move(promise);
} else {
// Since it's a persistent session, we need to save the session ID after
// it's been created.
new_promise = std::make_unique<NewPersistentSessionCdmPromise>(
base::BindOnce(&ClearKeyPersistentSessionCdm::AddPersistentSession,
weak_factory_.GetWeakPtr()),
std::move(promise));
}
cdm_->CreateSessionAndGenerateRequest(session_type, init_data_type, init_data,
std::move(new_promise));
}
void ClearKeyPersistentSessionCdm::LoadSession(
CdmSessionType session_type,
const std::string& session_id,
std::unique_ptr<NewSessionCdmPromise> promise) {
DCHECK_EQ(CdmSessionType::kPersistentLicense, session_type);
// Load the saved state for |session_id| and then create the session.
std::unique_ptr<CdmFileAdapter> file(new CdmFileAdapter(cdm_host_proxy_));
CdmFileAdapter* file_ref = file.get();
file_ref->Open(
session_id,
base::BindOnce(&ClearKeyPersistentSessionCdm::OnFileOpenedForLoadSession,
weak_factory_.GetWeakPtr(), session_id, std::move(file),
std::move(promise)));
}
void ClearKeyPersistentSessionCdm::OnFileOpenedForLoadSession(
const std::string& session_id,
std::unique_ptr<CdmFileAdapter> file,
std::unique_ptr<NewSessionCdmPromise> promise,
CdmFileAdapter::Status status) {
if (status != CdmFileAdapter::Status::kSuccess) {
// If unable to get data for the session, we can't load it. Return an empty
// string to indicate that the session was not found.
promise->resolve(std::string());
return;
}
CdmFileAdapter* file_reader = file.get();
file_reader->Read(
base::BindOnce(&ClearKeyPersistentSessionCdm::OnFileReadForLoadSession,
weak_factory_.GetWeakPtr(), session_id, std::move(file),
std::move(promise)));
}
void ClearKeyPersistentSessionCdm::OnFileReadForLoadSession(
const std::string& session_id,
std::unique_ptr<CdmFileAdapter> file,
std::unique_ptr<NewSessionCdmPromise> promise,
bool success,
const std::vector<uint8_t>& data) {
if (!success) {
// If unable to get data for the session, so most likely the file doesn't
// exist. Return an empty string to indicate that the session was not found.
promise->resolve(std::string());
return;
}
// Add the session to the list of active sessions.
if (!cdm_->CreateSession(session_id, CdmSessionType::kPersistentLicense)) {
// If the session can't be created it's due to an already existing session
// with the same name.
promise->reject(CdmPromise::Exception::QUOTA_EXCEEDED_ERROR, 0,
"Session already exists.");
return;
}
AddPersistentSession(session_id);
// Set the session's state using the data just read.
bool key_added = false;
CdmPromise::Exception exception;
std::string error_message;
if (!cdm_->UpdateSessionWithJWK(session_id,
std::string(data.begin(), data.end()),
&key_added, &exception, &error_message)) {
NOTREACHED() << "Saved session data is not usable, error = "
<< error_message;
// Return an empty string to indicate that the session was not found.
promise->resolve(std::string());
return;
}
// FinishUpdate() needs a SimpleCdmPromise, so create a wrapper promise.
std::unique_ptr<SimpleCdmPromise> simple_promise(
new FinishLoadCdmPromise(session_id, std::move(promise)));
cdm_->FinishUpdate(session_id, key_added, std::move(simple_promise));
}
void ClearKeyPersistentSessionCdm::UpdateSession(
const std::string& session_id,
const std::vector<uint8_t>& response,
std::unique_ptr<SimpleCdmPromise> promise) {
CHECK(!response.empty());
auto it = persistent_sessions_.find(session_id);
if (it == persistent_sessions_.end()) {
// Not a persistent session, so simply pass the request on.
cdm_->UpdateSession(session_id, response, std::move(promise));
return;
}
bool key_added = false;
CdmPromise::Exception exception;
std::string error_message;
if (!cdm_->UpdateSessionWithJWK(session_id,
std::string(response.begin(), response.end()),
&key_added, &exception, &error_message)) {
promise->reject(exception, 0, error_message);
return;
}
// Persistent session has been updated, so save the current state.
std::unique_ptr<CdmFileAdapter> file(new CdmFileAdapter(cdm_host_proxy_));
CdmFileAdapter* file_ref = file.get();
file_ref->Open(
session_id,
base::BindOnce(
&ClearKeyPersistentSessionCdm::OnFileOpenedForUpdateSession,
weak_factory_.GetWeakPtr(), session_id, key_added, std::move(file),
std::move(promise)));
}
void ClearKeyPersistentSessionCdm::OnFileOpenedForUpdateSession(
const std::string& session_id,
bool key_added,
std::unique_ptr<CdmFileAdapter> file,
std::unique_ptr<SimpleCdmPromise> promise,
CdmFileAdapter::Status status) {
if (status != CdmFileAdapter::Status::kSuccess) {
// Unable to open the file, so the state can't be saved.
promise->reject(CdmPromise::Exception::INVALID_STATE_ERROR, 0,
"Unable to save session state.");
return;
}
// Grab the current session state and save it.
std::string current_state = cdm_->GetSessionStateAsJWK(session_id);
CdmFileAdapter* file_writer = file.get();
file_writer->Write(
std::vector<uint8_t>(current_state.begin(), current_state.end()),
base::BindOnce(
&ClearKeyPersistentSessionCdm::OnFileWrittenForUpdateSession,
weak_factory_.GetWeakPtr(), session_id, key_added, std::move(file),
std::move(promise)));
}
void ClearKeyPersistentSessionCdm::OnFileWrittenForUpdateSession(
const std::string& session_id,
bool key_added,
std::unique_ptr<CdmFileAdapter> file,
std::unique_ptr<SimpleCdmPromise> promise,
bool success) {
if (!success) {
// Unable to save the state.
promise->reject(CdmPromise::Exception::INVALID_STATE_ERROR, 0,
"Unable to save session state.");
return;
}
cdm_->FinishUpdate(session_id, key_added, std::move(promise));
}
void ClearKeyPersistentSessionCdm::CloseSession(
const std::string& session_id,
std::unique_ptr<SimpleCdmPromise> promise) {
cdm_->CloseSession(session_id, std::move(promise));
}
void ClearKeyPersistentSessionCdm::RemoveSession(
const std::string& session_id,
std::unique_ptr<SimpleCdmPromise> promise) {
auto it = persistent_sessions_.find(session_id);
if (it == persistent_sessions_.end()) {
// Not a persistent session, so simply pass the request on.
cdm_->RemoveSession(session_id, std::move(promise));
return;
}
// Remove the saved state for |session_id| first.
std::unique_ptr<CdmFileAdapter> file(new CdmFileAdapter(cdm_host_proxy_));
CdmFileAdapter* file_ref = file.get();
file_ref->Open(
session_id,
base::BindOnce(
&ClearKeyPersistentSessionCdm::OnFileOpenedForRemoveSession,
weak_factory_.GetWeakPtr(), session_id, std::move(file),
std::move(promise)));
}
void ClearKeyPersistentSessionCdm::OnFileOpenedForRemoveSession(
const std::string& session_id,
std::unique_ptr<CdmFileAdapter> file,
std::unique_ptr<SimpleCdmPromise> promise,
CdmFileAdapter::Status status) {
if (status != CdmFileAdapter::Status::kSuccess) {
// If no saved data, simply call RemoveSession().
cdm_->RemoveSession(session_id, std::move(promise));
return;
}
// Write out 0 length data to erase the file.
CdmFileAdapter* file_writer = file.get();
file_writer->Write(
std::vector<uint8_t>(),
base::BindOnce(
&ClearKeyPersistentSessionCdm::OnFileWrittenForRemoveSession,
weak_factory_.GetWeakPtr(), session_id, std::move(file),
std::move(promise)));
}
void ClearKeyPersistentSessionCdm::OnFileWrittenForRemoveSession(
const std::string& session_id,
std::unique_ptr<CdmFileAdapter> file,
std::unique_ptr<SimpleCdmPromise> promise,
bool success) {
DCHECK(success);
}
CdmContext* ClearKeyPersistentSessionCdm::GetCdmContext() {
return cdm_.get();
}
void ClearKeyPersistentSessionCdm::AddPersistentSession(
const std::string& session_id) {
persistent_sessions_.insert(session_id);
}
void ClearKeyPersistentSessionCdm::OnSessionClosed(
const std::string& session_id,
CdmSessionClosedReason reason) {
persistent_sessions_.erase(session_id);
session_closed_cb_.Run(session_id, reason);
}
void ClearKeyPersistentSessionCdm::OnSessionMessage(
const std::string& session_id,
CdmMessageType message_type,
const std::vector<uint8_t>& message) {
session_message_cb_.Run(session_id, message_type, message);
}
} // namespace media