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