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.
4
5#include "content/renderer/media/crypto/ppapi_decryptor.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/callback.h"
11#include "base/callback_helpers.h"
12#include "base/location.h"
13#include "base/logging.h"
14#include "base/message_loop/message_loop.h"
15#include "base/message_loop/message_loop_proxy.h"
16#include "content/renderer/media/crypto/key_systems.h"
17#include "content/renderer/pepper/content_decryptor_delegate.h"
18#include "content/renderer/pepper/pepper_plugin_instance_impl.h"
19#include "media/base/audio_decoder_config.h"
20#include "media/base/cdm_promise.h"
21#include "media/base/data_buffer.h"
22#include "media/base/decoder_buffer.h"
23#include "media/base/video_decoder_config.h"
24#include "media/base/video_frame.h"
25
26namespace content {
27
28// This class is needed so that resolving an Update() promise triggers playback
29// of the stream. It intercepts the resolve() call to invoke an additional
30// callback.
31class SessionUpdatedPromise : public media::SimpleCdmPromise {
32 public:
33  SessionUpdatedPromise(scoped_ptr<media::SimpleCdmPromise> caller_promise,
34                        base::Closure additional_resolve_cb)
35      : caller_promise_(caller_promise.Pass()),
36        additional_resolve_cb_(additional_resolve_cb) {}
37
38  virtual void resolve() OVERRIDE {
39    DCHECK(is_pending_);
40    is_pending_ = false;
41    additional_resolve_cb_.Run();
42    caller_promise_->resolve();
43  }
44
45  virtual void reject(media::MediaKeys::Exception exception_code,
46                      uint32 system_code,
47                      const std::string& error_message) OVERRIDE {
48    DCHECK(is_pending_);
49    is_pending_ = false;
50    caller_promise_->reject(exception_code, system_code, error_message);
51  }
52
53 protected:
54  scoped_ptr<media::SimpleCdmPromise> caller_promise_;
55  base::Closure additional_resolve_cb_;
56};
57
58// This class is needed so that resolving a SessionLoaded() promise triggers
59// playback of the stream. It intercepts the resolve() call to invoke an
60// additional callback. This is only needed until KeysChange event gets passed
61// through Pepper.
62class SessionLoadedPromise : public media::NewSessionCdmPromise {
63 public:
64  SessionLoadedPromise(scoped_ptr<media::NewSessionCdmPromise> caller_promise,
65                       base::Closure additional_resolve_cb)
66      : caller_promise_(caller_promise.Pass()),
67        additional_resolve_cb_(additional_resolve_cb) {}
68
69  virtual void resolve(const std::string& web_session_id) OVERRIDE {
70    DCHECK(is_pending_);
71    is_pending_ = false;
72    additional_resolve_cb_.Run();
73    caller_promise_->resolve(web_session_id);
74  }
75
76  virtual void reject(media::MediaKeys::Exception exception_code,
77                      uint32 system_code,
78                      const std::string& error_message) OVERRIDE {
79    DCHECK(is_pending_);
80    is_pending_ = false;
81    caller_promise_->reject(exception_code, system_code, error_message);
82  }
83
84 protected:
85  scoped_ptr<media::NewSessionCdmPromise> caller_promise_;
86  base::Closure additional_resolve_cb_;
87};
88
89scoped_ptr<PpapiDecryptor> PpapiDecryptor::Create(
90    const std::string& key_system,
91    const GURL& security_origin,
92    const CreatePepperCdmCB& create_pepper_cdm_cb,
93    const media::SessionMessageCB& session_message_cb,
94    const media::SessionReadyCB& session_ready_cb,
95    const media::SessionClosedCB& session_closed_cb,
96    const media::SessionErrorCB& session_error_cb,
97    const media::SessionKeysChangeCB& session_keys_change_cb,
98    const media::SessionExpirationUpdateCB& session_expiration_update_cb) {
99  std::string plugin_type = GetPepperType(key_system);
100  DCHECK(!plugin_type.empty());
101  scoped_ptr<PepperCdmWrapper> pepper_cdm_wrapper =
102      create_pepper_cdm_cb.Run(plugin_type, security_origin);
103  if (!pepper_cdm_wrapper) {
104    DLOG(ERROR) << "Plugin instance creation failed.";
105    return scoped_ptr<PpapiDecryptor>();
106  }
107
108  return scoped_ptr<PpapiDecryptor>(
109      new PpapiDecryptor(key_system,
110                         pepper_cdm_wrapper.Pass(),
111                         session_message_cb,
112                         session_ready_cb,
113                         session_closed_cb,
114                         session_error_cb,
115                         session_keys_change_cb,
116                         session_expiration_update_cb));
117}
118
119PpapiDecryptor::PpapiDecryptor(
120    const std::string& key_system,
121    scoped_ptr<PepperCdmWrapper> pepper_cdm_wrapper,
122    const media::SessionMessageCB& session_message_cb,
123    const media::SessionReadyCB& session_ready_cb,
124    const media::SessionClosedCB& session_closed_cb,
125    const media::SessionErrorCB& session_error_cb,
126    const media::SessionKeysChangeCB& session_keys_change_cb,
127    const media::SessionExpirationUpdateCB& session_expiration_update_cb)
128    : pepper_cdm_wrapper_(pepper_cdm_wrapper.Pass()),
129      session_message_cb_(session_message_cb),
130      session_ready_cb_(session_ready_cb),
131      session_closed_cb_(session_closed_cb),
132      session_error_cb_(session_error_cb),
133      session_keys_change_cb_(session_keys_change_cb),
134      session_expiration_update_cb_(session_expiration_update_cb),
135      render_loop_proxy_(base::MessageLoopProxy::current()),
136      weak_ptr_factory_(this) {
137  DCHECK(pepper_cdm_wrapper_.get());
138  DCHECK(!session_message_cb_.is_null());
139  DCHECK(!session_ready_cb_.is_null());
140  DCHECK(!session_closed_cb_.is_null());
141  DCHECK(!session_error_cb_.is_null());
142  DCHECK(!session_keys_change_cb.is_null());
143  DCHECK(!session_expiration_update_cb.is_null());
144
145  base::WeakPtr<PpapiDecryptor> weak_this = weak_ptr_factory_.GetWeakPtr();
146  CdmDelegate()->Initialize(
147      key_system,
148      base::Bind(&PpapiDecryptor::OnSessionMessage, weak_this),
149      base::Bind(&PpapiDecryptor::OnSessionReady, weak_this),
150      base::Bind(&PpapiDecryptor::OnSessionClosed, weak_this),
151      base::Bind(&PpapiDecryptor::OnSessionError, weak_this),
152      base::Bind(&PpapiDecryptor::OnSessionKeysChange, weak_this),
153      base::Bind(&PpapiDecryptor::OnSessionExpirationUpdate, weak_this),
154      base::Bind(&PpapiDecryptor::OnFatalPluginError, weak_this));
155}
156
157PpapiDecryptor::~PpapiDecryptor() {
158  pepper_cdm_wrapper_.reset();
159}
160
161void PpapiDecryptor::SetServerCertificate(
162    const uint8* certificate_data,
163    int certificate_data_length,
164    scoped_ptr<media::SimpleCdmPromise> promise) {
165  DVLOG(2) << __FUNCTION__;
166  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
167
168  if (!CdmDelegate()) {
169    promise->reject(INVALID_STATE_ERROR, 0, "CdmDelegate() does not exist.");
170    return;
171  }
172
173  CdmDelegate()->SetServerCertificate(
174      certificate_data, certificate_data_length, promise.Pass());
175}
176
177void PpapiDecryptor::CreateSession(
178    const std::string& init_data_type,
179    const uint8* init_data,
180    int init_data_length,
181    SessionType session_type,
182    scoped_ptr<media::NewSessionCdmPromise> promise) {
183  DVLOG(2) << __FUNCTION__;
184  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
185
186  if (!CdmDelegate()) {
187    promise->reject(INVALID_STATE_ERROR, 0, "CdmDelegate() does not exist.");
188    return;
189  }
190
191  CdmDelegate()->CreateSession(init_data_type,
192                               init_data,
193                               init_data_length,
194                               session_type,
195                               promise.Pass());
196}
197
198void PpapiDecryptor::LoadSession(
199    const std::string& web_session_id,
200    scoped_ptr<media::NewSessionCdmPromise> promise) {
201  DVLOG(2) << __FUNCTION__;
202  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
203
204  if (!CdmDelegate()) {
205    promise->reject(INVALID_STATE_ERROR, 0, "CdmDelegate() does not exist.");
206    return;
207  }
208
209  // TODO(jrummell): Intercepting the promise should not be necessary once
210  // OnSessionKeysChange() is called in all cases. http://crbug.com/413413.
211  scoped_ptr<SessionLoadedPromise> session_loaded_promise(
212      new SessionLoadedPromise(promise.Pass(),
213                               base::Bind(&PpapiDecryptor::ResumePlayback,
214                                          weak_ptr_factory_.GetWeakPtr())));
215
216  CdmDelegate()->LoadSession(
217      web_session_id,
218      session_loaded_promise.PassAs<media::NewSessionCdmPromise>());
219}
220
221void PpapiDecryptor::UpdateSession(
222    const std::string& web_session_id,
223    const uint8* response,
224    int response_length,
225    scoped_ptr<media::SimpleCdmPromise> promise) {
226  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
227
228  if (!CdmDelegate()) {
229    promise->reject(INVALID_STATE_ERROR, 0, "CdmDelegate() does not exist.");
230    return;
231  }
232
233  // TODO(jrummell): Intercepting the promise should not be necessary once
234  // OnSessionKeysChange() is called in all cases. http://crbug.com/413413.
235  scoped_ptr<SessionUpdatedPromise> session_updated_promise(
236      new SessionUpdatedPromise(promise.Pass(),
237                                base::Bind(&PpapiDecryptor::ResumePlayback,
238                                           weak_ptr_factory_.GetWeakPtr())));
239  CdmDelegate()->UpdateSession(
240      web_session_id,
241      response,
242      response_length,
243      session_updated_promise.PassAs<media::SimpleCdmPromise>());
244}
245
246void PpapiDecryptor::CloseSession(const std::string& web_session_id,
247                                  scoped_ptr<media::SimpleCdmPromise> promise) {
248  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
249
250  if (!CdmDelegate()) {
251    promise->reject(INVALID_STATE_ERROR, 0, "CdmDelegate() does not exist.");
252    return;
253  }
254
255  CdmDelegate()->CloseSession(web_session_id, promise.Pass());
256}
257
258void PpapiDecryptor::RemoveSession(
259    const std::string& web_session_id,
260    scoped_ptr<media::SimpleCdmPromise> promise) {
261  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
262
263  if (!CdmDelegate()) {
264    promise->reject(INVALID_STATE_ERROR, 0, "CdmDelegate() does not exist.");
265    return;
266  }
267
268  CdmDelegate()->RemoveSession(web_session_id, promise.Pass());
269}
270
271void PpapiDecryptor::GetUsableKeyIds(const std::string& web_session_id,
272                                     scoped_ptr<media::KeyIdsPromise> promise) {
273  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
274
275  if (!CdmDelegate()) {
276    promise->reject(INVALID_STATE_ERROR, 0, "CdmDelegate() does not exist.");
277    return;
278  }
279
280  CdmDelegate()->GetUsableKeyIds(web_session_id, promise.Pass());
281}
282
283media::Decryptor* PpapiDecryptor::GetDecryptor() {
284  return this;
285}
286
287void PpapiDecryptor::RegisterNewKeyCB(StreamType stream_type,
288                                      const NewKeyCB& new_key_cb) {
289  if (!render_loop_proxy_->BelongsToCurrentThread()) {
290    render_loop_proxy_->PostTask(FROM_HERE,
291                                 base::Bind(&PpapiDecryptor::RegisterNewKeyCB,
292                                            weak_ptr_factory_.GetWeakPtr(),
293                                            stream_type,
294                                            new_key_cb));
295    return;
296  }
297
298  DVLOG(3) << __FUNCTION__ << " - stream_type: " << stream_type;
299  switch (stream_type) {
300    case kAudio:
301      new_audio_key_cb_ = new_key_cb;
302      break;
303    case kVideo:
304      new_video_key_cb_ = new_key_cb;
305      break;
306    default:
307      NOTREACHED();
308  }
309}
310
311void PpapiDecryptor::Decrypt(
312    StreamType stream_type,
313    const scoped_refptr<media::DecoderBuffer>& encrypted,
314    const DecryptCB& decrypt_cb) {
315  if (!render_loop_proxy_->BelongsToCurrentThread()) {
316    render_loop_proxy_->PostTask(FROM_HERE,
317                                 base::Bind(&PpapiDecryptor::Decrypt,
318                                            weak_ptr_factory_.GetWeakPtr(),
319                                            stream_type,
320                                            encrypted,
321                                            decrypt_cb));
322    return;
323  }
324
325  DVLOG(3) << __FUNCTION__ << " - stream_type: " << stream_type;
326  if (!CdmDelegate() ||
327      !CdmDelegate()->Decrypt(stream_type, encrypted, decrypt_cb)) {
328    decrypt_cb.Run(kError, NULL);
329  }
330}
331
332void PpapiDecryptor::CancelDecrypt(StreamType stream_type) {
333  if (!render_loop_proxy_->BelongsToCurrentThread()) {
334    render_loop_proxy_->PostTask(FROM_HERE,
335                                 base::Bind(&PpapiDecryptor::CancelDecrypt,
336                                            weak_ptr_factory_.GetWeakPtr(),
337                                            stream_type));
338    return;
339  }
340
341  DVLOG(1) << __FUNCTION__ << " - stream_type: " << stream_type;
342  if (CdmDelegate())
343    CdmDelegate()->CancelDecrypt(stream_type);
344}
345
346void PpapiDecryptor::InitializeAudioDecoder(
347      const media::AudioDecoderConfig& config,
348      const DecoderInitCB& init_cb) {
349  if (!render_loop_proxy_->BelongsToCurrentThread()) {
350    render_loop_proxy_->PostTask(
351        FROM_HERE,
352        base::Bind(&PpapiDecryptor::InitializeAudioDecoder,
353                   weak_ptr_factory_.GetWeakPtr(),
354                   config,
355                   init_cb));
356    return;
357  }
358
359  DVLOG(2) << __FUNCTION__;
360  DCHECK(config.is_encrypted());
361  DCHECK(config.IsValidConfig());
362
363  audio_decoder_init_cb_ = init_cb;
364  if (!CdmDelegate() || !CdmDelegate()->InitializeAudioDecoder(
365                            config,
366                            base::Bind(&PpapiDecryptor::OnDecoderInitialized,
367                                       weak_ptr_factory_.GetWeakPtr(),
368                                       kAudio))) {
369    base::ResetAndReturn(&audio_decoder_init_cb_).Run(false);
370    return;
371  }
372}
373
374void PpapiDecryptor::InitializeVideoDecoder(
375    const media::VideoDecoderConfig& config,
376    const DecoderInitCB& init_cb) {
377  if (!render_loop_proxy_->BelongsToCurrentThread()) {
378    render_loop_proxy_->PostTask(
379        FROM_HERE,
380        base::Bind(&PpapiDecryptor::InitializeVideoDecoder,
381                   weak_ptr_factory_.GetWeakPtr(),
382                   config,
383                   init_cb));
384    return;
385  }
386
387  DVLOG(2) << __FUNCTION__;
388  DCHECK(config.is_encrypted());
389  DCHECK(config.IsValidConfig());
390
391  video_decoder_init_cb_ = init_cb;
392  if (!CdmDelegate() || !CdmDelegate()->InitializeVideoDecoder(
393                            config,
394                            base::Bind(&PpapiDecryptor::OnDecoderInitialized,
395                                       weak_ptr_factory_.GetWeakPtr(),
396                                       kVideo))) {
397    base::ResetAndReturn(&video_decoder_init_cb_).Run(false);
398    return;
399  }
400}
401
402void PpapiDecryptor::DecryptAndDecodeAudio(
403    const scoped_refptr<media::DecoderBuffer>& encrypted,
404    const AudioDecodeCB& audio_decode_cb) {
405  if (!render_loop_proxy_->BelongsToCurrentThread()) {
406    render_loop_proxy_->PostTask(
407        FROM_HERE,
408        base::Bind(&PpapiDecryptor::DecryptAndDecodeAudio,
409                   weak_ptr_factory_.GetWeakPtr(),
410                   encrypted,
411                   audio_decode_cb));
412    return;
413  }
414
415  DVLOG(3) << __FUNCTION__;
416  if (!CdmDelegate() ||
417      !CdmDelegate()->DecryptAndDecodeAudio(encrypted, audio_decode_cb)) {
418    audio_decode_cb.Run(kError, AudioBuffers());
419  }
420}
421
422void PpapiDecryptor::DecryptAndDecodeVideo(
423    const scoped_refptr<media::DecoderBuffer>& encrypted,
424    const VideoDecodeCB& video_decode_cb) {
425  if (!render_loop_proxy_->BelongsToCurrentThread()) {
426    render_loop_proxy_->PostTask(
427        FROM_HERE,
428        base::Bind(&PpapiDecryptor::DecryptAndDecodeVideo,
429                   weak_ptr_factory_.GetWeakPtr(),
430                   encrypted,
431                   video_decode_cb));
432    return;
433  }
434
435  DVLOG(3) << __FUNCTION__;
436  if (!CdmDelegate() ||
437      !CdmDelegate()->DecryptAndDecodeVideo(encrypted, video_decode_cb)) {
438    video_decode_cb.Run(kError, NULL);
439  }
440}
441
442void PpapiDecryptor::ResetDecoder(StreamType stream_type) {
443  if (!render_loop_proxy_->BelongsToCurrentThread()) {
444    render_loop_proxy_->PostTask(FROM_HERE,
445                                 base::Bind(&PpapiDecryptor::ResetDecoder,
446                                            weak_ptr_factory_.GetWeakPtr(),
447                                            stream_type));
448    return;
449  }
450
451  DVLOG(2) << __FUNCTION__ << " - stream_type: " << stream_type;
452  if (CdmDelegate())
453    CdmDelegate()->ResetDecoder(stream_type);
454}
455
456void PpapiDecryptor::DeinitializeDecoder(StreamType stream_type) {
457  if (!render_loop_proxy_->BelongsToCurrentThread()) {
458    render_loop_proxy_->PostTask(
459        FROM_HERE,
460        base::Bind(&PpapiDecryptor::DeinitializeDecoder,
461                   weak_ptr_factory_.GetWeakPtr(),
462                   stream_type));
463    return;
464  }
465
466  DVLOG(2) << __FUNCTION__ << " - stream_type: " << stream_type;
467  if (CdmDelegate())
468    CdmDelegate()->DeinitializeDecoder(stream_type);
469}
470
471void PpapiDecryptor::OnDecoderInitialized(StreamType stream_type,
472                                          bool success) {
473  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
474  switch (stream_type) {
475    case kAudio:
476      DCHECK(!audio_decoder_init_cb_.is_null());
477      base::ResetAndReturn(&audio_decoder_init_cb_).Run(success);
478      break;
479    case kVideo:
480      DCHECK(!video_decoder_init_cb_.is_null());
481      base::ResetAndReturn(&video_decoder_init_cb_).Run(success);
482      break;
483    default:
484      NOTREACHED();
485  }
486}
487
488void PpapiDecryptor::OnSessionMessage(const std::string& web_session_id,
489                                      const std::vector<uint8>& message,
490                                      const GURL& destination_url) {
491  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
492  session_message_cb_.Run(web_session_id, message, destination_url);
493}
494
495void PpapiDecryptor::OnSessionKeysChange(const std::string& web_session_id,
496                                         bool has_additional_usable_key) {
497  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
498
499  // TODO(jrummell): Handling resume playback should be done in the media
500  // player, not in the Decryptors. http://crbug.com/413413.
501  if (has_additional_usable_key)
502    ResumePlayback();
503
504  session_keys_change_cb_.Run(web_session_id, has_additional_usable_key);
505}
506
507void PpapiDecryptor::OnSessionExpirationUpdate(
508    const std::string& web_session_id,
509    const base::Time& new_expiry_time) {
510  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
511  session_expiration_update_cb_.Run(web_session_id, new_expiry_time);
512}
513
514void PpapiDecryptor::OnSessionReady(const std::string& web_session_id) {
515  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
516
517  // TODO(jrummell): Calling ResumePlayback() here should not be necessary once
518  // OnSessionKeysChange() is called in all cases. http://crbug.com/413413.
519  ResumePlayback();
520  session_ready_cb_.Run(web_session_id);
521}
522
523void PpapiDecryptor::OnSessionClosed(const std::string& web_session_id) {
524  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
525  session_closed_cb_.Run(web_session_id);
526}
527
528void PpapiDecryptor::OnSessionError(const std::string& web_session_id,
529                                    MediaKeys::Exception exception_code,
530                                    uint32 system_code,
531                                    const std::string& error_description) {
532  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
533  session_error_cb_.Run(
534      web_session_id, exception_code, system_code, error_description);
535}
536
537void PpapiDecryptor::ResumePlayback() {
538  // Based on the spec, we need to resume playback when update() completes
539  // successfully, or when a session is successfully loaded (triggered by
540  // OnSessionReady()). So we choose to call the NewKeyCBs here.
541  if (!new_audio_key_cb_.is_null())
542    new_audio_key_cb_.Run();
543
544  if (!new_video_key_cb_.is_null())
545    new_video_key_cb_.Run();
546}
547
548void PpapiDecryptor::OnFatalPluginError() {
549  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
550  pepper_cdm_wrapper_.reset();
551}
552
553ContentDecryptorDelegate* PpapiDecryptor::CdmDelegate() {
554  DCHECK(render_loop_proxy_->BelongsToCurrentThread());
555  return (pepper_cdm_wrapper_) ? pepper_cdm_wrapper_->GetCdmDelegate() : NULL;
556}
557
558}  // namespace content
559