| // 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 |