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"
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"
34 using media::BrowserCdm
;
35 using media::MediaKeys
;
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
...> {
62 CdmPromiseInternal(BrowserCdmManager
* manager
,
67 render_frame_id_(render_frame_id
),
69 promise_id_(promise_id
) {
73 ~CdmPromiseInternal() final
{}
75 // CdmPromiseTemplate<> implementation.
76 void resolve(const T
&... result
) final
;
78 void reject(MediaKeys::Exception exception
,
80 const std::string
& error_message
) final
{
82 manager_
->RejectPromise(render_frame_id_
, cdm_id_
, promise_id_
, exception
,
83 system_code
, error_message
);
87 using media::CdmPromiseTemplate
<T
...>::MarkPromiseSettled
;
89 BrowserCdmManager
* const manager_
;
90 const int render_frame_id_
;
92 const uint32_t promise_id_
;
96 void CdmPromiseInternal
<>::resolve() {
98 manager_
->ResolvePromise(render_frame_id_
, cdm_id_
, promise_id_
);
102 void CdmPromiseInternal
<std::string
>::resolve(const std::string
& session_id
) {
103 MarkPromiseSettled();
104 manager_
->ResolvePromiseWithSession(render_frame_id_
, cdm_id_
, promise_id_
,
108 typedef CdmPromiseInternal
<> SimplePromise
;
109 typedef CdmPromiseInternal
<std::string
> NewSessionPromise
;
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
))
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()) {
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
)) {
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
)
172 return task_runner_
.get();
175 bool BrowserCdmManager::OnMessageReceived(const IPC::Message
& msg
) {
176 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
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()
191 media::BrowserCdm
* BrowserCdmManager::GetCdm(int render_frame_id
,
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(
201 base::Bind(&BrowserCdmManager::RemoveAllCdmForFrame
,
202 this, render_frame_id
));
205 RemoveAllCdmForFrame(render_frame_id
);
208 void BrowserCdmManager::ResolvePromise(int render_frame_id
,
210 uint32_t promise_id
) {
211 Send(new CdmMsg_ResolvePromise(render_frame_id
, cdm_id
, promise_id
));
214 void BrowserCdmManager::ResolvePromiseWithSession(
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.");
226 Send(new CdmMsg_ResolvePromiseWithSession(render_frame_id
, cdm_id
, promise_id
,
230 void BrowserCdmManager::RejectPromise(int render_frame_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(
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();
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
,
261 const std::string
& session_id
) {
262 Send(new CdmMsg_SessionClosed(render_frame_id
, cdm_id
, session_id
));
265 void BrowserCdmManager::OnLegacySessionError(
268 const std::string
& session_id
,
269 MediaKeys::Exception exception_code
,
271 const std::string
& error_message
) {
272 Send(new CdmMsg_LegacySessionError(render_frame_id
, cdm_id
, session_id
,
273 exception_code
, system_code
,
277 void BrowserCdmManager::OnSessionKeysChange(int render_frame_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
,
290 void BrowserCdmManager::OnSessionExpirationUpdate(
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
,
299 void BrowserCdmManager::OnInitializeCdm(int render_frame_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
;
310 AddCdm(render_frame_id
, cdm_id
, key_system
, security_origin
);
313 void BrowserCdmManager::OnSetServerCertificate(
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
);
325 promise
->reject(MediaKeys::INVALID_STATE_ERROR
, 0, "CDM not found.");
329 if (certificate
.empty()) {
330 promise
->reject(MediaKeys::INVALID_ACCESS_ERROR
, 0, "Empty certificate.");
334 cdm
->SetServerCertificate(&certificate
[0], certificate
.size(),
338 void BrowserCdmManager::OnCreateSessionAndGenerateRequest(
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.");
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
;
361 #if defined(USE_PROPRIETARY_CODECS)
362 case INIT_DATA_TYPE_CENC
:
363 eme_init_data_type
= media::EmeInitDataType::CENC
;
368 promise
->reject(MediaKeys::INVALID_ACCESS_ERROR
, 0,
369 "Invalid init data type.");
373 BrowserCdm
* cdm
= GetCdm(render_frame_id
, cdm_id
);
375 DLOG(WARNING
) << "No CDM found for: " << render_frame_id
<< ", " << cdm_id
;
376 promise
->reject(MediaKeys::INVALID_STATE_ERROR
, 0, "CDM not found.");
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
,
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
);
399 promise
->reject(MediaKeys::INVALID_STATE_ERROR
, 0, "CDM not found.");
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.");
410 if (response
.empty()) {
411 promise
->reject(MediaKeys::INVALID_ACCESS_ERROR
, 0, "Response is empty.");
415 cdm
->UpdateSession(session_id
, &response
[0], response
.size(), promise
.Pass());
418 void BrowserCdmManager::OnCloseSession(int render_frame_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
);
429 promise
->reject(MediaKeys::INVALID_STATE_ERROR
, 0, "CDM not found.");
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
,
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
);
462 content::RendererPreferences
* prefs
=
463 web_contents
->GetMutableRendererPrefs();
464 use_secure_surface
= prefs
->use_video_overlay_for_embedded_encrypted_video
;
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
)));
476 // This failure will be discovered and reported by
477 // OnCreateSessionAndGenerateRequest() as GetCdm() will return null.
478 DVLOG(1) << "failed to create CDM.";
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());
504 cdm_security_origin_map_
.erase(id
);
507 void BrowserCdmManager::CheckPermissionStatus(
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
));
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(
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);
550 PermissionStatus permission_status
= permission_manager
->GetPermissionStatus(
551 content::PermissionType::PROTECTED_MEDIA_IDENTIFIER
,
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
));
560 permission_status_cb
.Run(allowed
);
564 void BrowserCdmManager::CreateSessionAndGenerateRequestIfPermitted(
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.");
578 BrowserCdm
* cdm
= GetCdm(render_frame_id
, cdm_id
);
580 promise
->reject(MediaKeys::INVALID_STATE_ERROR
, 0, "CDM not found.");
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