Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / media / cdm / browser_cdm_manager.cc
blob3eb55f1efc9e383c9f5620ed46b56770a1994a89
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/media/cdm/browser_cdm_manager.h"
7 #include <string>
9 #include "base/bind.h"
10 #include "base/lazy_instance.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/task_runner.h"
13 #include "content/common/media/cdm_messages.h"
14 #include "content/public/browser/browser_context.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/content_browser_client.h"
17 #include "content/public/browser/permission_manager.h"
18 #include "content/public/browser/permission_type.h"
19 #include "content/public/browser/render_frame_host.h"
20 #include "content/public/browser/render_process_host.h"
21 #include "content/public/browser/render_view_host.h"
22 #include "content/public/browser/web_contents.h"
23 #include "media/base/browser_cdm.h"
24 #include "media/base/browser_cdm_factory.h"
25 #include "media/base/cdm_promise.h"
26 #include "media/base/limits.h"
28 #if defined(OS_ANDROID)
29 #include "content/public/common/renderer_preferences.h"
30 #endif
32 namespace content {
34 using media::BrowserCdm;
35 using media::MediaKeys;
37 namespace {
39 // Maximum lengths for various EME API parameters. These are checks to
40 // prevent unnecessarily large parameters from being passed around, and the
41 // lengths are somewhat arbitrary as the EME spec doesn't specify any limits.
42 const size_t kMaxInitDataLength = 64 * 1024; // 64 KB
43 const size_t kMaxSessionResponseLength = 64 * 1024; // 64 KB
44 const size_t kMaxKeySystemLength = 256;
46 // The ID used in this class is a concatenation of |render_frame_id| and
47 // |cdm_id|, i.e. (render_frame_id << 32) + cdm_id.
49 uint64 GetId(int render_frame_id, int cdm_id) {
50 return (static_cast<uint64>(render_frame_id) << 32) +
51 static_cast<uint64>(cdm_id);
54 bool IdBelongsToFrame(uint64 id, int render_frame_id) {
55 return (id >> 32) == static_cast<uint64>(render_frame_id);
58 // media::CdmPromiseTemplate implementation backed by a BrowserCdmManager.
59 template <typename... T>
60 class CdmPromiseInternal : public media::CdmPromiseTemplate<T...> {
61 public:
62 CdmPromiseInternal(BrowserCdmManager* manager,
63 int render_frame_id,
64 int cdm_id,
65 uint32_t promise_id)
66 : manager_(manager),
67 render_frame_id_(render_frame_id),
68 cdm_id_(cdm_id),
69 promise_id_(promise_id) {
70 DCHECK(manager_);
73 ~CdmPromiseInternal() final {}
75 // CdmPromiseTemplate<> implementation.
76 void resolve(const T&... result) final;
78 void reject(MediaKeys::Exception exception,
79 uint32_t system_code,
80 const std::string& error_message) final {
81 MarkPromiseSettled();
82 manager_->RejectPromise(render_frame_id_, cdm_id_, promise_id_, exception,
83 system_code, error_message);
86 private:
87 using media::CdmPromiseTemplate<T...>::MarkPromiseSettled;
89 BrowserCdmManager* const manager_;
90 const int render_frame_id_;
91 const int cdm_id_;
92 const uint32_t promise_id_;
95 template <>
96 void CdmPromiseInternal<>::resolve() {
97 MarkPromiseSettled();
98 manager_->ResolvePromise(render_frame_id_, cdm_id_, promise_id_);
101 template <>
102 void CdmPromiseInternal<std::string>::resolve(const std::string& session_id) {
103 MarkPromiseSettled();
104 manager_->ResolvePromiseWithSession(render_frame_id_, cdm_id_, promise_id_,
105 session_id);
108 typedef CdmPromiseInternal<> SimplePromise;
109 typedef CdmPromiseInternal<std::string> NewSessionPromise;
111 } // namespace
113 // Render process ID to BrowserCdmManager map.
114 typedef std::map<int, BrowserCdmManager*> BrowserCdmManagerMap;
115 base::LazyInstance<BrowserCdmManagerMap> g_browser_cdm_manager_map =
116 LAZY_INSTANCE_INITIALIZER;
118 BrowserCdmManager* BrowserCdmManager::FromProcess(int render_process_id) {
119 DCHECK_CURRENTLY_ON(BrowserThread::UI);
121 if (!g_browser_cdm_manager_map.Get().count(render_process_id))
122 return NULL;
124 return g_browser_cdm_manager_map.Get()[render_process_id];
127 BrowserCdmManager::BrowserCdmManager(
128 int render_process_id,
129 const scoped_refptr<base::TaskRunner>& task_runner)
130 : BrowserMessageFilter(CdmMsgStart),
131 render_process_id_(render_process_id),
132 task_runner_(task_runner),
133 weak_ptr_factory_(this) {
134 DVLOG(1) << __FUNCTION__ << ": " << render_process_id_;
135 DCHECK_CURRENTLY_ON(BrowserThread::UI);
137 if (!task_runner_.get()) {
138 task_runner_ =
139 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI);
142 DCHECK(!g_browser_cdm_manager_map.Get().count(render_process_id_))
143 << render_process_id_;
144 g_browser_cdm_manager_map.Get()[render_process_id] = this;
147 BrowserCdmManager::~BrowserCdmManager() {
148 DVLOG(1) << __FUNCTION__ << ": " << render_process_id_;
149 DCHECK_CURRENTLY_ON(BrowserThread::UI);
150 DCHECK(g_browser_cdm_manager_map.Get().count(render_process_id_));
151 DCHECK_EQ(this, g_browser_cdm_manager_map.Get()[render_process_id_]);
153 g_browser_cdm_manager_map.Get().erase(render_process_id_);
156 // Makes sure BrowserCdmManager is always deleted on the Browser UI thread.
157 void BrowserCdmManager::OnDestruct() const {
158 DVLOG(1) << __FUNCTION__ << ": " << render_process_id_;
159 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
160 delete this;
161 } else {
162 BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
166 base::TaskRunner* BrowserCdmManager::OverrideTaskRunnerForMessage(
167 const IPC::Message& message) {
168 // Only handles CDM messages.
169 if (IPC_MESSAGE_CLASS(message) != CdmMsgStart)
170 return NULL;
172 return task_runner_.get();
175 bool BrowserCdmManager::OnMessageReceived(const IPC::Message& msg) {
176 DCHECK(task_runner_->RunsTasksOnCurrentThread());
177 bool handled = true;
178 IPC_BEGIN_MESSAGE_MAP(BrowserCdmManager, msg)
179 IPC_MESSAGE_HANDLER(CdmHostMsg_InitializeCdm, OnInitializeCdm)
180 IPC_MESSAGE_HANDLER(CdmHostMsg_SetServerCertificate, OnSetServerCertificate)
181 IPC_MESSAGE_HANDLER(CdmHostMsg_CreateSessionAndGenerateRequest,
182 OnCreateSessionAndGenerateRequest)
183 IPC_MESSAGE_HANDLER(CdmHostMsg_UpdateSession, OnUpdateSession)
184 IPC_MESSAGE_HANDLER(CdmHostMsg_CloseSession, OnCloseSession)
185 IPC_MESSAGE_HANDLER(CdmHostMsg_DestroyCdm, OnDestroyCdm)
186 IPC_MESSAGE_UNHANDLED(handled = false)
187 IPC_END_MESSAGE_MAP()
188 return handled;
191 media::BrowserCdm* BrowserCdmManager::GetCdm(int render_frame_id,
192 int cdm_id) const {
193 DCHECK(task_runner_->RunsTasksOnCurrentThread());
194 return cdm_map_.get(GetId(render_frame_id, cdm_id));
197 void BrowserCdmManager::RenderFrameDeleted(int render_frame_id) {
198 if (!task_runner_->RunsTasksOnCurrentThread()) {
199 task_runner_->PostTask(
200 FROM_HERE,
201 base::Bind(&BrowserCdmManager::RemoveAllCdmForFrame,
202 this, render_frame_id));
203 return;
205 RemoveAllCdmForFrame(render_frame_id);
208 void BrowserCdmManager::ResolvePromise(int render_frame_id,
209 int cdm_id,
210 uint32_t promise_id) {
211 Send(new CdmMsg_ResolvePromise(render_frame_id, cdm_id, promise_id));
214 void BrowserCdmManager::ResolvePromiseWithSession(
215 int render_frame_id,
216 int cdm_id,
217 uint32_t promise_id,
218 const std::string& session_id) {
219 if (session_id.length() > media::limits::kMaxSessionIdLength) {
220 RejectPromise(render_frame_id, cdm_id, promise_id,
221 MediaKeys::INVALID_ACCESS_ERROR, 0,
222 "Session ID is too long.");
223 return;
226 Send(new CdmMsg_ResolvePromiseWithSession(render_frame_id, cdm_id, promise_id,
227 session_id));
230 void BrowserCdmManager::RejectPromise(int render_frame_id,
231 int cdm_id,
232 uint32_t promise_id,
233 media::MediaKeys::Exception exception,
234 uint32_t system_code,
235 const std::string& error_message) {
236 Send(new CdmMsg_RejectPromise(render_frame_id, cdm_id, promise_id, exception,
237 system_code, error_message));
240 void BrowserCdmManager::OnSessionMessage(
241 int render_frame_id,
242 int cdm_id,
243 const std::string& session_id,
244 media::MediaKeys::MessageType message_type,
245 const std::vector<uint8>& message,
246 const GURL& legacy_destination_url) {
247 GURL verified_gurl = legacy_destination_url;
248 if (!verified_gurl.is_valid() && !verified_gurl.is_empty()) {
249 DLOG(WARNING) << "SessionMessage legacy_destination_url is invalid : "
250 << legacy_destination_url.possibly_invalid_spec();
251 verified_gurl =
252 GURL::EmptyGURL(); // Replace invalid legacy_destination_url.
255 Send(new CdmMsg_SessionMessage(render_frame_id, cdm_id, session_id,
256 message_type, message, verified_gurl));
259 void BrowserCdmManager::OnSessionClosed(int render_frame_id,
260 int cdm_id,
261 const std::string& session_id) {
262 Send(new CdmMsg_SessionClosed(render_frame_id, cdm_id, session_id));
265 void BrowserCdmManager::OnLegacySessionError(
266 int render_frame_id,
267 int cdm_id,
268 const std::string& session_id,
269 MediaKeys::Exception exception_code,
270 uint32 system_code,
271 const std::string& error_message) {
272 Send(new CdmMsg_LegacySessionError(render_frame_id, cdm_id, session_id,
273 exception_code, system_code,
274 error_message));
277 void BrowserCdmManager::OnSessionKeysChange(int render_frame_id,
278 int cdm_id,
279 const std::string& session_id,
280 bool has_additional_usable_key,
281 media::CdmKeysInfo keys_info) {
282 std::vector<media::CdmKeyInformation> key_info_vector;
283 for (const auto& key_info : keys_info)
284 key_info_vector.push_back(*key_info);
285 Send(new CdmMsg_SessionKeysChange(render_frame_id, cdm_id, session_id,
286 has_additional_usable_key,
287 key_info_vector));
290 void BrowserCdmManager::OnSessionExpirationUpdate(
291 int render_frame_id,
292 int cdm_id,
293 const std::string& session_id,
294 const base::Time& new_expiry_time) {
295 Send(new CdmMsg_SessionExpirationUpdate(render_frame_id, cdm_id, session_id,
296 new_expiry_time));
299 void BrowserCdmManager::OnInitializeCdm(int render_frame_id,
300 int cdm_id,
301 const std::string& key_system,
302 const GURL& security_origin) {
303 if (key_system.size() > kMaxKeySystemLength) {
304 // This failure will be discovered and reported by OnCreateSession()
305 // as GetCdm() will return null.
306 NOTREACHED() << "Invalid key system: " << key_system;
307 return;
310 AddCdm(render_frame_id, cdm_id, key_system, security_origin);
313 void BrowserCdmManager::OnSetServerCertificate(
314 int render_frame_id,
315 int cdm_id,
316 uint32_t promise_id,
317 const std::vector<uint8_t>& certificate) {
318 DCHECK(task_runner_->RunsTasksOnCurrentThread());
320 scoped_ptr<SimplePromise> promise(
321 new SimplePromise(this, render_frame_id, cdm_id, promise_id));
323 BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
324 if (!cdm) {
325 promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found.");
326 return;
329 if (certificate.empty()) {
330 promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, "Empty certificate.");
331 return;
334 cdm->SetServerCertificate(&certificate[0], certificate.size(),
335 promise.Pass());
338 void BrowserCdmManager::OnCreateSessionAndGenerateRequest(
339 int render_frame_id,
340 int cdm_id,
341 uint32_t promise_id,
342 CdmHostMsg_CreateSession_InitDataType init_data_type,
343 const std::vector<uint8>& init_data) {
344 DCHECK(task_runner_->RunsTasksOnCurrentThread());
346 scoped_ptr<NewSessionPromise> promise(
347 new NewSessionPromise(this, render_frame_id, cdm_id, promise_id));
349 if (init_data.size() > kMaxInitDataLength) {
350 LOG(WARNING) << "InitData for ID: " << cdm_id
351 << " too long: " << init_data.size();
352 promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, "Init data too long.");
353 return;
356 media::EmeInitDataType eme_init_data_type;
357 switch (init_data_type) {
358 case INIT_DATA_TYPE_WEBM:
359 eme_init_data_type = media::EmeInitDataType::WEBM;
360 break;
361 #if defined(USE_PROPRIETARY_CODECS)
362 case INIT_DATA_TYPE_CENC:
363 eme_init_data_type = media::EmeInitDataType::CENC;
364 break;
365 #endif
366 default:
367 NOTREACHED();
368 promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0,
369 "Invalid init data type.");
370 return;
373 BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
374 if (!cdm) {
375 DLOG(WARNING) << "No CDM found for: " << render_frame_id << ", " << cdm_id;
376 promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found.");
377 return;
380 CheckPermissionStatus(
381 render_frame_id, cdm_id,
382 base::Bind(&BrowserCdmManager::CreateSessionAndGenerateRequestIfPermitted,
383 this, render_frame_id, cdm_id, eme_init_data_type, init_data,
384 base::Passed(&promise)));
387 void BrowserCdmManager::OnUpdateSession(int render_frame_id,
388 int cdm_id,
389 uint32_t promise_id,
390 const std::string& session_id,
391 const std::vector<uint8>& response) {
392 DCHECK(task_runner_->RunsTasksOnCurrentThread());
394 scoped_ptr<SimplePromise> promise(
395 new SimplePromise(this, render_frame_id, cdm_id, promise_id));
397 BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
398 if (!cdm) {
399 promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found.");
400 return;
403 if (response.size() > kMaxSessionResponseLength) {
404 LOG(WARNING) << "Response for ID " << cdm_id
405 << " is too long: " << response.size();
406 promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, "Response too long.");
407 return;
410 if (response.empty()) {
411 promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, "Response is empty.");
412 return;
415 cdm->UpdateSession(session_id, &response[0], response.size(), promise.Pass());
418 void BrowserCdmManager::OnCloseSession(int render_frame_id,
419 int cdm_id,
420 uint32_t promise_id,
421 const std::string& session_id) {
422 DCHECK(task_runner_->RunsTasksOnCurrentThread());
424 scoped_ptr<SimplePromise> promise(
425 new SimplePromise(this, render_frame_id, cdm_id, promise_id));
427 BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
428 if (!cdm) {
429 promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found.");
430 return;
433 cdm->CloseSession(session_id, promise.Pass());
436 void BrowserCdmManager::OnDestroyCdm(int render_frame_id, int cdm_id) {
437 DCHECK(task_runner_->RunsTasksOnCurrentThread());
438 RemoveCdm(GetId(render_frame_id, cdm_id));
441 // Use a weak pointer here instead of |this| to avoid circular references.
442 #define BROWSER_CDM_MANAGER_CB(func) \
443 base::Bind(&BrowserCdmManager::func, weak_ptr_factory_.GetWeakPtr(), \
444 render_frame_id, cdm_id)
446 void BrowserCdmManager::AddCdm(int render_frame_id,
447 int cdm_id,
448 const std::string& key_system,
449 const GURL& security_origin) {
450 DCHECK(task_runner_->RunsTasksOnCurrentThread());
451 DCHECK(!GetCdm(render_frame_id, cdm_id));
453 bool use_secure_surface = false;
455 #if defined(OS_ANDROID)
456 // TODO(sandersd): Pass the security level from key system instead.
457 // http://crbug.com/467779
458 RenderFrameHost* rfh =
459 RenderFrameHost::FromID(render_process_id_, render_frame_id);
460 WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
461 if (web_contents) {
462 content::RendererPreferences* prefs =
463 web_contents->GetMutableRendererPrefs();
464 use_secure_surface = prefs->use_video_overlay_for_embedded_encrypted_video;
466 #endif
468 scoped_ptr<BrowserCdm> cdm(media::CreateBrowserCdm(
469 key_system, use_secure_surface, BROWSER_CDM_MANAGER_CB(OnSessionMessage),
470 BROWSER_CDM_MANAGER_CB(OnSessionClosed),
471 BROWSER_CDM_MANAGER_CB(OnLegacySessionError),
472 BROWSER_CDM_MANAGER_CB(OnSessionKeysChange),
473 BROWSER_CDM_MANAGER_CB(OnSessionExpirationUpdate)));
475 if (!cdm) {
476 // This failure will be discovered and reported by
477 // OnCreateSessionAndGenerateRequest() as GetCdm() will return null.
478 DVLOG(1) << "failed to create CDM.";
479 return;
482 uint64 id = GetId(render_frame_id, cdm_id);
483 cdm_map_.add(id, cdm.Pass());
484 cdm_security_origin_map_[id] = security_origin;
487 void BrowserCdmManager::RemoveAllCdmForFrame(int render_frame_id) {
488 DCHECK(task_runner_->RunsTasksOnCurrentThread());
490 std::vector<uint64> ids_to_remove;
491 for (CdmMap::iterator it = cdm_map_.begin(); it != cdm_map_.end(); ++it) {
492 if (IdBelongsToFrame(it->first, render_frame_id))
493 ids_to_remove.push_back(it->first);
496 for (size_t i = 0; i < ids_to_remove.size(); ++i)
497 RemoveCdm(ids_to_remove[i]);
500 void BrowserCdmManager::RemoveCdm(uint64 id) {
501 DCHECK(task_runner_->RunsTasksOnCurrentThread());
503 cdm_map_.erase(id);
504 cdm_security_origin_map_.erase(id);
507 void BrowserCdmManager::CheckPermissionStatus(
508 int render_frame_id,
509 int cdm_id,
510 const PermissionStatusCB& permission_status_cb) {
511 // Always called on |task_runner_|, which may not be on the UI thread.
512 DCHECK(task_runner_->RunsTasksOnCurrentThread());
514 GURL security_origin;
515 std::map<uint64, GURL>::const_iterator iter =
516 cdm_security_origin_map_.find(GetId(render_frame_id, cdm_id));
517 DCHECK(iter != cdm_security_origin_map_.end());
518 if (iter != cdm_security_origin_map_.end())
519 security_origin = iter->second;
521 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
522 BrowserThread::PostTask(
523 BrowserThread::UI, FROM_HERE,
524 base::Bind(&BrowserCdmManager::CheckPermissionStatusOnUIThread, this,
525 render_frame_id, security_origin, permission_status_cb));
526 } else {
527 CheckPermissionStatusOnUIThread(render_frame_id, security_origin,
528 permission_status_cb);
532 // Note: This function runs on the UI thread, which may be different from
533 // |task_runner_|. Be careful about thread safety!
534 void BrowserCdmManager::CheckPermissionStatusOnUIThread(
535 int render_frame_id,
536 const GURL& security_origin,
537 const base::Callback<void(bool)>& permission_status_cb) {
538 DCHECK_CURRENTLY_ON(BrowserThread::UI);
540 RenderFrameHost* rfh =
541 RenderFrameHost::FromID(render_process_id_, render_frame_id);
542 WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
543 PermissionManager* permission_manager =
544 web_contents->GetBrowserContext()->GetPermissionManager();
545 if (!permission_manager) {
546 permission_status_cb.Run(false);
547 return;
550 PermissionStatus permission_status = permission_manager->GetPermissionStatus(
551 content::PermissionType::PROTECTED_MEDIA_IDENTIFIER,
552 security_origin,
553 web_contents->GetLastCommittedURL().GetOrigin());
555 bool allowed = (permission_status == PERMISSION_STATUS_GRANTED);
556 if (!task_runner_->RunsTasksOnCurrentThread()) {
557 task_runner_->PostTask(FROM_HERE,
558 base::Bind(permission_status_cb, allowed));
559 } else {
560 permission_status_cb.Run(allowed);
564 void BrowserCdmManager::CreateSessionAndGenerateRequestIfPermitted(
565 int render_frame_id,
566 int cdm_id,
567 media::EmeInitDataType init_data_type,
568 const std::vector<uint8>& init_data,
569 scoped_ptr<media::NewSessionCdmPromise> promise,
570 bool permission_was_allowed) {
571 DCHECK(task_runner_->RunsTasksOnCurrentThread());
573 if (!permission_was_allowed) {
574 promise->reject(MediaKeys::NOT_SUPPORTED_ERROR, 0, "Permission denied.");
575 return;
578 BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
579 if (!cdm) {
580 promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found.");
581 return;
584 // Only the temporary session type is supported in browser CDM path.
585 // TODO(xhwang): Add SessionType support if needed.
586 cdm->CreateSessionAndGenerateRequest(media::MediaKeys::TEMPORARY_SESSION,
587 init_data_type, &init_data[0],
588 init_data.size(), promise.Pass());
591 } // namespace content