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