Upstreaming browser/ui/uikit_ui_util from iOS.
[chromium-blink-merge.git] / media / cdm / proxy_decryptor.cc
blob321a4a2c63e94beab75fed29df6d5f6e7ddd4021
1 // Copyright 2013 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.
5 #include "media/cdm/proxy_decryptor.h"
7 #include <cstring>
9 #include "base/bind.h"
10 #include "base/callback_helpers.h"
11 #include "base/logging.h"
12 #include "base/stl_util.h"
13 #include "base/strings/string_util.h"
14 #include "media/base/cdm_callback_promise.h"
15 #include "media/base/cdm_config.h"
16 #include "media/base/cdm_factory.h"
17 #include "media/base/cdm_key_information.h"
18 #include "media/base/key_systems.h"
19 #include "media/base/media_permission.h"
20 #include "media/cdm/json_web_key.h"
21 #include "media/cdm/key_system_names.h"
23 namespace media {
25 // Special system code to signal a closed persistent session in a SessionError()
26 // call. This is needed because there is no SessionClosed() call in the prefixed
27 // EME API.
28 const int kSessionClosedSystemCode = 29127;
30 ProxyDecryptor::PendingGenerateKeyRequestData::PendingGenerateKeyRequestData(
31 EmeInitDataType init_data_type,
32 const std::vector<uint8>& init_data)
33 : init_data_type(init_data_type), init_data(init_data) {
36 ProxyDecryptor::PendingGenerateKeyRequestData::
37 ~PendingGenerateKeyRequestData() {
40 ProxyDecryptor::ProxyDecryptor(MediaPermission* media_permission,
41 bool use_hw_secure_codecs,
42 const KeyAddedCB& key_added_cb,
43 const KeyErrorCB& key_error_cb,
44 const KeyMessageCB& key_message_cb)
45 : is_creating_cdm_(false),
46 #if defined(OS_CHROMEOS) || defined(OS_ANDROID)
47 media_permission_(media_permission),
48 #endif
49 use_hw_secure_codecs_(use_hw_secure_codecs),
50 key_added_cb_(key_added_cb),
51 key_error_cb_(key_error_cb),
52 key_message_cb_(key_message_cb),
53 is_clear_key_(false),
54 weak_ptr_factory_(this) {
55 DCHECK(media_permission);
56 DCHECK(!key_added_cb_.is_null());
57 DCHECK(!key_error_cb_.is_null());
58 DCHECK(!key_message_cb_.is_null());
61 ProxyDecryptor::~ProxyDecryptor() {
62 // Destroy the decryptor explicitly before destroying the plugin.
63 media_keys_.reset();
66 void ProxyDecryptor::CreateCdm(CdmFactory* cdm_factory,
67 const std::string& key_system,
68 const GURL& security_origin,
69 const CdmContextReadyCB& cdm_context_ready_cb) {
70 DVLOG(1) << __FUNCTION__ << ": key_system = " << key_system;
71 DCHECK(!is_creating_cdm_);
72 DCHECK(!media_keys_);
74 // TODO(sandersd): Trigger permissions check here and use it to determine
75 // distinctive identifier support, instead of always requiring the
76 // permission. http://crbug.com/455271
77 CdmConfig cdm_config;
78 cdm_config.allow_distinctive_identifier = true;
79 cdm_config.allow_persistent_state = true;
80 cdm_config.use_hw_secure_codecs = use_hw_secure_codecs_;
82 is_creating_cdm_ = true;
84 base::WeakPtr<ProxyDecryptor> weak_this = weak_ptr_factory_.GetWeakPtr();
85 cdm_factory->Create(
86 key_system, security_origin, cdm_config,
87 base::Bind(&ProxyDecryptor::OnSessionMessage, weak_this),
88 base::Bind(&ProxyDecryptor::OnSessionClosed, weak_this),
89 base::Bind(&ProxyDecryptor::OnLegacySessionError, weak_this),
90 base::Bind(&ProxyDecryptor::OnSessionKeysChange, weak_this),
91 base::Bind(&ProxyDecryptor::OnSessionExpirationUpdate, weak_this),
92 base::Bind(&ProxyDecryptor::OnCdmCreated, weak_this, key_system,
93 security_origin, cdm_context_ready_cb));
96 void ProxyDecryptor::OnCdmCreated(const std::string& key_system,
97 const GURL& security_origin,
98 const CdmContextReadyCB& cdm_context_ready_cb,
99 scoped_ptr<MediaKeys> cdm,
100 const std::string& /* error_message */) {
101 is_creating_cdm_ = false;
103 if (!cdm) {
104 cdm_context_ready_cb.Run(nullptr);
105 } else {
106 key_system_ = key_system;
107 security_origin_ = security_origin;
108 is_clear_key_ = IsClearKey(key_system) || IsExternalClearKey(key_system);
109 media_keys_ = cdm.Pass();
111 cdm_context_ready_cb.Run(media_keys_->GetCdmContext());
114 for (const auto& request : pending_requests_)
115 GenerateKeyRequestInternal(request->init_data_type, request->init_data);
117 pending_requests_.clear();
120 void ProxyDecryptor::GenerateKeyRequest(EmeInitDataType init_data_type,
121 const uint8* init_data,
122 int init_data_length) {
123 std::vector<uint8> init_data_vector(init_data, init_data + init_data_length);
125 if (is_creating_cdm_) {
126 pending_requests_.push_back(
127 new PendingGenerateKeyRequestData(init_data_type, init_data_vector));
128 return;
131 GenerateKeyRequestInternal(init_data_type, init_data_vector);
134 // Returns true if |data| is prefixed with |header| and has data after the
135 // |header|.
136 static bool HasHeader(const std::vector<uint8>& data,
137 const std::string& header) {
138 return data.size() > header.size() &&
139 std::equal(header.begin(), header.end(), data.begin());
142 // Removes the first |length| items from |data|.
143 static void StripHeader(std::vector<uint8>& data, size_t length) {
144 data.erase(data.begin(), data.begin() + length);
147 void ProxyDecryptor::GenerateKeyRequestInternal(
148 EmeInitDataType init_data_type,
149 const std::vector<uint8>& init_data) {
150 DVLOG(1) << __FUNCTION__;
151 DCHECK(!is_creating_cdm_);
153 if (!media_keys_) {
154 OnLegacySessionError(std::string(), MediaKeys::NOT_SUPPORTED_ERROR, 0,
155 "CDM creation failed.");
156 return;
159 const char kPrefixedApiPersistentSessionHeader[] = "PERSISTENT|";
160 const char kPrefixedApiLoadSessionHeader[] = "LOAD_SESSION|";
162 SessionCreationType session_creation_type = TemporarySession;
163 std::vector<uint8> stripped_init_data = init_data;
164 if (HasHeader(init_data, kPrefixedApiLoadSessionHeader)) {
165 session_creation_type = LoadSession;
166 StripHeader(stripped_init_data, strlen(kPrefixedApiLoadSessionHeader));
167 } else if (HasHeader(init_data, kPrefixedApiPersistentSessionHeader)) {
168 session_creation_type = PersistentSession;
169 StripHeader(stripped_init_data,
170 strlen(kPrefixedApiPersistentSessionHeader));
173 scoped_ptr<NewSessionCdmPromise> promise(new CdmCallbackPromise<std::string>(
174 base::Bind(&ProxyDecryptor::SetSessionId, weak_ptr_factory_.GetWeakPtr(),
175 session_creation_type),
176 base::Bind(&ProxyDecryptor::OnLegacySessionError,
177 weak_ptr_factory_.GetWeakPtr(),
178 std::string()))); // No session id until created.
180 if (session_creation_type == LoadSession) {
181 media_keys_->LoadSession(
182 MediaKeys::PERSISTENT_LICENSE_SESSION,
183 std::string(
184 reinterpret_cast<const char*>(vector_as_array(&stripped_init_data)),
185 stripped_init_data.size()),
186 promise.Pass());
187 return;
190 MediaKeys::SessionType session_type =
191 session_creation_type == PersistentSession
192 ? MediaKeys::PERSISTENT_LICENSE_SESSION
193 : MediaKeys::TEMPORARY_SESSION;
195 // No permission required when AesDecryptor is used or when the key system is
196 // external clear key.
197 DCHECK(!key_system_.empty());
198 if (CanUseAesDecryptor(key_system_) || IsExternalClearKey(key_system_)) {
199 OnPermissionStatus(session_type, init_data_type, stripped_init_data,
200 promise.Pass(), true /* granted */);
201 return;
204 #if defined(OS_CHROMEOS) || defined(OS_ANDROID)
205 media_permission_->RequestPermission(
206 MediaPermission::PROTECTED_MEDIA_IDENTIFIER, security_origin_,
207 base::Bind(&ProxyDecryptor::OnPermissionStatus,
208 weak_ptr_factory_.GetWeakPtr(), session_type, init_data_type,
209 stripped_init_data, base::Passed(&promise)));
210 #else
211 OnPermissionStatus(session_type, init_data_type, stripped_init_data,
212 promise.Pass(), true /* granted */);
213 #endif
216 void ProxyDecryptor::OnPermissionStatus(
217 MediaKeys::SessionType session_type,
218 EmeInitDataType init_data_type,
219 const std::vector<uint8>& init_data,
220 scoped_ptr<NewSessionCdmPromise> promise,
221 bool granted) {
222 // ProxyDecryptor is only used by Prefixed EME, where RequestPermission() is
223 // only for triggering the permission UI. Later CheckPermission() will be
224 // called (e.g. in PlatformVerificationFlow on ChromeOS; in BrowserCdmManager
225 // on Android) and the permission status will be evaluated then.
226 DVLOG_IF(1, !granted) << "Permission request rejected.";
228 media_keys_->CreateSessionAndGenerateRequest(session_type, init_data_type,
229 init_data, promise.Pass());
232 void ProxyDecryptor::AddKey(const uint8* key,
233 int key_length,
234 const uint8* init_data,
235 int init_data_length,
236 const std::string& session_id) {
237 DVLOG(1) << "AddKey()";
239 if (!media_keys_) {
240 OnLegacySessionError(std::string(), MediaKeys::INVALID_STATE_ERROR, 0,
241 "CDM is not available.");
242 return;
245 // In the prefixed API, the session parameter provided to addKey() is
246 // optional, so use the single existing session if it exists.
247 std::string new_session_id(session_id);
248 if (new_session_id.empty()) {
249 if (active_sessions_.size() == 1) {
250 base::hash_map<std::string, bool>::iterator it = active_sessions_.begin();
251 new_session_id = it->first;
252 } else {
253 OnLegacySessionError(std::string(), MediaKeys::NOT_SUPPORTED_ERROR, 0,
254 "SessionId not specified.");
255 return;
259 scoped_ptr<SimpleCdmPromise> promise(new CdmCallbackPromise<>(
260 base::Bind(&ProxyDecryptor::GenerateKeyAdded,
261 weak_ptr_factory_.GetWeakPtr(), session_id),
262 base::Bind(&ProxyDecryptor::OnLegacySessionError,
263 weak_ptr_factory_.GetWeakPtr(), session_id)));
265 // EME WD spec only supports a single array passed to the CDM. For
266 // Clear Key using v0.1b, both arrays are used (|init_data| is key_id).
267 // Since the EME WD spec supports the key as a JSON Web Key,
268 // convert the 2 arrays to a JWK and pass it as the single array.
269 if (is_clear_key_) {
270 // Decryptor doesn't support empty key ID (see http://crbug.com/123265).
271 // So ensure a non-empty value is passed.
272 if (!init_data) {
273 static const uint8 kDummyInitData[1] = {0};
274 init_data = kDummyInitData;
275 init_data_length = arraysize(kDummyInitData);
278 std::string jwk =
279 GenerateJWKSet(key, key_length, init_data, init_data_length);
280 DCHECK(!jwk.empty());
281 media_keys_->UpdateSession(new_session_id,
282 std::vector<uint8_t>(jwk.begin(), jwk.end()),
283 promise.Pass());
284 return;
287 media_keys_->UpdateSession(new_session_id,
288 std::vector<uint8_t>(key, key + key_length),
289 promise.Pass());
292 void ProxyDecryptor::CancelKeyRequest(const std::string& session_id) {
293 DVLOG(1) << "CancelKeyRequest()";
295 if (!media_keys_) {
296 OnLegacySessionError(std::string(), MediaKeys::INVALID_STATE_ERROR, 0,
297 "CDM is not available.");
298 return;
301 scoped_ptr<SimpleCdmPromise> promise(new CdmCallbackPromise<>(
302 base::Bind(&ProxyDecryptor::OnSessionClosed,
303 weak_ptr_factory_.GetWeakPtr(), session_id),
304 base::Bind(&ProxyDecryptor::OnLegacySessionError,
305 weak_ptr_factory_.GetWeakPtr(), session_id)));
306 media_keys_->RemoveSession(session_id, promise.Pass());
309 void ProxyDecryptor::OnSessionMessage(const std::string& session_id,
310 MediaKeys::MessageType message_type,
311 const std::vector<uint8>& message,
312 const GURL& legacy_destination_url) {
313 // Assumes that OnSessionCreated() has been called before this.
315 // For ClearKey, convert the message from JSON into just passing the key
316 // as the message. If unable to extract the key, return the message unchanged.
317 if (is_clear_key_) {
318 std::vector<uint8> key;
319 if (ExtractFirstKeyIdFromLicenseRequest(message, &key)) {
320 key_message_cb_.Run(session_id, key, legacy_destination_url);
321 return;
325 key_message_cb_.Run(session_id, message, legacy_destination_url);
328 void ProxyDecryptor::OnSessionKeysChange(const std::string& session_id,
329 bool has_additional_usable_key,
330 CdmKeysInfo keys_info) {
331 // EME v0.1b doesn't support this event.
334 void ProxyDecryptor::OnSessionExpirationUpdate(
335 const std::string& session_id,
336 const base::Time& new_expiry_time) {
337 // EME v0.1b doesn't support this event.
340 void ProxyDecryptor::GenerateKeyAdded(const std::string& session_id) {
341 // EME WD doesn't support this event, but it is needed for EME v0.1b.
342 key_added_cb_.Run(session_id);
345 void ProxyDecryptor::OnSessionClosed(const std::string& session_id) {
346 base::hash_map<std::string, bool>::iterator it =
347 active_sessions_.find(session_id);
349 // Latest EME spec separates closing a session ("allows an application to
350 // indicate that it no longer needs the session") and actually closing the
351 // session (done by the CDM at any point "such as in response to a close()
352 // call, when the session is no longer needed, or when system resources are
353 // lost.") Thus the CDM may cause 2 close() events -- one to resolve the
354 // close() promise, and a second to actually close the session. Prefixed EME
355 // only expects 1 close event, so drop the second (and subsequent) events.
356 // However, this means we can't tell if the CDM is generating spurious close()
357 // events.
358 if (it == active_sessions_.end())
359 return;
361 if (it->second) {
362 OnLegacySessionError(session_id, MediaKeys::NOT_SUPPORTED_ERROR,
363 kSessionClosedSystemCode,
364 "Do not close persistent sessions.");
366 active_sessions_.erase(it);
369 void ProxyDecryptor::OnLegacySessionError(const std::string& session_id,
370 MediaKeys::Exception exception_code,
371 uint32 system_code,
372 const std::string& error_message) {
373 // Convert |error_name| back to MediaKeys::KeyError if possible. Prefixed
374 // EME has different error message, so all the specific error events will
375 // get lost.
376 MediaKeys::KeyError error_code;
377 switch (exception_code) {
378 case MediaKeys::CLIENT_ERROR:
379 error_code = MediaKeys::kClientError;
380 break;
381 case MediaKeys::OUTPUT_ERROR:
382 error_code = MediaKeys::kOutputError;
383 break;
384 default:
385 // This will include all other CDM4 errors and any error generated
386 // by CDM5 or later.
387 error_code = MediaKeys::kUnknownError;
388 break;
390 key_error_cb_.Run(session_id, error_code, system_code);
393 void ProxyDecryptor::SetSessionId(SessionCreationType session_type,
394 const std::string& session_id) {
395 // LoadSession() returns empty |session_id| if the session is not found, so
396 // convert this into an error.
397 if (session_type == LoadSession && session_id.empty()) {
398 OnLegacySessionError(session_id, MediaKeys::INVALID_ACCESS_ERROR, 0,
399 "Incorrect session id specified for LoadSession().");
400 return;
403 // Loaded sessions are considered persistent.
404 bool is_persistent =
405 session_type == PersistentSession || session_type == LoadSession;
406 active_sessions_.insert(std::make_pair(session_id, is_persistent));
408 // For LoadSession(), generate the KeyAdded event.
409 if (session_type == LoadSession)
410 GenerateKeyAdded(session_id);
413 } // namespace media