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