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/command_line.h" 8 #include "content/common/media/cdm_messages.h" 9 #include "content/public/browser/content_browser_client.h" 10 #include "content/public/browser/render_frame_host.h" 11 #include "content/public/browser/render_process_host.h" 12 #include "content/public/browser/render_view_host.h" 13 #include "content/public/browser/web_contents.h" 14 #include "content/public/common/content_switches.h" 15 #include "media/base/browser_cdm.h" 16 #include "media/base/browser_cdm_factory.h" 17 #include "media/base/media_switches.h" 18 19 namespace content { 20 21 using media::BrowserCdm; 22 using media::MediaKeys; 23 24 // Maximum lengths for various EME API parameters. These are checks to 25 // prevent unnecessarily large parameters from being passed around, and the 26 // lengths are somewhat arbitrary as the EME spec doesn't specify any limits. 27 const size_t kMaxInitDataLength = 64 * 1024; // 64 KB 28 const size_t kMaxSessionResponseLength = 64 * 1024; // 64 KB 29 const size_t kMaxKeySystemLength = 256; 30 31 // static 32 BrowserCdmManager* BrowserCdmManager::Create(RenderFrameHost* rfh) { 33 return new BrowserCdmManager(rfh); 34 } 35 36 BrowserCdmManager::BrowserCdmManager(RenderFrameHost* render_frame_host) 37 : render_frame_host_(render_frame_host), 38 web_contents_(WebContents::FromRenderFrameHost(render_frame_host)), 39 weak_ptr_factory_(this) { 40 } 41 42 BrowserCdmManager::~BrowserCdmManager() { 43 // During the tear down process, OnDestroyCdm() may or may not be called 44 // (e.g. WebContents may be destroyed before the render process is killed). So 45 // we cannot DCHECK(cdm_map_.empty()) here. Instead, all CDMs in |cdm_map_| 46 // will be destroyed here because they are owned by BrowserCdmManager. 47 } 48 49 BrowserCdm* BrowserCdmManager::GetCdm(int cdm_id) { 50 return cdm_map_.get(cdm_id); 51 } 52 53 void BrowserCdmManager::OnSessionCreated( 54 int cdm_id, 55 uint32 session_id, 56 const std::string& web_session_id) { 57 Send(new CdmMsg_SessionCreated( 58 RoutingID(), cdm_id, session_id, web_session_id)); 59 } 60 61 void BrowserCdmManager::OnSessionMessage( 62 int cdm_id, 63 uint32 session_id, 64 const std::vector<uint8>& message, 65 const GURL& destination_url) { 66 GURL verified_gurl = destination_url; 67 if (!verified_gurl.is_valid() && !verified_gurl.is_empty()) { 68 DLOG(WARNING) << "SessionMessage destination_url is invalid : " 69 << destination_url.possibly_invalid_spec(); 70 verified_gurl = GURL::EmptyGURL(); // Replace invalid destination_url. 71 } 72 73 Send(new CdmMsg_SessionMessage( 74 RoutingID(), cdm_id, session_id, message, verified_gurl)); 75 } 76 77 void BrowserCdmManager::OnSessionReady(int cdm_id, uint32 session_id) { 78 Send(new CdmMsg_SessionReady(RoutingID(), cdm_id, session_id)); 79 } 80 81 void BrowserCdmManager::OnSessionClosed(int cdm_id, uint32 session_id) { 82 Send(new CdmMsg_SessionClosed(RoutingID(), cdm_id, session_id)); 83 } 84 85 void BrowserCdmManager::OnSessionError(int cdm_id, 86 uint32 session_id, 87 MediaKeys::KeyError error_code, 88 uint32 system_code) { 89 Send(new CdmMsg_SessionError( 90 RoutingID(), cdm_id, session_id, error_code, system_code)); 91 } 92 93 void BrowserCdmManager::OnInitializeCdm(int cdm_id, 94 const std::string& key_system, 95 const GURL& security_origin) { 96 if (key_system.size() > kMaxKeySystemLength) { 97 // This failure will be discovered and reported by OnCreateSession() 98 // as GetCdm() will return null. 99 NOTREACHED() << "Invalid key system: " << key_system; 100 return; 101 } 102 103 AddCdm(cdm_id, key_system, security_origin); 104 } 105 106 void BrowserCdmManager::OnCreateSession( 107 int cdm_id, 108 uint32 session_id, 109 CdmHostMsg_CreateSession_ContentType content_type, 110 const std::vector<uint8>& init_data) { 111 if (init_data.size() > kMaxInitDataLength) { 112 LOG(WARNING) << "InitData for ID: " << cdm_id 113 << " too long: " << init_data.size(); 114 OnSessionError(cdm_id, session_id, MediaKeys::kUnknownError, 0); 115 return; 116 } 117 118 // Convert the session content type into a MIME type. "audio" and "video" 119 // don't matter, so using "video" for the MIME type. 120 // Ref: 121 // https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-createsession 122 std::string mime_type; 123 switch (content_type) { 124 case CREATE_SESSION_TYPE_WEBM: 125 mime_type = "video/webm"; 126 break; 127 case CREATE_SESSION_TYPE_MP4: 128 mime_type = "video/mp4"; 129 break; 130 default: 131 NOTREACHED(); 132 return; 133 } 134 135 #if defined(OS_ANDROID) 136 if (CommandLine::ForCurrentProcess() 137 ->HasSwitch(switches::kDisableInfobarForProtectedMediaIdentifier)) { 138 CreateSessionIfPermitted(cdm_id, session_id, mime_type, init_data, true); 139 return; 140 } 141 #endif 142 143 BrowserCdm* cdm = GetCdm(cdm_id); 144 if (!cdm) { 145 DLOG(WARNING) << "No CDM for ID " << cdm_id << " found"; 146 OnSessionError(cdm_id, session_id, MediaKeys::kUnknownError, 0); 147 return; 148 } 149 150 std::map<int, GURL>::const_iterator iter = 151 cdm_security_origin_map_.find(cdm_id); 152 if (iter == cdm_security_origin_map_.end()) { 153 NOTREACHED(); 154 OnSessionError(cdm_id, session_id, MediaKeys::kUnknownError, 0); 155 return; 156 } 157 158 base::Closure cancel_callback; 159 GetContentClient()->browser()->RequestProtectedMediaIdentifierPermission( 160 web_contents_, 161 iter->second, 162 base::Bind(&BrowserCdmManager::CreateSessionIfPermitted, 163 weak_ptr_factory_.GetWeakPtr(), 164 cdm_id, 165 session_id, 166 mime_type, 167 init_data), 168 &cancel_callback); 169 if (!cancel_callback.is_null()) 170 cdm_cancel_permision_map_[cdm_id] = cancel_callback; 171 } 172 173 void BrowserCdmManager::OnUpdateSession( 174 int cdm_id, 175 uint32 session_id, 176 const std::vector<uint8>& response) { 177 BrowserCdm* cdm = GetCdm(cdm_id); 178 if (!cdm) { 179 DLOG(WARNING) << "No CDM for ID " << cdm_id << " found"; 180 OnSessionError(cdm_id, session_id, MediaKeys::kUnknownError, 0); 181 return; 182 } 183 184 if (response.size() > kMaxSessionResponseLength) { 185 LOG(WARNING) << "Response for ID " << cdm_id 186 << " is too long: " << response.size(); 187 OnSessionError(cdm_id, session_id, MediaKeys::kUnknownError, 0); 188 return; 189 } 190 191 cdm->UpdateSession(session_id, &response[0], response.size()); 192 } 193 194 void BrowserCdmManager::OnReleaseSession(int cdm_id, uint32 session_id) { 195 BrowserCdm* cdm = GetCdm(cdm_id); 196 if (!cdm) { 197 DLOG(WARNING) << "No CDM for ID " << cdm_id << " found"; 198 OnSessionError(cdm_id, session_id, MediaKeys::kUnknownError, 0); 199 return; 200 } 201 202 cdm->ReleaseSession(session_id); 203 } 204 205 void BrowserCdmManager::OnDestroyCdm(int cdm_id) { 206 BrowserCdm* cdm = GetCdm(cdm_id); 207 if (!cdm) 208 return; 209 210 CancelAllPendingSessionCreations(cdm_id); 211 RemoveCdm(cdm_id); 212 } 213 214 void BrowserCdmManager::CancelAllPendingSessionCreations(int cdm_id) { 215 if (cdm_cancel_permision_map_.count(cdm_id)) { 216 cdm_cancel_permision_map_[cdm_id].Run(); 217 cdm_cancel_permision_map_.erase(cdm_id); 218 } 219 } 220 221 void BrowserCdmManager::AddCdm(int cdm_id, 222 const std::string& key_system, 223 const GURL& security_origin) { 224 DCHECK(!GetCdm(cdm_id)); 225 base::WeakPtr<BrowserCdmManager> weak_this = weak_ptr_factory_.GetWeakPtr(); 226 scoped_ptr<BrowserCdm> cdm(media::CreateBrowserCdm( 227 key_system, 228 base::Bind(&BrowserCdmManager::OnSessionCreated, weak_this, cdm_id), 229 base::Bind(&BrowserCdmManager::OnSessionMessage, weak_this, cdm_id), 230 base::Bind(&BrowserCdmManager::OnSessionReady, weak_this, cdm_id), 231 base::Bind(&BrowserCdmManager::OnSessionClosed, weak_this, cdm_id), 232 base::Bind(&BrowserCdmManager::OnSessionError, weak_this, cdm_id))); 233 234 if (!cdm) { 235 // This failure will be discovered and reported by OnCreateSession() 236 // as GetCdm() will return null. 237 DVLOG(1) << "failed to create CDM."; 238 return; 239 } 240 241 cdm_map_.add(cdm_id, cdm.Pass()); 242 cdm_security_origin_map_[cdm_id] = security_origin; 243 } 244 245 void BrowserCdmManager::RemoveCdm(int cdm_id) { 246 // TODO(xhwang): Detach CDM from the player it's set to. In prefixed 247 // EME implementation the current code is fine because we always destroy the 248 // player before we destroy the DrmBridge. This will not always be the case 249 // in unprefixed EME implementation. 250 cdm_map_.erase(cdm_id); 251 cdm_security_origin_map_.erase(cdm_id); 252 cdm_cancel_permision_map_.erase(cdm_id); 253 } 254 255 int BrowserCdmManager::RoutingID() { 256 return render_frame_host_->GetRoutingID(); 257 } 258 259 bool BrowserCdmManager::Send(IPC::Message* msg) { 260 return render_frame_host_->Send(msg); 261 } 262 263 void BrowserCdmManager::CreateSessionIfPermitted( 264 int cdm_id, 265 uint32 session_id, 266 const std::string& content_type, 267 const std::vector<uint8>& init_data, 268 bool permitted) { 269 cdm_cancel_permision_map_.erase(cdm_id); 270 if (!permitted) { 271 OnSessionError(cdm_id, session_id, MediaKeys::kUnknownError, 0); 272 return; 273 } 274 275 BrowserCdm* cdm = GetCdm(cdm_id); 276 if (!cdm) { 277 DLOG(WARNING) << "No CDM for ID: " << cdm_id << " found"; 278 OnSessionError(cdm_id, session_id, MediaKeys::kUnknownError, 0); 279 return; 280 } 281 282 // This could fail, in which case a SessionError will be fired. 283 cdm->CreateSession(session_id, content_type, &init_data[0], init_data.size()); 284 } 285 286 } // namespace content 287