Home | History | Annotate | Download | only in crypto
      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/renderer/media/crypto/proxy_media_keys.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/basictypes.h"
     10 #include "base/logging.h"
     11 #include "base/stl_util.h"
     12 #include "content/renderer/media/crypto/key_systems.h"
     13 #include "content/renderer/media/crypto/renderer_cdm_manager.h"
     14 #include "media/base/cdm_promise.h"
     15 
     16 namespace content {
     17 
     18 scoped_ptr<ProxyMediaKeys> ProxyMediaKeys::Create(
     19     const std::string& key_system,
     20     const GURL& security_origin,
     21     RendererCdmManager* manager,
     22     const media::SessionMessageCB& session_message_cb,
     23     const media::SessionReadyCB& session_ready_cb,
     24     const media::SessionClosedCB& session_closed_cb,
     25     const media::SessionErrorCB& session_error_cb,
     26     const media::SessionKeysChangeCB& session_keys_change_cb,
     27     const media::SessionExpirationUpdateCB& session_expiration_update_cb) {
     28   DCHECK(manager);
     29 
     30   // TODO(jrummell): Add support for SessionKeysChangeCB and
     31   // SessionExpirationUpdateCB.
     32   scoped_ptr<ProxyMediaKeys> proxy_media_keys(
     33       new ProxyMediaKeys(manager,
     34                          session_message_cb,
     35                          session_ready_cb,
     36                          session_closed_cb,
     37                          session_error_cb));
     38   proxy_media_keys->InitializeCdm(key_system, security_origin);
     39   return proxy_media_keys.Pass();
     40 }
     41 
     42 ProxyMediaKeys::~ProxyMediaKeys() {
     43   manager_->DestroyCdm(cdm_id_);
     44   manager_->UnregisterMediaKeys(cdm_id_);
     45 
     46   // Reject any outstanding promises.
     47   for (PromiseMap::iterator it = session_id_to_promise_map_.begin();
     48        it != session_id_to_promise_map_.end();
     49        ++it) {
     50     it->second->reject(
     51         media::MediaKeys::NOT_SUPPORTED_ERROR, 0, "The operation was aborted.");
     52   }
     53   session_id_to_promise_map_.clear();
     54 }
     55 
     56 void ProxyMediaKeys::SetServerCertificate(
     57     const uint8* certificate_data,
     58     int certificate_data_length,
     59     scoped_ptr<media::SimpleCdmPromise> promise) {
     60   promise->reject(NOT_SUPPORTED_ERROR, 0, "Not yet implemented.");
     61 }
     62 
     63 void ProxyMediaKeys::CreateSession(
     64     const std::string& init_data_type,
     65     const uint8* init_data,
     66     int init_data_length,
     67     SessionType session_type,
     68     scoped_ptr<media::NewSessionCdmPromise> promise) {
     69   // TODO(xhwang): Move these checks up to blink and DCHECK here.
     70   // See http://crbug.com/342510
     71   CdmHostMsg_CreateSession_ContentType create_session_content_type;
     72   if (init_data_type == "cenc") {
     73     create_session_content_type = CREATE_SESSION_TYPE_MP4;
     74   } else if (init_data_type == "webm") {
     75     create_session_content_type = CREATE_SESSION_TYPE_WEBM;
     76   } else {
     77     DLOG(ERROR) << "Unsupported EME CreateSession content type of "
     78                 << init_data_type;
     79     promise->reject(
     80         NOT_SUPPORTED_ERROR,
     81         0,
     82         "Unsupported EME CreateSession init data type of " + init_data_type);
     83     return;
     84   }
     85 
     86   uint32 session_id = CreateSessionId();
     87   SavePromise(session_id, promise.PassAs<media::CdmPromise>());
     88   manager_->CreateSession(
     89       cdm_id_,
     90       session_id,
     91       create_session_content_type,
     92       std::vector<uint8>(init_data, init_data + init_data_length));
     93 }
     94 
     95 void ProxyMediaKeys::LoadSession(
     96     const std::string& web_session_id,
     97     scoped_ptr<media::NewSessionCdmPromise> promise) {
     98   // TODO(xhwang): Check key system and platform support for LoadSession in
     99   // blink and add NOTREACHED() here.
    100   DLOG(ERROR) << "ProxyMediaKeys doesn't support session loading.";
    101   promise->reject(NOT_SUPPORTED_ERROR, 0, "LoadSession() is not supported.");
    102 }
    103 
    104 void ProxyMediaKeys::UpdateSession(
    105     const std::string& web_session_id,
    106     const uint8* response,
    107     int response_length,
    108     scoped_ptr<media::SimpleCdmPromise> promise) {
    109   uint32 session_id = LookupSessionId(web_session_id);
    110   if (!session_id) {
    111     promise->reject(INVALID_ACCESS_ERROR, 0, "Session does not exist.");
    112     return;
    113   }
    114 
    115   SavePromise(session_id, promise.PassAs<media::CdmPromise>());
    116   manager_->UpdateSession(
    117       cdm_id_,
    118       session_id,
    119       std::vector<uint8>(response, response + response_length));
    120 }
    121 
    122 void ProxyMediaKeys::CloseSession(const std::string& web_session_id,
    123                                   scoped_ptr<media::SimpleCdmPromise> promise) {
    124   uint32 session_id = LookupSessionId(web_session_id);
    125   if (!session_id) {
    126     promise->reject(INVALID_ACCESS_ERROR, 0, "Session does not exist.");
    127     return;
    128   }
    129 
    130   SavePromise(session_id, promise.PassAs<media::CdmPromise>());
    131   manager_->ReleaseSession(cdm_id_, session_id);
    132 }
    133 
    134 void ProxyMediaKeys::RemoveSession(
    135     const std::string& web_session_id,
    136     scoped_ptr<media::SimpleCdmPromise> promise) {
    137   promise->reject(NOT_SUPPORTED_ERROR, 0, "Not yet implemented.");
    138 }
    139 
    140 void ProxyMediaKeys::GetUsableKeyIds(const std::string& web_session_id,
    141                                      scoped_ptr<media::KeyIdsPromise> promise) {
    142   promise->reject(NOT_SUPPORTED_ERROR, 0, "Not yet implemented.");
    143 }
    144 
    145 void ProxyMediaKeys::OnSessionCreated(uint32 session_id,
    146                                       const std::string& web_session_id) {
    147   AssignWebSessionId(session_id, web_session_id);
    148   scoped_ptr<media::CdmPromise> promise = TakePromise(session_id);
    149   if (promise) {
    150     media::NewSessionCdmPromise* session_promise(
    151         static_cast<media::NewSessionCdmPromise*>(promise.get()));
    152     session_promise->resolve(web_session_id);
    153   }
    154 }
    155 
    156 void ProxyMediaKeys::OnSessionMessage(uint32 session_id,
    157                                       const std::vector<uint8>& message,
    158                                       const GURL& destination_url) {
    159   session_message_cb_.Run(
    160       LookupWebSessionId(session_id), message, destination_url);
    161 }
    162 
    163 void ProxyMediaKeys::OnSessionReady(uint32 session_id) {
    164   scoped_ptr<media::CdmPromise> promise = TakePromise(session_id);
    165   if (promise) {
    166     media::SimpleCdmPromise* simple_promise(
    167         static_cast<media::SimpleCdmPromise*>(promise.get()));
    168     simple_promise->resolve();
    169   } else {
    170     // Still needed for keyadded.
    171     const std::string web_session_id = LookupWebSessionId(session_id);
    172     session_ready_cb_.Run(web_session_id);
    173   }
    174 }
    175 
    176 void ProxyMediaKeys::OnSessionClosed(uint32 session_id) {
    177   const std::string web_session_id = LookupWebSessionId(session_id);
    178   DropWebSessionId(web_session_id);
    179   scoped_ptr<media::CdmPromise> promise = TakePromise(session_id);
    180   if (promise) {
    181     media::SimpleCdmPromise* simple_promise(
    182         static_cast<media::SimpleCdmPromise*>(promise.get()));
    183     simple_promise->resolve();
    184   } else {
    185     // It is possible for the CDM to close a session independent of a
    186     // Release() request.
    187     session_closed_cb_.Run(web_session_id);
    188   }
    189 }
    190 
    191 void ProxyMediaKeys::OnSessionError(uint32 session_id,
    192                                     media::MediaKeys::KeyError error_code,
    193                                     uint32 system_code) {
    194   const std::string web_session_id = LookupWebSessionId(session_id);
    195   media::MediaKeys::Exception exception_code;
    196   switch (error_code) {
    197     case media::MediaKeys::kClientError:
    198       exception_code = media::MediaKeys::CLIENT_ERROR;
    199       break;
    200     case media::MediaKeys::kOutputError:
    201       exception_code = media::MediaKeys::OUTPUT_ERROR;
    202       break;
    203     case media::MediaKeys::kUnknownError:
    204     default:
    205       exception_code = media::MediaKeys::UNKNOWN_ERROR;
    206       break;
    207   }
    208 
    209   scoped_ptr<media::CdmPromise> promise = TakePromise(session_id);
    210   if (promise) {
    211     promise->reject(exception_code, system_code, std::string());
    212     return;
    213   }
    214 
    215   // Errors generally happen in response to a request, but it is possible
    216   // for something bad to happen in the CDM and it needs to tell the client.
    217   session_error_cb_.Run(
    218       web_session_id, exception_code, system_code, std::string());
    219 }
    220 
    221 int ProxyMediaKeys::GetCdmId() const {
    222   return cdm_id_;
    223 }
    224 
    225 ProxyMediaKeys::ProxyMediaKeys(
    226     RendererCdmManager* manager,
    227     const media::SessionMessageCB& session_message_cb,
    228     const media::SessionReadyCB& session_ready_cb,
    229     const media::SessionClosedCB& session_closed_cb,
    230     const media::SessionErrorCB& session_error_cb)
    231     : manager_(manager),
    232       session_message_cb_(session_message_cb),
    233       session_ready_cb_(session_ready_cb),
    234       session_closed_cb_(session_closed_cb),
    235       session_error_cb_(session_error_cb),
    236       next_session_id_(1) {
    237   cdm_id_ = manager->RegisterMediaKeys(this);
    238 }
    239 
    240 void ProxyMediaKeys::InitializeCdm(const std::string& key_system,
    241                                    const GURL& security_origin) {
    242   manager_->InitializeCdm(cdm_id_, this, key_system, security_origin);
    243 }
    244 
    245 uint32_t ProxyMediaKeys::CreateSessionId() {
    246   return next_session_id_++;
    247 }
    248 
    249 void ProxyMediaKeys::AssignWebSessionId(uint32_t session_id,
    250                                         const std::string& web_session_id) {
    251   DCHECK(!ContainsKey(web_session_to_session_id_map_, web_session_id));
    252   DCHECK(session_id);
    253   web_session_to_session_id_map_.insert(
    254       std::make_pair(web_session_id, session_id));
    255 }
    256 
    257 uint32_t ProxyMediaKeys::LookupSessionId(
    258     const std::string& web_session_id) const {
    259   SessionIdMap::const_iterator it =
    260       web_session_to_session_id_map_.find(web_session_id);
    261   return (it != web_session_to_session_id_map_.end()) ? it->second : 0;
    262 }
    263 
    264 std::string ProxyMediaKeys::LookupWebSessionId(uint32_t session_id) const {
    265   for (SessionIdMap::const_iterator it = web_session_to_session_id_map_.begin();
    266        it != web_session_to_session_id_map_.end();
    267        ++it) {
    268     if (it->second == session_id)
    269       return it->first;
    270   }
    271   // Possible to get an error creating a session, so no |web_session_id|
    272   // available.
    273   return std::string();
    274 }
    275 
    276 void ProxyMediaKeys::DropWebSessionId(const std::string& web_session_id) {
    277   web_session_to_session_id_map_.erase(web_session_id);
    278 }
    279 
    280 void ProxyMediaKeys::SavePromise(uint32_t session_id,
    281                                  scoped_ptr<media::CdmPromise> promise) {
    282   // Should only be one promise outstanding for any |session_id|.
    283   DCHECK(!ContainsKey(session_id_to_promise_map_, session_id));
    284   session_id_to_promise_map_.add(session_id, promise.Pass());
    285 }
    286 
    287 scoped_ptr<media::CdmPromise> ProxyMediaKeys::TakePromise(uint32_t session_id) {
    288   PromiseMap::iterator it = session_id_to_promise_map_.find(session_id);
    289   // May not be a promise associated with this session for asynchronous events.
    290   if (it == session_id_to_promise_map_.end())
    291     return scoped_ptr<media::CdmPromise>();
    292   return session_id_to_promise_map_.take_and_erase(it);
    293 }
    294 
    295 }  // namespace content
    296