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