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/encrypted_media_player_support_impl.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/callback_helpers.h"
11#include "base/metrics/histogram.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/strings/string_util.h"
14#include "base/strings/utf_string_conversions.h"
15#include "content/renderer/media/crypto/key_systems.h"
16#include "content/renderer/media/webcontentdecryptionmodule_impl.h"
17#include "content/renderer/pepper/pepper_webplugin_impl.h"
18#include "media/base/bind_to_current_loop.h"
19#include "media/blink/encrypted_media_player_support.h"
20#include "third_party/WebKit/public/platform/WebContentDecryptionModule.h"
21#include "third_party/WebKit/public/platform/WebContentDecryptionModuleResult.h"
22#include "third_party/WebKit/public/platform/WebMediaPlayerClient.h"
23#include "third_party/WebKit/public/web/WebDocument.h"
24#include "third_party/WebKit/public/web/WebLocalFrame.h"
25#include "third_party/WebKit/public/web/WebRuntimeFeatures.h"
26
27#if defined(ENABLE_PEPPER_CDMS)
28#include "content/renderer/media/crypto/pepper_cdm_wrapper_impl.h"
29#endif
30
31using blink::WebMediaPlayer;
32using blink::WebMediaPlayerClient;
33using blink::WebString;
34
35namespace content {
36
37#define BIND_TO_RENDER_LOOP(function)            \
38  (media::BindToCurrentLoop(base::Bind(function, AsWeakPtr())))
39
40#define BIND_TO_RENDER_LOOP1(function, arg1)     \
41  (media::BindToCurrentLoop(base::Bind(function, AsWeakPtr(), arg1)))
42
43
44// Prefix for histograms related to Encrypted Media Extensions.
45static const char* kMediaEme = "Media.EME.";
46
47// Used for calls to decryptor_ready_cb where the result can be ignored.
48static void DoNothing(bool success) {
49}
50
51// Convert a WebString to ASCII, falling back on an empty string in the case
52// of a non-ASCII string.
53static std::string ToASCIIOrEmpty(const WebString& string) {
54  return base::IsStringASCII(string) ? base::UTF16ToASCII(string)
55                                     : std::string();
56}
57
58// Helper functions to report media EME related stats to UMA. They follow the
59// convention of more commonly used macros UMA_HISTOGRAM_ENUMERATION and
60// UMA_HISTOGRAM_COUNTS. The reason that we cannot use those macros directly is
61// that UMA_* macros require the names to be constant throughout the process'
62// lifetime.
63static void EmeUMAHistogramEnumeration(const std::string& key_system,
64                                       const std::string& method,
65                                       int sample,
66                                       int boundary_value) {
67  base::LinearHistogram::FactoryGet(
68      kMediaEme + KeySystemNameForUMA(key_system) + "." + method,
69      1, boundary_value, boundary_value + 1,
70      base::Histogram::kUmaTargetedHistogramFlag)->Add(sample);
71}
72
73static void EmeUMAHistogramCounts(const std::string& key_system,
74                                  const std::string& method,
75                                  int sample) {
76  // Use the same parameters as UMA_HISTOGRAM_COUNTS.
77  base::Histogram::FactoryGet(
78      kMediaEme + KeySystemNameForUMA(key_system) + "." + method,
79      1, 1000000, 50, base::Histogram::kUmaTargetedHistogramFlag)->Add(sample);
80}
81
82// Helper enum for reporting generateKeyRequest/addKey histograms.
83enum MediaKeyException {
84  kUnknownResultId,
85  kSuccess,
86  kKeySystemNotSupported,
87  kInvalidPlayerState,
88  kMaxMediaKeyException
89};
90
91static MediaKeyException MediaKeyExceptionForUMA(
92    WebMediaPlayer::MediaKeyException e) {
93  switch (e) {
94    case WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported:
95      return kKeySystemNotSupported;
96    case WebMediaPlayer::MediaKeyExceptionInvalidPlayerState:
97      return kInvalidPlayerState;
98    case WebMediaPlayer::MediaKeyExceptionNoError:
99      return kSuccess;
100    default:
101      return kUnknownResultId;
102  }
103}
104
105// Helper for converting |key_system| name and exception |e| to a pair of enum
106// values from above, for reporting to UMA.
107static void ReportMediaKeyExceptionToUMA(const std::string& method,
108                                         const std::string& key_system,
109                                         WebMediaPlayer::MediaKeyException e) {
110  MediaKeyException result_id = MediaKeyExceptionForUMA(e);
111  DCHECK_NE(result_id, kUnknownResultId) << e;
112  EmeUMAHistogramEnumeration(
113      key_system, method, result_id, kMaxMediaKeyException);
114}
115
116// Guess the type of |init_data|. This is only used to handle some corner cases
117// so we keep it as simple as possible without breaking major use cases.
118static std::string GuessInitDataType(const unsigned char* init_data,
119                                     unsigned init_data_length) {
120  // Most WebM files use KeyId of 16 bytes. MP4 init data are always >16 bytes.
121  if (init_data_length == 16)
122    return "video/webm";
123
124  return "video/mp4";
125}
126
127scoped_ptr<media::EncryptedMediaPlayerSupport>
128EncryptedMediaPlayerSupportImpl::Create(blink::WebMediaPlayerClient* client) {
129  return scoped_ptr<EncryptedMediaPlayerSupport>(
130      new EncryptedMediaPlayerSupportImpl(client));
131}
132
133EncryptedMediaPlayerSupportImpl::EncryptedMediaPlayerSupportImpl(
134    blink::WebMediaPlayerClient* client)
135    : client_(client),
136      web_cdm_(NULL) {
137}
138
139EncryptedMediaPlayerSupportImpl::~EncryptedMediaPlayerSupportImpl() {
140}
141
142WebMediaPlayer::MediaKeyException
143EncryptedMediaPlayerSupportImpl::GenerateKeyRequest(
144    blink::WebLocalFrame* frame,
145    const WebString& key_system,
146    const unsigned char* init_data,
147    unsigned init_data_length) {
148  DVLOG(1) << "generateKeyRequest: " << base::string16(key_system) << ": "
149           << std::string(reinterpret_cast<const char*>(init_data),
150                          static_cast<size_t>(init_data_length));
151
152  std::string ascii_key_system =
153      GetUnprefixedKeySystemName(ToASCIIOrEmpty(key_system));
154
155  WebMediaPlayer::MediaKeyException e =
156      GenerateKeyRequestInternal(frame, ascii_key_system, init_data,
157                                 init_data_length);
158  ReportMediaKeyExceptionToUMA("generateKeyRequest", ascii_key_system, e);
159  return e;
160}
161
162
163WebMediaPlayer::MediaKeyException
164EncryptedMediaPlayerSupportImpl::GenerateKeyRequestInternal(
165    blink::WebLocalFrame* frame,
166    const std::string& key_system,
167    const unsigned char* init_data,
168    unsigned init_data_length) {
169  if (!IsConcreteSupportedKeySystem(key_system))
170    return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
171
172  // We do not support run-time switching between key systems for now.
173  if (current_key_system_.empty()) {
174    if (!proxy_decryptor_) {
175      proxy_decryptor_.reset(new ProxyDecryptor(
176#if defined(ENABLE_PEPPER_CDMS)
177          // Create() must be called synchronously as |frame| may not be
178          // valid afterwards.
179          base::Bind(&PepperCdmWrapperImpl::Create, frame),
180#elif defined(ENABLE_BROWSER_CDMS)
181#error Browser side CDM in WMPI for prefixed EME API not supported yet.
182#endif
183          BIND_TO_RENDER_LOOP(&EncryptedMediaPlayerSupportImpl::OnKeyAdded),
184          BIND_TO_RENDER_LOOP(&EncryptedMediaPlayerSupportImpl::OnKeyError),
185          BIND_TO_RENDER_LOOP(&EncryptedMediaPlayerSupportImpl::OnKeyMessage)));
186    }
187
188    GURL security_origin(frame->document().securityOrigin().toString());
189    if (!proxy_decryptor_->InitializeCDM(key_system, security_origin))
190      return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
191
192    if (proxy_decryptor_ && !decryptor_ready_cb_.is_null()) {
193      base::ResetAndReturn(&decryptor_ready_cb_)
194          .Run(proxy_decryptor_->GetDecryptor(), base::Bind(DoNothing));
195    }
196
197    current_key_system_ = key_system;
198  } else if (key_system != current_key_system_) {
199    return WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;
200  }
201
202  std::string init_data_type = init_data_type_;
203  if (init_data_type.empty())
204    init_data_type = GuessInitDataType(init_data, init_data_length);
205
206  // TODO(xhwang): We assume all streams are from the same container (thus have
207  // the same "type") for now. In the future, the "type" should be passed down
208  // from the application.
209  if (!proxy_decryptor_->GenerateKeyRequest(
210           init_data_type, init_data, init_data_length)) {
211    current_key_system_.clear();
212    return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
213  }
214
215  return WebMediaPlayer::MediaKeyExceptionNoError;
216}
217
218WebMediaPlayer::MediaKeyException EncryptedMediaPlayerSupportImpl::AddKey(
219    const WebString& key_system,
220    const unsigned char* key,
221    unsigned key_length,
222    const unsigned char* init_data,
223    unsigned init_data_length,
224    const WebString& session_id) {
225  DVLOG(1) << "addKey: " << base::string16(key_system) << ": "
226           << std::string(reinterpret_cast<const char*>(key),
227                          static_cast<size_t>(key_length)) << ", "
228           << std::string(reinterpret_cast<const char*>(init_data),
229                          static_cast<size_t>(init_data_length)) << " ["
230           << base::string16(session_id) << "]";
231
232  std::string ascii_key_system =
233      GetUnprefixedKeySystemName(ToASCIIOrEmpty(key_system));
234  std::string ascii_session_id = ToASCIIOrEmpty(session_id);
235
236  WebMediaPlayer::MediaKeyException e = AddKeyInternal(ascii_key_system,
237                                                       key,
238                                                       key_length,
239                                                       init_data,
240                                                       init_data_length,
241                                                       ascii_session_id);
242  ReportMediaKeyExceptionToUMA("addKey", ascii_key_system, e);
243  return e;
244}
245
246WebMediaPlayer::MediaKeyException
247EncryptedMediaPlayerSupportImpl::AddKeyInternal(
248    const std::string& key_system,
249    const unsigned char* key,
250    unsigned key_length,
251    const unsigned char* init_data,
252    unsigned init_data_length,
253    const std::string& session_id) {
254  DCHECK(key);
255  DCHECK_GT(key_length, 0u);
256
257  if (!IsConcreteSupportedKeySystem(key_system))
258    return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
259
260  if (current_key_system_.empty() || key_system != current_key_system_)
261    return WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;
262
263  proxy_decryptor_->AddKey(
264      key, key_length, init_data, init_data_length, session_id);
265  return WebMediaPlayer::MediaKeyExceptionNoError;
266}
267
268WebMediaPlayer::MediaKeyException
269EncryptedMediaPlayerSupportImpl::CancelKeyRequest(
270    const WebString& key_system,
271    const WebString& session_id) {
272  DVLOG(1) << "cancelKeyRequest: " << base::string16(key_system) << ": "
273           << " [" << base::string16(session_id) << "]";
274
275  std::string ascii_key_system =
276      GetUnprefixedKeySystemName(ToASCIIOrEmpty(key_system));
277  std::string ascii_session_id = ToASCIIOrEmpty(session_id);
278
279  WebMediaPlayer::MediaKeyException e =
280      CancelKeyRequestInternal(ascii_key_system, ascii_session_id);
281  ReportMediaKeyExceptionToUMA("cancelKeyRequest", ascii_key_system, e);
282  return e;
283}
284
285WebMediaPlayer::MediaKeyException
286EncryptedMediaPlayerSupportImpl::CancelKeyRequestInternal(
287    const std::string& key_system,
288    const std::string& session_id) {
289  if (!IsConcreteSupportedKeySystem(key_system))
290    return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
291
292  if (current_key_system_.empty() || key_system != current_key_system_)
293    return WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;
294
295  proxy_decryptor_->CancelKeyRequest(session_id);
296  return WebMediaPlayer::MediaKeyExceptionNoError;
297}
298
299void EncryptedMediaPlayerSupportImpl::SetInitialContentDecryptionModule(
300    blink::WebContentDecryptionModule* initial_cdm) {
301  // Used when loading media and no pipeline/decoder attached yet.
302  DCHECK(decryptor_ready_cb_.is_null());
303
304  web_cdm_ = ToWebContentDecryptionModuleImpl(initial_cdm);
305}
306
307void EncryptedMediaPlayerSupportImpl::SetContentDecryptionModule(
308    blink::WebContentDecryptionModule* cdm) {
309  // TODO(xhwang): Support setMediaKeys(0) if necessary: http://crbug.com/330324
310  if (!cdm)
311    return;
312
313  web_cdm_ = ToWebContentDecryptionModuleImpl(cdm);
314
315  if (web_cdm_ && !decryptor_ready_cb_.is_null())
316    base::ResetAndReturn(&decryptor_ready_cb_)
317        .Run(web_cdm_->GetDecryptor(), base::Bind(DoNothing));
318}
319
320void EncryptedMediaPlayerSupportImpl::SetContentDecryptionModule(
321    blink::WebContentDecryptionModule* cdm,
322    blink::WebContentDecryptionModuleResult result) {
323  // TODO(xhwang): Support setMediaKeys(0) if necessary: http://crbug.com/330324
324  if (!cdm) {
325    result.completeWithError(
326        blink::WebContentDecryptionModuleExceptionNotSupportedError,
327        0,
328        "Null MediaKeys object is not supported.");
329    return;
330  }
331
332  web_cdm_ = ToWebContentDecryptionModuleImpl(cdm);
333
334  if (web_cdm_ && !decryptor_ready_cb_.is_null()) {
335    base::ResetAndReturn(&decryptor_ready_cb_)
336        .Run(web_cdm_->GetDecryptor(), BIND_TO_RENDER_LOOP1(
337            &EncryptedMediaPlayerSupportImpl::ContentDecryptionModuleAttached,
338            result));
339  } else {
340    // No pipeline/decoder connected, so resolve the promise. When something
341    // is connected, setting the CDM will happen in SetDecryptorReadyCB().
342    ContentDecryptionModuleAttached(result, true);
343  }
344}
345
346void EncryptedMediaPlayerSupportImpl::ContentDecryptionModuleAttached(
347    blink::WebContentDecryptionModuleResult result,
348    bool success) {
349  if (success) {
350    result.complete();
351    return;
352  }
353
354  result.completeWithError(
355      blink::WebContentDecryptionModuleExceptionNotSupportedError,
356      0,
357      "Unable to set MediaKeys object");
358}
359
360media::SetDecryptorReadyCB
361EncryptedMediaPlayerSupportImpl::CreateSetDecryptorReadyCB() {
362  return BIND_TO_RENDER_LOOP(
363      &EncryptedMediaPlayerSupportImpl::SetDecryptorReadyCB);
364}
365
366media::Demuxer::NeedKeyCB
367EncryptedMediaPlayerSupportImpl::CreateNeedKeyCB() {
368  return BIND_TO_RENDER_LOOP(&EncryptedMediaPlayerSupportImpl::OnNeedKey);
369}
370
371void EncryptedMediaPlayerSupportImpl::OnPipelineDecryptError() {
372  EmeUMAHistogramCounts(current_key_system_, "DecryptError", 1);
373}
374
375void EncryptedMediaPlayerSupportImpl::OnNeedKey(const std::string& type,
376                                   const std::vector<uint8>& init_data) {
377  // Do not fire NeedKey event if encrypted media is not enabled.
378  if (!blink::WebRuntimeFeatures::isPrefixedEncryptedMediaEnabled() &&
379      !blink::WebRuntimeFeatures::isEncryptedMediaEnabled()) {
380    return;
381  }
382
383  UMA_HISTOGRAM_COUNTS(kMediaEme + std::string("NeedKey"), 1);
384
385  DCHECK(init_data_type_.empty() || type.empty() || type == init_data_type_);
386  if (init_data_type_.empty())
387    init_data_type_ = type;
388
389  const uint8* init_data_ptr = init_data.empty() ? NULL : &init_data[0];
390  client_->keyNeeded(
391      WebString::fromUTF8(type), init_data_ptr, init_data.size());
392}
393
394void EncryptedMediaPlayerSupportImpl::OnKeyAdded(
395    const std::string& session_id) {
396  EmeUMAHistogramCounts(current_key_system_, "KeyAdded", 1);
397  client_->keyAdded(
398      WebString::fromUTF8(GetPrefixedKeySystemName(current_key_system_)),
399      WebString::fromUTF8(session_id));
400}
401
402void EncryptedMediaPlayerSupportImpl::OnKeyError(const std::string& session_id,
403                                    media::MediaKeys::KeyError error_code,
404                                    uint32 system_code) {
405  EmeUMAHistogramEnumeration(current_key_system_, "KeyError",
406                             error_code, media::MediaKeys::kMaxKeyError);
407
408  uint16 short_system_code = 0;
409  if (system_code > std::numeric_limits<uint16>::max()) {
410    LOG(WARNING) << "system_code exceeds unsigned short limit.";
411    short_system_code = std::numeric_limits<uint16>::max();
412  } else {
413    short_system_code = static_cast<uint16>(system_code);
414  }
415
416  client_->keyError(
417      WebString::fromUTF8(GetPrefixedKeySystemName(current_key_system_)),
418      WebString::fromUTF8(session_id),
419      static_cast<WebMediaPlayerClient::MediaKeyErrorCode>(error_code),
420      short_system_code);
421}
422
423void EncryptedMediaPlayerSupportImpl::OnKeyMessage(
424    const std::string& session_id,
425    const std::vector<uint8>& message,
426    const GURL& destination_url) {
427  DCHECK(destination_url.is_empty() || destination_url.is_valid());
428
429  client_->keyMessage(
430      WebString::fromUTF8(GetPrefixedKeySystemName(current_key_system_)),
431      WebString::fromUTF8(session_id),
432      message.empty() ? NULL : &message[0],
433      message.size(),
434      destination_url);
435}
436
437void EncryptedMediaPlayerSupportImpl::SetDecryptorReadyCB(
438     const media::DecryptorReadyCB& decryptor_ready_cb) {
439  // Cancels the previous decryptor request.
440  if (decryptor_ready_cb.is_null()) {
441    if (!decryptor_ready_cb_.is_null()) {
442      base::ResetAndReturn(&decryptor_ready_cb_)
443          .Run(NULL, base::Bind(DoNothing));
444    }
445    return;
446  }
447
448  // TODO(xhwang): Support multiple decryptor notification request (e.g. from
449  // video and audio). The current implementation is okay for the current
450  // media pipeline since we initialize audio and video decoders in sequence.
451  // But WebMediaPlayerImpl should not depend on media pipeline's implementation
452  // detail.
453  DCHECK(decryptor_ready_cb_.is_null());
454
455  // Mixed use of prefixed and unprefixed EME APIs is disallowed by Blink.
456  DCHECK(!proxy_decryptor_ || !web_cdm_);
457
458  if (proxy_decryptor_) {
459    decryptor_ready_cb.Run(proxy_decryptor_->GetDecryptor(),
460                           base::Bind(DoNothing));
461    return;
462  }
463
464  if (web_cdm_) {
465    decryptor_ready_cb.Run(web_cdm_->GetDecryptor(), base::Bind(DoNothing));
466    return;
467  }
468
469  decryptor_ready_cb_ = decryptor_ready_cb;
470}
471
472}  // namespace content
473