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