Home | History | Annotate | Download | only in cdm
      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 
     23 namespace content {
     24 
     25 using media::BrowserCdm;
     26 using 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.
     31 const size_t kMaxInitDataLength = 64 * 1024;  // 64 KB
     32 const size_t kMaxSessionResponseLength = 64 * 1024;  // 64 KB
     33 const 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 
     38 static 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 
     43 static 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.
     48 typedef std::map<int, BrowserCdmManager*> BrowserCdmManagerMap;
     49 base::LazyInstance<BrowserCdmManagerMap> g_browser_cdm_manager_map =
     50     LAZY_INSTANCE_INITIALIZER;
     51 
     52 BrowserCdmManager* 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 
     61 BrowserCdmManager::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 
     80 BrowserCdmManager::~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.
     88 void 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 
     96 base::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 
    105 bool 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 
    119 media::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 
    124 void 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 
    137 void 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 
    145 void 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 
    161 void 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 
    167 void 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 
    173 void 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 
    182 void 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 
    196 void 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 
    271 void 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 
    293 void 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 
    306 void BrowserCdmManager::OnDestroyCdm(int render_frame_id, int cdm_id) {
    307   RemoveCdm(GetId(render_frame_id, cdm_id));
    308 }
    309 
    310 void 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 
    320 void 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 
    347 void 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 
    358 void 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