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.
4
5#include "content/browser/media/cdm/browser_cdm_manager.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/lazy_instance.h"
10#include "base/task_runner.h"
11#include "content/common/media/cdm_messages.h"
12#include "content/public/browser/browser_thread.h"
13#include "content/public/browser/content_browser_client.h"
14#include "content/public/browser/render_frame_host.h"
15#include "content/public/browser/render_process_host.h"
16#include "content/public/browser/render_view_host.h"
17#include "content/public/browser/web_contents.h"
18#include "content/public/common/content_switches.h"
19#include "media/base/browser_cdm.h"
20#include "media/base/browser_cdm_factory.h"
21#include "media/base/media_switches.h"
22
23namespace content {
24
25using media::BrowserCdm;
26using media::MediaKeys;
27
28// Maximum lengths for various EME API parameters. These are checks to
29// prevent unnecessarily large parameters from being passed around, and the
30// lengths are somewhat arbitrary as the EME spec doesn't specify any limits.
31const size_t kMaxInitDataLength = 64 * 1024;  // 64 KB
32const size_t kMaxSessionResponseLength = 64 * 1024;  // 64 KB
33const size_t kMaxKeySystemLength = 256;
34
35// The ID used in this class is a concatenation of |render_frame_id| and
36// |cdm_id|, i.e. (render_frame_id << 32) + cdm_id.
37
38static uint64 GetId(int render_frame_id, int cdm_id) {
39  return (static_cast<uint64>(render_frame_id) << 32) +
40         static_cast<uint64>(cdm_id);
41}
42
43static bool IdBelongsToFrame(uint64 id, int render_frame_id) {
44  return (id >> 32) == static_cast<uint64>(render_frame_id);
45}
46
47// Render process ID to BrowserCdmManager map.
48typedef std::map<int, BrowserCdmManager*> BrowserCdmManagerMap;
49base::LazyInstance<BrowserCdmManagerMap> g_browser_cdm_manager_map =
50    LAZY_INSTANCE_INITIALIZER;
51
52BrowserCdmManager* BrowserCdmManager::FromProcess(int render_process_id) {
53  DCHECK_CURRENTLY_ON(BrowserThread::UI);
54
55  if (!g_browser_cdm_manager_map.Get().count(render_process_id))
56    return NULL;
57
58  return g_browser_cdm_manager_map.Get()[render_process_id];
59}
60
61BrowserCdmManager::BrowserCdmManager(
62    int render_process_id,
63    const scoped_refptr<base::TaskRunner>& task_runner)
64    : BrowserMessageFilter(CdmMsgStart),
65      render_process_id_(render_process_id),
66      task_runner_(task_runner) {
67  DCHECK_CURRENTLY_ON(BrowserThread::UI);
68
69  if (!task_runner_) {
70    task_runner_ =
71        BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI);
72  }
73
74  // This may overwrite an existing entry of |render_process_id| if the
75  // previous process crashed and didn't cleanup its child frames. For example,
76  // see FrameTreeBrowserTest.FrameTreeAfterCrash test.
77  g_browser_cdm_manager_map.Get()[render_process_id] = this;
78}
79
80BrowserCdmManager::~BrowserCdmManager() {
81  DCHECK_CURRENTLY_ON(BrowserThread::UI);
82  DCHECK(g_browser_cdm_manager_map.Get().count(render_process_id_));
83
84  g_browser_cdm_manager_map.Get().erase(render_process_id_);
85}
86
87// Makes sure BrowserCdmManager is always deleted on the Browser UI thread.
88void BrowserCdmManager::OnDestruct() const {
89  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
90    delete this;
91  } else {
92    BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
93  }
94}
95
96base::TaskRunner* BrowserCdmManager::OverrideTaskRunnerForMessage(
97    const IPC::Message& message) {
98  // Only handles CDM messages.
99  if (IPC_MESSAGE_CLASS(message) != CdmMsgStart)
100    return NULL;
101
102  return task_runner_.get();
103}
104
105bool BrowserCdmManager::OnMessageReceived(const IPC::Message& msg) {
106  DCHECK(task_runner_->RunsTasksOnCurrentThread());
107  bool handled = true;
108  IPC_BEGIN_MESSAGE_MAP(BrowserCdmManager, msg)
109    IPC_MESSAGE_HANDLER(CdmHostMsg_InitializeCdm, OnInitializeCdm)
110    IPC_MESSAGE_HANDLER(CdmHostMsg_CreateSession, OnCreateSession)
111    IPC_MESSAGE_HANDLER(CdmHostMsg_UpdateSession, OnUpdateSession)
112    IPC_MESSAGE_HANDLER(CdmHostMsg_ReleaseSession, OnReleaseSession)
113    IPC_MESSAGE_HANDLER(CdmHostMsg_DestroyCdm, OnDestroyCdm)
114    IPC_MESSAGE_UNHANDLED(handled = false)
115  IPC_END_MESSAGE_MAP()
116  return handled;
117}
118
119media::BrowserCdm* BrowserCdmManager::GetCdm(int render_frame_id, int cdm_id) {
120  DCHECK(task_runner_->RunsTasksOnCurrentThread());
121  return cdm_map_.get(GetId(render_frame_id, cdm_id));
122}
123
124void BrowserCdmManager::RenderFrameDeleted(int render_frame_id) {
125  DCHECK(task_runner_->RunsTasksOnCurrentThread());
126
127  std::vector<uint64> ids_to_remove;
128  for (CdmMap::iterator it = cdm_map_.begin(); it != cdm_map_.end(); ++it) {
129    if (IdBelongsToFrame(it->first, render_frame_id))
130      ids_to_remove.push_back(it->first);
131  }
132
133  for (size_t i = 0; i < ids_to_remove.size(); ++i)
134    RemoveCdm(ids_to_remove[i]);
135}
136
137void BrowserCdmManager::OnSessionCreated(int render_frame_id,
138                                         int cdm_id,
139                                         uint32 session_id,
140                                         const std::string& web_session_id) {
141  Send(new CdmMsg_SessionCreated(
142      render_frame_id, cdm_id, session_id, web_session_id));
143}
144
145void BrowserCdmManager::OnSessionMessage(int render_frame_id,
146                                         int cdm_id,
147                                         uint32 session_id,
148                                         const std::vector<uint8>& message,
149                                         const GURL& destination_url) {
150  GURL verified_gurl = destination_url;
151  if (!verified_gurl.is_valid() && !verified_gurl.is_empty()) {
152    DLOG(WARNING) << "SessionMessage destination_url is invalid : "
153                  << destination_url.possibly_invalid_spec();
154    verified_gurl = GURL::EmptyGURL();  // Replace invalid destination_url.
155  }
156
157  Send(new CdmMsg_SessionMessage(
158      render_frame_id, cdm_id, session_id, message, verified_gurl));
159}
160
161void BrowserCdmManager::OnSessionReady(int render_frame_id,
162                                       int cdm_id,
163                                       uint32 session_id) {
164  Send(new CdmMsg_SessionReady(render_frame_id, cdm_id, session_id));
165}
166
167void BrowserCdmManager::OnSessionClosed(int render_frame_id,
168                                        int cdm_id,
169                                        uint32 session_id) {
170  Send(new CdmMsg_SessionClosed(render_frame_id, cdm_id, session_id));
171}
172
173void BrowserCdmManager::OnSessionError(int render_frame_id,
174                                       int cdm_id,
175                                       uint32 session_id,
176                                       MediaKeys::KeyError error_code,
177                                       uint32 system_code) {
178  Send(new CdmMsg_SessionError(
179      render_frame_id, cdm_id, session_id, error_code, system_code));
180}
181
182void BrowserCdmManager::OnInitializeCdm(int render_frame_id,
183                                        int cdm_id,
184                                        const std::string& key_system,
185                                        const GURL& security_origin) {
186  if (key_system.size() > kMaxKeySystemLength) {
187    // This failure will be discovered and reported by OnCreateSession()
188    // as GetCdm() will return null.
189    NOTREACHED() << "Invalid key system: " << key_system;
190    return;
191  }
192
193  AddCdm(render_frame_id, cdm_id, key_system, security_origin);
194}
195
196void BrowserCdmManager::OnCreateSession(
197    int render_frame_id,
198    int cdm_id,
199    uint32 session_id,
200    CdmHostMsg_CreateSession_ContentType content_type,
201    const std::vector<uint8>& init_data) {
202  if (init_data.size() > kMaxInitDataLength) {
203    LOG(WARNING) << "InitData for ID: " << cdm_id
204                 << " too long: " << init_data.size();
205    SendSessionError(render_frame_id, cdm_id, session_id);
206    return;
207  }
208
209  // Convert the session content type into a MIME type. "audio" and "video"
210  // don't matter, so using "video" for the MIME type.
211  // Ref:
212  // https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-createsession
213  std::string mime_type;
214  switch (content_type) {
215    case CREATE_SESSION_TYPE_WEBM:
216      mime_type = "video/webm";
217      break;
218    case CREATE_SESSION_TYPE_MP4:
219      mime_type = "video/mp4";
220      break;
221    default:
222      NOTREACHED();
223      return;
224  }
225
226#if defined(OS_ANDROID)
227  if (CommandLine::ForCurrentProcess()
228      ->HasSwitch(switches::kDisableInfobarForProtectedMediaIdentifier)) {
229    CreateSessionIfPermitted(
230        render_frame_id, cdm_id, session_id, mime_type, init_data, true);
231    return;
232  }
233#endif
234
235  BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
236  if (!cdm) {
237    DLOG(WARNING) << "No CDM found for: " << render_frame_id << ", " << cdm_id;
238    SendSessionError(render_frame_id, cdm_id, session_id);
239    return;
240  }
241
242  std::map<uint64, GURL>::const_iterator iter =
243      cdm_security_origin_map_.find(GetId(render_frame_id, cdm_id));
244  if (iter == cdm_security_origin_map_.end()) {
245    NOTREACHED();
246    SendSessionError(render_frame_id, cdm_id, session_id);
247    return;
248  }
249  GURL security_origin = iter->second;
250
251  RenderFrameHost* rfh =
252      RenderFrameHost::FromID(render_process_id_, render_frame_id);
253  WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
254  DCHECK(web_contents);
255
256  base::Closure cancel_callback;
257  GetContentClient()->browser()->RequestProtectedMediaIdentifierPermission(
258      web_contents,
259      security_origin,
260      base::Bind(&BrowserCdmManager::CreateSessionIfPermitted,
261                 this,
262                 render_frame_id, cdm_id, session_id,
263                 mime_type, init_data),
264      &cancel_callback);
265
266  if (cancel_callback.is_null())
267    return;
268  cdm_cancel_permission_map_[GetId(render_frame_id, cdm_id)] = cancel_callback;
269}
270
271void BrowserCdmManager::OnUpdateSession(
272    int render_frame_id,
273    int cdm_id,
274    uint32 session_id,
275    const std::vector<uint8>& response) {
276  BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
277  if (!cdm) {
278    DLOG(WARNING) << "No CDM found for: " << render_frame_id << ", " << cdm_id;
279    SendSessionError(render_frame_id, cdm_id, session_id);
280    return;
281  }
282
283  if (response.size() > kMaxSessionResponseLength) {
284    LOG(WARNING) << "Response for ID " << cdm_id
285                 << " is too long: " << response.size();
286    SendSessionError(render_frame_id, cdm_id, session_id);
287    return;
288  }
289
290  cdm->UpdateSession(session_id, &response[0], response.size());
291}
292
293void BrowserCdmManager::OnReleaseSession(int render_frame_id,
294                                         int cdm_id,
295                                         uint32 session_id) {
296  BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
297  if (!cdm) {
298    DLOG(WARNING) << "No CDM found for: " << render_frame_id << ", " << cdm_id;
299    SendSessionError(render_frame_id, cdm_id, session_id);
300    return;
301  }
302
303  cdm->ReleaseSession(session_id);
304}
305
306void BrowserCdmManager::OnDestroyCdm(int render_frame_id, int cdm_id) {
307  RemoveCdm(GetId(render_frame_id, cdm_id));
308}
309
310void BrowserCdmManager::SendSessionError(int render_frame_id,
311                                         int cdm_id,
312                                         uint32 session_id) {
313  OnSessionError(
314        render_frame_id, cdm_id, session_id, MediaKeys::kUnknownError, 0);
315}
316
317#define BROWSER_CDM_MANAGER_CB(func) \
318  base::Bind(&BrowserCdmManager::func, this, render_frame_id, cdm_id)
319
320void BrowserCdmManager::AddCdm(int render_frame_id,
321                               int cdm_id,
322                               const std::string& key_system,
323                               const GURL& security_origin) {
324  DCHECK(task_runner_->RunsTasksOnCurrentThread());
325  DCHECK(!GetCdm(render_frame_id, cdm_id));
326
327  scoped_ptr<BrowserCdm> cdm(
328      media::CreateBrowserCdm(key_system,
329                              BROWSER_CDM_MANAGER_CB(OnSessionCreated),
330                              BROWSER_CDM_MANAGER_CB(OnSessionMessage),
331                              BROWSER_CDM_MANAGER_CB(OnSessionReady),
332                              BROWSER_CDM_MANAGER_CB(OnSessionClosed),
333                              BROWSER_CDM_MANAGER_CB(OnSessionError)));
334
335  if (!cdm) {
336    // This failure will be discovered and reported by OnCreateSession()
337    // as GetCdm() will return null.
338    DVLOG(1) << "failed to create CDM.";
339    return;
340  }
341
342  uint64 id = GetId(render_frame_id, cdm_id);
343  cdm_map_.add(id, cdm.Pass());
344  cdm_security_origin_map_[id] = security_origin;
345}
346
347void BrowserCdmManager::RemoveCdm(uint64 id) {
348  DCHECK(task_runner_->RunsTasksOnCurrentThread());
349
350  cdm_map_.erase(id);
351  cdm_security_origin_map_.erase(id);
352  if (cdm_cancel_permission_map_.count(id)) {
353    cdm_cancel_permission_map_[id].Run();
354    cdm_cancel_permission_map_.erase(id);
355  }
356}
357
358void BrowserCdmManager::CreateSessionIfPermitted(
359    int render_frame_id,
360    int cdm_id,
361    uint32 session_id,
362    const std::string& content_type,
363    const std::vector<uint8>& init_data,
364    bool permitted) {
365  cdm_cancel_permission_map_.erase(GetId(render_frame_id, cdm_id));
366  if (!permitted) {
367    SendSessionError(render_frame_id, cdm_id, session_id);
368    return;
369  }
370
371  BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
372  if (!cdm) {
373    DLOG(WARNING) << "No CDM found for: " << render_frame_id << ", " << cdm_id;
374    SendSessionError(render_frame_id, cdm_id, session_id);
375    return;
376  }
377
378  // This could fail, in which case a SessionError will be fired.
379  cdm->CreateSession(session_id, content_type, &init_data[0], init_data.size());
380}
381
382}  // namespace content
383