1 // Copyright 2019 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 "media/fuchsia/cdm/fuchsia_cdm.h"
6 
7 #include "base/command_line.h"
8 #include "base/fuchsia/fuchsia_logging.h"
9 #include "base/logging.h"
10 #include "base/optional.h"
11 #include "fuchsia/base/mem_buffer_util.h"
12 #include "media/base/callback_registry.h"
13 #include "media/base/cdm_promise.h"
14 #include "media/base/media_switches.h"
15 
16 #define REJECT_PROMISE_AND_RETURN_IF_BAD_CDM(promise, cdm)         \
17   if (!cdm) {                                                      \
18     promise->reject(CdmPromise::Exception::INVALID_STATE_ERROR, 0, \
19                     "CDM channel is disconnected.");               \
20     return;                                                        \
21   }
22 
23 namespace media {
24 
25 namespace {
26 
27 // Audio packets are normally smaller than 128kB (more than enough for 2 seconds
28 // at 320kb/s).
29 const size_t kAudioStreamBufferSize = 128 * 1024;
30 
GetInitDataTypeName(EmeInitDataType type)31 std::string GetInitDataTypeName(EmeInitDataType type) {
32   switch (type) {
33     case EmeInitDataType::WEBM:
34       return "webm";
35     case EmeInitDataType::CENC:
36       return "cenc";
37     case EmeInitDataType::KEYIDS:
38       return "keyids";
39     case EmeInitDataType::UNKNOWN:
40       return "unknown";
41   }
42 }
43 
CreateLicenseInitData(EmeInitDataType type,const std::vector<uint8_t> & data)44 fuchsia::media::drm::LicenseInitData CreateLicenseInitData(
45     EmeInitDataType type,
46     const std::vector<uint8_t>& data) {
47   fuchsia::media::drm::LicenseInitData init_data;
48   init_data.type = GetInitDataTypeName(type);
49   init_data.data = data;
50   return init_data;
51 }
52 
CreateLicenseServerMessage(const std::vector<uint8_t> & response)53 fuchsia::media::drm::LicenseServerMessage CreateLicenseServerMessage(
54     const std::vector<uint8_t>& response) {
55   fuchsia::media::drm::LicenseServerMessage message;
56   message.message = cr_fuchsia::MemBufferFromString(
57       base::StringPiece(reinterpret_cast<const char*>(response.data()),
58                         response.size()),
59       "cr-drm-license-server-message");
60   return message;
61 }
62 
ToCdmMessageType(fuchsia::media::drm::LicenseMessageType type)63 CdmMessageType ToCdmMessageType(fuchsia::media::drm::LicenseMessageType type) {
64   switch (type) {
65     case fuchsia::media::drm::LicenseMessageType::REQUEST:
66       return CdmMessageType::LICENSE_REQUEST;
67     case fuchsia::media::drm::LicenseMessageType::RENEWAL:
68       return CdmMessageType::LICENSE_RENEWAL;
69     case fuchsia::media::drm::LicenseMessageType::RELEASE:
70       return CdmMessageType::LICENSE_RELEASE;
71   }
72 }
73 
ToCdmKeyStatus(fuchsia::media::drm::KeyStatus status)74 CdmKeyInformation::KeyStatus ToCdmKeyStatus(
75     fuchsia::media::drm::KeyStatus status) {
76   switch (status) {
77     case fuchsia::media::drm::KeyStatus::USABLE:
78       return CdmKeyInformation::USABLE;
79     case fuchsia::media::drm::KeyStatus::EXPIRED:
80       return CdmKeyInformation::EXPIRED;
81     case fuchsia::media::drm::KeyStatus::RELEASED:
82       return CdmKeyInformation::RELEASED;
83     case fuchsia::media::drm::KeyStatus::OUTPUT_RESTRICTED:
84       return CdmKeyInformation::OUTPUT_RESTRICTED;
85     case fuchsia::media::drm::KeyStatus::OUTPUT_DOWNSCALED:
86       return CdmKeyInformation::OUTPUT_DOWNSCALED;
87     case fuchsia::media::drm::KeyStatus::STATUS_PENDING:
88       return CdmKeyInformation::KEY_STATUS_PENDING;
89     case fuchsia::media::drm::KeyStatus::INTERNAL_ERROR:
90       return CdmKeyInformation::INTERNAL_ERROR;
91   }
92 }
93 
ToCdmPromiseException(fuchsia::media::drm::Error error)94 CdmPromise::Exception ToCdmPromiseException(fuchsia::media::drm::Error error) {
95   switch (error) {
96     case fuchsia::media::drm::Error::TYPE:
97       return CdmPromise::Exception::TYPE_ERROR;
98     case fuchsia::media::drm::Error::NOT_SUPPORTED:
99       return CdmPromise::Exception::NOT_SUPPORTED_ERROR;
100     case fuchsia::media::drm::Error::INVALID_STATE:
101       return CdmPromise::Exception::INVALID_STATE_ERROR;
102     case fuchsia::media::drm::Error::QUOTA_EXCEEDED:
103       return CdmPromise::Exception::QUOTA_EXCEEDED_ERROR;
104 
105     case fuchsia::media::drm::Error::NOT_PROVISIONED:
106       // FuchsiaCdmManager is supposed to provision CDM.
107       NOTREACHED();
108       return CdmPromise::Exception::INVALID_STATE_ERROR;
109 
110     case fuchsia::media::drm::Error::INTERNAL:
111       DLOG(ERROR) << "CDM failed due to an internal error.";
112       return CdmPromise::Exception::INVALID_STATE_ERROR;
113   }
114 }
115 
116 }  // namespace
117 
118 class FuchsiaCdm::CdmSession {
119  public:
120   using ResultCB =
121       base::OnceCallback<void(base::Optional<CdmPromise::Exception>)>;
122 
CdmSession(const FuchsiaCdm::SessionCallbacks * callbacks,base::RepeatingClosure on_new_key)123   CdmSession(const FuchsiaCdm::SessionCallbacks* callbacks,
124              base::RepeatingClosure on_new_key)
125       : session_callbacks_(callbacks), on_new_key_(on_new_key) {
126     // License session events, e.g. license request message, key status change.
127     // Fuchsia CDM service guarantees callback of functions (e.g.
128     // GenerateLicenseRequest) are called before event callbacks. So it's safe
129     // to rely on this to resolve the EME promises and send session events to
130     // JS. EME requires promises are resolved before session message.
131     session_.events().OnLicenseMessageGenerated =
132         fit::bind_member(this, &CdmSession::OnLicenseMessageGenerated);
133     session_.events().OnKeyStatesChanged =
134         fit::bind_member(this, &CdmSession::OnKeyStatesChanged);
135 
136     session_.set_error_handler(
137         fit::bind_member(this, &CdmSession::OnSessionError));
138   }
139 
~CdmSession()140   ~CdmSession() {
141     if (!session_id_.empty())
142       session_callbacks_->closed_cb.Run(session_id_);
143   }
144 
NewRequest()145   fidl::InterfaceRequest<fuchsia::media::drm::LicenseSession> NewRequest() {
146     return session_.NewRequest();
147   }
148 
GenerateLicenseRequest(EmeInitDataType init_data_type,const std::vector<uint8_t> & init_data,ResultCB generate_license_request_cb)149   void GenerateLicenseRequest(EmeInitDataType init_data_type,
150                               const std::vector<uint8_t>& init_data,
151                               ResultCB generate_license_request_cb) {
152     DCHECK(!result_cb_);
153     result_cb_ = std::move(generate_license_request_cb);
154     session_->GenerateLicenseRequest(
155         CreateLicenseInitData(init_data_type, init_data),
156         [this](fuchsia::media::drm::LicenseSession_GenerateLicenseRequest_Result
157                    result) { ProcessResult(result); });
158   }
159 
ProcessLicenseResponse(const std::vector<uint8_t> & response,ResultCB process_license_response_cb)160   void ProcessLicenseResponse(const std::vector<uint8_t>& response,
161                               ResultCB process_license_response_cb) {
162     DCHECK(!result_cb_);
163     result_cb_ = std::move(process_license_response_cb);
164     session_->ProcessLicenseResponse(
165         CreateLicenseServerMessage(response),
166         [this](fuchsia::media::drm::LicenseSession_ProcessLicenseResponse_Result
167                    result) { ProcessResult(result); });
168   }
169 
set_session_id(const std::string & session_id)170   void set_session_id(const std::string& session_id) {
171     session_id_ = session_id;
172   }
session_id() const173   const std::string& session_id() const { return session_id_; }
174 
175  private:
OnLicenseMessageGenerated(fuchsia::media::drm::LicenseMessage message)176   void OnLicenseMessageGenerated(fuchsia::media::drm::LicenseMessage message) {
177     DCHECK(!session_id_.empty());
178     std::string session_msg;
179     bool msg_available =
180         cr_fuchsia::StringFromMemBuffer(message.message, &session_msg);
181 
182     if (!msg_available) {
183       LOG(ERROR) << "Failed to generate message for session " << session_id_;
184       return;
185     }
186 
187     session_callbacks_->message_cb.Run(
188         session_id_, ToCdmMessageType(message.type),
189         std::vector<uint8_t>(session_msg.begin(), session_msg.end()));
190   }
191 
OnKeyStatesChanged(std::vector<fuchsia::media::drm::KeyState> key_states)192   void OnKeyStatesChanged(
193       std::vector<fuchsia::media::drm::KeyState> key_states) {
194     bool has_additional_usable_key = false;
195     CdmKeysInfo keys_info;
196     for (const auto& key_state : key_states) {
197       if (!key_state.has_key_id() || !key_state.has_status()) {
198         continue;
199       }
200       CdmKeyInformation::KeyStatus status = ToCdmKeyStatus(key_state.status());
201       has_additional_usable_key |= (status == CdmKeyInformation::USABLE);
202       keys_info.emplace_back(
203           new CdmKeyInformation(key_state.key_id(), status, 0));
204     }
205 
206     session_callbacks_->keys_change_cb.Run(
207         session_id_, has_additional_usable_key, std::move(keys_info));
208 
209     if (has_additional_usable_key)
210       on_new_key_.Run();
211   }
212 
OnSessionError(zx_status_t status)213   void OnSessionError(zx_status_t status) {
214     ZX_LOG(ERROR, status) << "Session error.";
215     if (result_cb_)
216       std::move(result_cb_).Run(CdmPromise::Exception::TYPE_ERROR);
217   }
218 
219   template <typename T>
ProcessResult(const T & result)220   void ProcessResult(const T& result) {
221     DCHECK(result_cb_);
222     std::move(result_cb_)
223         .Run(result.is_err()
224                  ? base::make_optional(ToCdmPromiseException(result.err()))
225                  : base::nullopt);
226   }
227 
228   const SessionCallbacks* const session_callbacks_;
229   base::RepeatingClosure on_new_key_;
230 
231   fuchsia::media::drm::LicenseSessionPtr session_;
232   std::string session_id_;
233 
234   // Callback for license operation.
235   ResultCB result_cb_;
236 
237   DISALLOW_COPY_AND_ASSIGN(CdmSession);
238 };
239 
240 FuchsiaCdm::SessionCallbacks::SessionCallbacks() = default;
241 FuchsiaCdm::SessionCallbacks::SessionCallbacks(SessionCallbacks&&) = default;
242 FuchsiaCdm::SessionCallbacks::~SessionCallbacks() = default;
243 FuchsiaCdm::SessionCallbacks& FuchsiaCdm::SessionCallbacks::operator=(
244     SessionCallbacks&&) = default;
245 
FuchsiaCdm(fuchsia::media::drm::ContentDecryptionModulePtr cdm,ReadyCB ready_cb,SessionCallbacks callbacks)246 FuchsiaCdm::FuchsiaCdm(fuchsia::media::drm::ContentDecryptionModulePtr cdm,
247                        ReadyCB ready_cb,
248                        SessionCallbacks callbacks)
249     : cdm_(std::move(cdm)),
250       ready_cb_(std::move(ready_cb)),
251       session_callbacks_(std::move(callbacks)),
252       decryptor_(this) {
253   DCHECK(cdm_);
254   cdm_.events().OnProvisioned =
255       fit::bind_member(this, &FuchsiaCdm::OnProvisioned);
256   cdm_.set_error_handler([this](zx_status_t status) {
257     ZX_LOG(ERROR, status) << "The fuchsia.media.drm.ContentDecryptionModule"
258                           << " channel was terminated.";
259 
260     // Reject all the pending promises.
261     promises_.Clear();
262 
263     // If the channel closed prior to invoking the ready_cb_, we should invoke
264     // it here with failure.
265     if (ready_cb_) {
266       std::move(ready_cb_).Run(
267           false, "ContentDecryptionModule closed prior to being ready");
268     }
269   });
270 }
271 
272 FuchsiaCdm::~FuchsiaCdm() = default;
273 
CreateVideoDecryptor(FuchsiaSecureStreamDecryptor::Client * client)274 std::unique_ptr<FuchsiaSecureStreamDecryptor> FuchsiaCdm::CreateVideoDecryptor(
275     FuchsiaSecureStreamDecryptor::Client* client) {
276   fuchsia::media::drm::DecryptorParams params;
277 
278   bool secure_mode = base::CommandLine::ForCurrentProcess()->HasSwitch(
279       switches::kEnableProtectedVideoBuffers);
280   params.set_require_secure_mode(secure_mode);
281 
282   params.mutable_input_details()->set_format_details_version_ordinal(0);
283   fuchsia::media::StreamProcessorPtr stream_processor;
284   cdm_->CreateDecryptor(std::move(params), stream_processor.NewRequest());
285 
286   auto decryptor = std::make_unique<FuchsiaSecureStreamDecryptor>(
287       std::move(stream_processor), client);
288 
289   // Save callback to use to notify the decryptor about a new key.
290   auto new_key_cb = decryptor->GetOnNewKeyClosure();
291   {
292     base::AutoLock auto_lock(new_key_cb_for_video_lock_);
293     new_key_cb_for_video_ = new_key_cb;
294   }
295 
296   return decryptor;
297 }
298 
299 std::unique_ptr<FuchsiaClearStreamDecryptor>
CreateAudioDecryptor()300 FuchsiaCdm::CreateAudioDecryptor() {
301   fuchsia::media::drm::DecryptorParams params;
302   params.set_require_secure_mode(false);
303   params.mutable_input_details()->set_format_details_version_ordinal(0);
304   fuchsia::media::StreamProcessorPtr stream_processor;
305   cdm_->CreateDecryptor(std::move(params), stream_processor.NewRequest());
306 
307   return std::make_unique<FuchsiaClearStreamDecryptor>(
308       std::move(stream_processor), kAudioStreamBufferSize);
309 }
310 
SetServerCertificate(const std::vector<uint8_t> & certificate,std::unique_ptr<SimpleCdmPromise> promise)311 void FuchsiaCdm::SetServerCertificate(
312     const std::vector<uint8_t>& certificate,
313     std::unique_ptr<SimpleCdmPromise> promise) {
314   REJECT_PROMISE_AND_RETURN_IF_BAD_CDM(promise, cdm_);
315 
316   uint32_t promise_id = promises_.SavePromise(std::move(promise));
317   cdm_->SetServerCertificate(
318       certificate,
319       [this, promise_id](
320           fuchsia::media::drm::
321               ContentDecryptionModule_SetServerCertificate_Result result) {
322         if (result.is_err()) {
323           promises_.RejectPromise(promise_id,
324                                   ToCdmPromiseException(result.err()), 0,
325                                   "Fail to set server cert.");
326           return;
327         }
328 
329         promises_.ResolvePromise(promise_id);
330       });
331 }
332 
CreateSessionAndGenerateRequest(CdmSessionType session_type,EmeInitDataType init_data_type,const std::vector<uint8_t> & init_data,std::unique_ptr<NewSessionCdmPromise> promise)333 void FuchsiaCdm::CreateSessionAndGenerateRequest(
334     CdmSessionType session_type,
335     EmeInitDataType init_data_type,
336     const std::vector<uint8_t>& init_data,
337     std::unique_ptr<NewSessionCdmPromise> promise) {
338   // TODO(crbug.com/1131114): Support persistent license.
339   if (session_type != CdmSessionType::kTemporary) {
340     promise->reject(CdmPromise::Exception::NOT_SUPPORTED_ERROR, 0,
341                     "session type is not supported.");
342     return;
343   }
344 
345   if (init_data_type == EmeInitDataType::UNKNOWN) {
346     promise->reject(CdmPromise::Exception::NOT_SUPPORTED_ERROR, 0,
347                     "init data type is not supported.");
348     return;
349   }
350 
351   REJECT_PROMISE_AND_RETURN_IF_BAD_CDM(promise, cdm_);
352 
353   uint32_t promise_id = promises_.SavePromise(std::move(promise));
354 
355   auto session = std::make_unique<CdmSession>(
356       &session_callbacks_,
357       base::BindRepeating(&FuchsiaCdm::OnNewKey, base::Unretained(this)));
358   CdmSession* session_ptr = session.get();
359 
360   cdm_->CreateLicenseSession(
361       fuchsia::media::drm::LicenseSessionType::TEMPORARY,
362       session_ptr->NewRequest(),
363       [this, promise_id,
364        session = std::move(session)](std::string session_id) mutable {
365         OnCreateSession(std::move(session), promise_id, session_id);
366       });
367 
368   // It's safe to pass raw pointer |session_ptr| because |session| owns the
369   // callback so it's guaranteed to outlive the callback.
370   session_ptr->GenerateLicenseRequest(
371       init_data_type, init_data,
372       base::BindOnce(&FuchsiaCdm::OnGenerateLicenseRequestStatus,
373                      base::Unretained(this), session_ptr, promise_id));
374 }
375 
OnProvisioned()376 void FuchsiaCdm::OnProvisioned() {
377   if (ready_cb_) {
378     std::move(ready_cb_).Run(true, "");
379   }
380 }
381 
OnCreateSession(std::unique_ptr<CdmSession> session,uint32_t promise_id,const std::string & session_id)382 void FuchsiaCdm::OnCreateSession(std::unique_ptr<CdmSession> session,
383                                  uint32_t promise_id,
384                                  const std::string& session_id) {
385   if (session_id.empty()) {
386     promises_.RejectPromise(promise_id,
387                             CdmPromise::Exception::NOT_SUPPORTED_ERROR, 0,
388                             "fail to create license session.");
389     return;
390   }
391 
392   session->set_session_id(session_id);
393   DCHECK(session_map_.find(session_id) == session_map_.end())
394       << "Duplicated session id " << session_id;
395   session_map_[session_id] = std::move(session);
396 }
397 
OnGenerateLicenseRequestStatus(CdmSession * session,uint32_t promise_id,base::Optional<CdmPromise::Exception> exception)398 void FuchsiaCdm::OnGenerateLicenseRequestStatus(
399     CdmSession* session,
400     uint32_t promise_id,
401     base::Optional<CdmPromise::Exception> exception) {
402   DCHECK(session);
403   std::string session_id = session->session_id();
404 
405   if (exception.has_value()) {
406     promises_.RejectPromise(promise_id, exception.value(), 0,
407                             "fail to generate license.");
408     session_map_.erase(session_id);
409     return;
410   }
411 
412   DCHECK(!session_id.empty());
413   promises_.ResolvePromise(promise_id, session_id);
414 }
415 
LoadSession(CdmSessionType session_type,const std::string & session_id,std::unique_ptr<NewSessionCdmPromise> promise)416 void FuchsiaCdm::LoadSession(CdmSessionType session_type,
417                              const std::string& session_id,
418                              std::unique_ptr<NewSessionCdmPromise> promise) {
419   NOTIMPLEMENTED();
420 }
421 
UpdateSession(const std::string & session_id,const std::vector<uint8_t> & response,std::unique_ptr<SimpleCdmPromise> promise)422 void FuchsiaCdm::UpdateSession(const std::string& session_id,
423                                const std::vector<uint8_t>& response,
424                                std::unique_ptr<SimpleCdmPromise> promise) {
425   auto it = session_map_.find(session_id);
426   if (it == session_map_.end()) {
427     promise->reject(CdmPromise::Exception::INVALID_STATE_ERROR, 0,
428                     "session doesn't exist.");
429     return;
430   }
431 
432   REJECT_PROMISE_AND_RETURN_IF_BAD_CDM(promise, cdm_);
433 
434   // Caller should NOT pass in an empty response.
435   DCHECK(!response.empty());
436 
437   uint32_t promise_id = promises_.SavePromise(std::move(promise));
438 
439   CdmSession* session = it->second.get();
440   DCHECK(session);
441 
442   session->ProcessLicenseResponse(
443       response, base::BindOnce(&FuchsiaCdm::OnProcessLicenseServerMessageStatus,
444                                base::Unretained(this), promise_id));
445 }
446 
OnProcessLicenseServerMessageStatus(uint32_t promise_id,base::Optional<CdmPromise::Exception> exception)447 void FuchsiaCdm::OnProcessLicenseServerMessageStatus(
448     uint32_t promise_id,
449     base::Optional<CdmPromise::Exception> exception) {
450   if (exception.has_value()) {
451     promises_.RejectPromise(promise_id, exception.value(), 0,
452                             "fail to process license.");
453     return;
454   }
455 
456   promises_.ResolvePromise(promise_id);
457 }
458 
CloseSession(const std::string & session_id,std::unique_ptr<SimpleCdmPromise> promise)459 void FuchsiaCdm::CloseSession(const std::string& session_id,
460                               std::unique_ptr<SimpleCdmPromise> promise) {
461   // There's a small window app can call close twice before receiving the closed
462   // event, in which case we want to resolve the promise. Read
463   // AesDecryptor::CloseSession for more details.
464   //
465   // Resolve the promise before deleting CdmSession. CdmSession will call
466   // SessionClosedCB in its destructor.
467   promise->resolve();
468   session_map_.erase(session_id);
469 }
470 
RemoveSession(const std::string & session_id,std::unique_ptr<SimpleCdmPromise> promise)471 void FuchsiaCdm::RemoveSession(const std::string& session_id,
472                                std::unique_ptr<SimpleCdmPromise> promise) {
473   NOTIMPLEMENTED();
474   promise->reject(CdmPromise::Exception::NOT_SUPPORTED_ERROR, 0,
475                   "not implemented");
476 }
477 
GetCdmContext()478 CdmContext* FuchsiaCdm::GetCdmContext() {
479   return this;
480 }
481 
RegisterEventCB(EventCB event_cb)482 std::unique_ptr<CallbackRegistration> FuchsiaCdm::RegisterEventCB(
483     EventCB event_cb) {
484   return event_callbacks_.Register(std::move(event_cb));
485 }
486 
GetDecryptor()487 Decryptor* FuchsiaCdm::GetDecryptor() {
488   return &decryptor_;
489 }
490 
GetFuchsiaCdmContext()491 FuchsiaCdmContext* FuchsiaCdm::GetFuchsiaCdmContext() {
492   return this;
493 }
494 
OnNewKey()495 void FuchsiaCdm::OnNewKey() {
496   event_callbacks_.Notify(Event::kHasAdditionalUsableKey);
497   {
498     base::AutoLock auto_lock(new_key_cb_for_video_lock_);
499     if (new_key_cb_for_video_)
500       new_key_cb_for_video_.Run();
501   }
502 }
503 
504 }  // namespace media
505