1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/dom/MediaKeySession.h"
8 #include "mozilla/MediaDrmCDMProxy.h"
9 #include "MediaDrmCDMCallbackProxy.h"
10
11 namespace mozilla {
12
ToMediaDrmSessionType(dom::MediaKeySessionType aSessionType)13 MediaDrmSessionType ToMediaDrmSessionType(
14 dom::MediaKeySessionType aSessionType) {
15 switch (aSessionType) {
16 case dom::MediaKeySessionType::Temporary:
17 return kKeyStreaming;
18 case dom::MediaKeySessionType::Persistent_license:
19 return kKeyOffline;
20 default:
21 return kKeyStreaming;
22 };
23 }
24
MediaDrmCDMProxy(dom::MediaKeys * aKeys,const nsAString & aKeySystem,bool aDistinctiveIdentifierRequired,bool aPersistentStateRequired)25 MediaDrmCDMProxy::MediaDrmCDMProxy(dom::MediaKeys* aKeys,
26 const nsAString& aKeySystem,
27 bool aDistinctiveIdentifierRequired,
28 bool aPersistentStateRequired)
29 : CDMProxy(aKeys, aKeySystem, aDistinctiveIdentifierRequired,
30 aPersistentStateRequired),
31 mCDM(nullptr),
32 mShutdownCalled(false) {
33 MOZ_ASSERT(NS_IsMainThread());
34 MOZ_COUNT_CTOR(MediaDrmCDMProxy);
35 }
36
~MediaDrmCDMProxy()37 MediaDrmCDMProxy::~MediaDrmCDMProxy() { MOZ_COUNT_DTOR(MediaDrmCDMProxy); }
38
Init(PromiseId aPromiseId,const nsAString & aOrigin,const nsAString & aTopLevelOrigin,const nsAString & aName)39 void MediaDrmCDMProxy::Init(PromiseId aPromiseId, const nsAString& aOrigin,
40 const nsAString& aTopLevelOrigin,
41 const nsAString& aName) {
42 MOZ_ASSERT(NS_IsMainThread());
43 NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
44
45 EME_LOG("MediaDrmCDMProxy::Init (%s, %s) %s",
46 NS_ConvertUTF16toUTF8(aOrigin).get(),
47 NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
48 NS_ConvertUTF16toUTF8(aName).get());
49
50 // Create a thread to work with cdm.
51 if (!mOwnerThread) {
52 nsresult rv =
53 NS_NewNamedThread("MDCDMThread", getter_AddRefs(mOwnerThread));
54 if (NS_FAILED(rv)) {
55 RejectPromiseWithStateError(
56 aPromiseId, nsLiteralCString(
57 "Couldn't create CDM thread MediaDrmCDMProxy::Init"));
58 return;
59 }
60 }
61
62 mCDM = mozilla::MakeUnique<MediaDrmProxySupport>(mKeySystem);
63 nsCOMPtr<nsIRunnable> task(
64 NewRunnableMethod<uint32_t>("MediaDrmCDMProxy::md_Init", this,
65 &MediaDrmCDMProxy::md_Init, aPromiseId));
66 mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
67 }
68
CreateSession(uint32_t aCreateSessionToken,MediaKeySessionType aSessionType,PromiseId aPromiseId,const nsAString & aInitDataType,nsTArray<uint8_t> & aInitData)69 void MediaDrmCDMProxy::CreateSession(uint32_t aCreateSessionToken,
70 MediaKeySessionType aSessionType,
71 PromiseId aPromiseId,
72 const nsAString& aInitDataType,
73 nsTArray<uint8_t>& aInitData) {
74 MOZ_ASSERT(NS_IsMainThread());
75 MOZ_ASSERT(mOwnerThread);
76
77 UniquePtr<CreateSessionData> data(new CreateSessionData());
78 data->mSessionType = aSessionType;
79 data->mCreateSessionToken = aCreateSessionToken;
80 data->mPromiseId = aPromiseId;
81 data->mInitDataType = NS_ConvertUTF16toUTF8(aInitDataType);
82 data->mInitData = std::move(aInitData);
83
84 nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<CreateSessionData>&&>(
85 "MediaDrmCDMProxy::md_CreateSession", this,
86 &MediaDrmCDMProxy::md_CreateSession, std::move(data)));
87 mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
88 }
89
LoadSession(PromiseId aPromiseId,dom::MediaKeySessionType aSessionType,const nsAString & aSessionId)90 void MediaDrmCDMProxy::LoadSession(PromiseId aPromiseId,
91 dom::MediaKeySessionType aSessionType,
92 const nsAString& aSessionId) {
93 // TODO: Implement LoadSession.
94 RejectPromiseWithStateError(
95 aPromiseId, "Currently Fennec does not support LoadSession"_ns);
96 }
97
SetServerCertificate(PromiseId aPromiseId,nsTArray<uint8_t> & aCert)98 void MediaDrmCDMProxy::SetServerCertificate(PromiseId aPromiseId,
99 nsTArray<uint8_t>& aCert) {
100 MOZ_ASSERT(NS_IsMainThread());
101 MOZ_ASSERT(mOwnerThread);
102
103 mOwnerThread->Dispatch(NewRunnableMethod<PromiseId, const nsTArray<uint8_t>>(
104 "MediaDrmCDMProxy::md_SetServerCertificate", this,
105 &MediaDrmCDMProxy::md_SetServerCertificate,
106 aPromiseId, std::move(aCert)),
107 NS_DISPATCH_NORMAL);
108 }
109
UpdateSession(const nsAString & aSessionId,PromiseId aPromiseId,nsTArray<uint8_t> & aResponse)110 void MediaDrmCDMProxy::UpdateSession(const nsAString& aSessionId,
111 PromiseId aPromiseId,
112 nsTArray<uint8_t>& aResponse) {
113 MOZ_ASSERT(NS_IsMainThread());
114 MOZ_ASSERT(mOwnerThread);
115 NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
116
117 UniquePtr<UpdateSessionData> data(new UpdateSessionData());
118 data->mPromiseId = aPromiseId;
119 data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
120 data->mResponse = std::move(aResponse);
121
122 nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<UpdateSessionData>&&>(
123 "MediaDrmCDMProxy::md_UpdateSession", this,
124 &MediaDrmCDMProxy::md_UpdateSession, std::move(data)));
125 mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
126 }
127
CloseSession(const nsAString & aSessionId,PromiseId aPromiseId)128 void MediaDrmCDMProxy::CloseSession(const nsAString& aSessionId,
129 PromiseId aPromiseId) {
130 MOZ_ASSERT(NS_IsMainThread());
131 MOZ_ASSERT(mOwnerThread);
132 NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
133
134 UniquePtr<SessionOpData> data(new SessionOpData());
135 data->mPromiseId = aPromiseId;
136 data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
137
138 nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<SessionOpData>&&>(
139 "MediaDrmCDMProxy::md_CloseSession", this,
140 &MediaDrmCDMProxy::md_CloseSession, std::move(data)));
141 mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
142 }
143
RemoveSession(const nsAString & aSessionId,PromiseId aPromiseId)144 void MediaDrmCDMProxy::RemoveSession(const nsAString& aSessionId,
145 PromiseId aPromiseId) {
146 // TODO: Implement RemoveSession.
147 RejectPromiseWithStateError(
148 aPromiseId, "Currently Fennec does not support RemoveSession"_ns);
149 }
150
QueryOutputProtectionStatus()151 void MediaDrmCDMProxy::QueryOutputProtectionStatus() {
152 // TODO(bryce): determine if this is needed for Android and implement as
153 // needed. See also `NotifyOutputProtectionStatus`.
154 }
155
NotifyOutputProtectionStatus(OutputProtectionCheckStatus aCheckStatus,OutputProtectionCaptureStatus aCaptureStatus)156 void MediaDrmCDMProxy::NotifyOutputProtectionStatus(
157 OutputProtectionCheckStatus aCheckStatus,
158 OutputProtectionCaptureStatus aCaptureStatus) {
159 // TODO(bryce): determine if this is needed for Android and implement as
160 // needed. See also `QueryOutputProtectionStatus`.
161 }
162
Shutdown()163 void MediaDrmCDMProxy::Shutdown() {
164 MOZ_ASSERT(NS_IsMainThread());
165 MOZ_ASSERT(mOwnerThread);
166 nsCOMPtr<nsIRunnable> task(NewRunnableMethod(
167 "MediaDrmCDMProxy::md_Shutdown", this, &MediaDrmCDMProxy::md_Shutdown));
168
169 mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
170 mOwnerThread->Shutdown();
171 mOwnerThread = nullptr;
172 }
173
Terminated()174 void MediaDrmCDMProxy::Terminated() {
175 // TODO: Implement Terminated.
176 // Should find a way to handle the case when remote side MediaDrm crashed.
177 }
178
GetNodeId() const179 const nsCString& MediaDrmCDMProxy::GetNodeId() const { return mNodeId; }
180
OnSetSessionId(uint32_t aCreateSessionToken,const nsAString & aSessionId)181 void MediaDrmCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken,
182 const nsAString& aSessionId) {
183 MOZ_ASSERT(NS_IsMainThread());
184 if (mKeys.IsNull()) {
185 return;
186 }
187
188 RefPtr<dom::MediaKeySession> session(
189 mKeys->GetPendingSession(aCreateSessionToken));
190 if (session) {
191 session->SetSessionId(aSessionId);
192 }
193 }
194
OnResolveLoadSessionPromise(uint32_t aPromiseId,bool aSuccess)195 void MediaDrmCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId,
196 bool aSuccess) {
197 MOZ_ASSERT(NS_IsMainThread());
198 if (mKeys.IsNull()) {
199 return;
200 }
201 mKeys->OnSessionLoaded(aPromiseId, aSuccess);
202 }
203
OnSessionMessage(const nsAString & aSessionId,dom::MediaKeyMessageType aMessageType,const nsTArray<uint8_t> & aMessage)204 void MediaDrmCDMProxy::OnSessionMessage(const nsAString& aSessionId,
205 dom::MediaKeyMessageType aMessageType,
206 const nsTArray<uint8_t>& aMessage) {
207 MOZ_ASSERT(NS_IsMainThread());
208 if (mKeys.IsNull()) {
209 return;
210 }
211 RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
212 if (session) {
213 session->DispatchKeyMessage(aMessageType, aMessage);
214 }
215 }
216
OnExpirationChange(const nsAString & aSessionId,UnixTime aExpiryTime)217 void MediaDrmCDMProxy::OnExpirationChange(const nsAString& aSessionId,
218 UnixTime aExpiryTime) {
219 MOZ_ASSERT(NS_IsMainThread());
220 if (mKeys.IsNull()) {
221 return;
222 }
223 RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
224 if (session) {
225 session->SetExpiration(static_cast<double>(aExpiryTime));
226 }
227 }
228
OnSessionClosed(const nsAString & aSessionId)229 void MediaDrmCDMProxy::OnSessionClosed(const nsAString& aSessionId) {
230 MOZ_ASSERT(NS_IsMainThread());
231 if (mKeys.IsNull()) {
232 return;
233 }
234 RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
235 if (session) {
236 session->OnClosed();
237 }
238 }
239
OnSessionError(const nsAString & aSessionId,nsresult aException,uint32_t aSystemCode,const nsAString & aMsg)240 void MediaDrmCDMProxy::OnSessionError(const nsAString& aSessionId,
241 nsresult aException, uint32_t aSystemCode,
242 const nsAString& aMsg) {
243 MOZ_ASSERT(NS_IsMainThread());
244 if (mKeys.IsNull()) {
245 return;
246 }
247 RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
248 if (session) {
249 session->DispatchKeyError(aSystemCode);
250 }
251 }
252
OnRejectPromise(uint32_t aPromiseId,ErrorResult && aException,const nsCString & aMsg)253 void MediaDrmCDMProxy::OnRejectPromise(uint32_t aPromiseId,
254 ErrorResult&& aException,
255 const nsCString& aMsg) {
256 MOZ_ASSERT(NS_IsMainThread());
257 RejectPromise(aPromiseId, std::move(aException), aMsg);
258 }
259
Decrypt(MediaRawData * aSample)260 RefPtr<DecryptPromise> MediaDrmCDMProxy::Decrypt(MediaRawData* aSample) {
261 MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypting individually");
262 return nullptr;
263 }
264
OnDecrypted(uint32_t aId,DecryptStatus aResult,const nsTArray<uint8_t> & aDecryptedData)265 void MediaDrmCDMProxy::OnDecrypted(uint32_t aId, DecryptStatus aResult,
266 const nsTArray<uint8_t>& aDecryptedData) {
267 MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypted event");
268 }
269
RejectPromise(PromiseId aId,ErrorResult && aException,const nsCString & aReason)270 void MediaDrmCDMProxy::RejectPromise(PromiseId aId, ErrorResult&& aException,
271 const nsCString& aReason) {
272 if (NS_IsMainThread()) {
273 if (!mKeys.IsNull()) {
274 mKeys->RejectPromise(aId, std::move(aException), aReason);
275 }
276 } else {
277 nsCOMPtr<nsIRunnable> task(
278 new RejectPromiseTask(this, aId, std::move(aException), aReason));
279 mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
280 }
281 }
282
RejectPromiseWithStateError(PromiseId aId,const nsCString & aReason)283 void MediaDrmCDMProxy::RejectPromiseWithStateError(PromiseId aId,
284 const nsCString& aReason) {
285 ErrorResult rv;
286 rv.ThrowInvalidStateError(aReason);
287 RejectPromise(aId, std::move(rv), aReason);
288 }
289
ResolvePromise(PromiseId aId)290 void MediaDrmCDMProxy::ResolvePromise(PromiseId aId) {
291 if (NS_IsMainThread()) {
292 if (!mKeys.IsNull()) {
293 mKeys->ResolvePromise(aId);
294 } else {
295 NS_WARNING("MediaDrmCDMProxy unable to resolve promise!");
296 }
297 } else {
298 nsCOMPtr<nsIRunnable> task;
299 task =
300 NewRunnableMethod<PromiseId>("MediaDrmCDMProxy::ResolvePromise", this,
301 &MediaDrmCDMProxy::ResolvePromise, aId);
302 mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
303 }
304 }
305
306 template <typename T>
ResolvePromiseWithResult(PromiseId aId,const T & aResult)307 void MediaDrmCDMProxy::ResolvePromiseWithResult(PromiseId aId,
308 const T& aResult) {
309 if (NS_IsMainThread()) {
310 if (!mKeys.IsNull()) {
311 mKeys->ResolvePromiseWithResult(aId, aResult);
312 } else {
313 NS_WARNING("MediaDrmCDMProxy unable to resolve promise!");
314 }
315 return;
316 }
317
318 nsCOMPtr<nsIRunnable> task;
319 task = NewRunnableMethod<PromiseId, T>(
320 "MediaDrmCDMProxy::ResolvePromiseWithResult", this,
321 &MediaDrmCDMProxy::ResolvePromiseWithResult<T>, aId, aResult);
322 mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
323 }
324
KeySystem() const325 const nsString& MediaDrmCDMProxy::KeySystem() const { return mKeySystem; }
326
Capabilites()327 DataMutex<CDMCaps>& MediaDrmCDMProxy::Capabilites() { return mCapabilites; }
328
OnKeyStatusesChange(const nsAString & aSessionId)329 void MediaDrmCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) {
330 MOZ_ASSERT(NS_IsMainThread());
331 if (mKeys.IsNull()) {
332 return;
333 }
334 RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
335 if (session) {
336 session->DispatchKeyStatusesChange();
337 }
338 }
339
GetStatusForPolicy(PromiseId aPromiseId,const nsAString & aMinHdcpVersion)340 void MediaDrmCDMProxy::GetStatusForPolicy(PromiseId aPromiseId,
341 const nsAString& aMinHdcpVersion) {
342 // TODO: Implement GetStatusForPolicy.
343 constexpr auto err =
344 "Currently Fennec does not support GetStatusForPolicy"_ns;
345
346 ErrorResult rv;
347 rv.ThrowNotSupportedError(err);
348 RejectPromise(aPromiseId, std::move(rv), err);
349 }
350
351 #ifdef DEBUG
IsOnOwnerThread()352 bool MediaDrmCDMProxy::IsOnOwnerThread() {
353 return NS_GetCurrentThread() == mOwnerThread;
354 }
355 #endif
356
GetMediaDrmStubId() const357 const nsString& MediaDrmCDMProxy::GetMediaDrmStubId() const {
358 MOZ_ASSERT(mCDM);
359 return mCDM->GetMediaDrmStubId();
360 }
361
OnCDMCreated(uint32_t aPromiseId)362 void MediaDrmCDMProxy::OnCDMCreated(uint32_t aPromiseId) {
363 MOZ_ASSERT(NS_IsMainThread());
364 if (mKeys.IsNull()) {
365 return;
366 }
367
368 if (mCDM) {
369 mKeys->OnCDMCreated(aPromiseId, 0);
370 return;
371 }
372
373 // No CDM? Just reject the promise.
374 constexpr auto err = "Null CDM in OnCDMCreated()"_ns;
375 ErrorResult rv;
376 rv.ThrowInvalidStateError(err);
377 mKeys->RejectPromise(aPromiseId, std::move(rv), err);
378 }
379
md_Init(uint32_t aPromiseId)380 void MediaDrmCDMProxy::md_Init(uint32_t aPromiseId) {
381 MOZ_ASSERT(IsOnOwnerThread());
382 MOZ_ASSERT(mCDM);
383
384 mCallback.reset(new MediaDrmCDMCallbackProxy(this));
385 mCDM->Init(mCallback.get());
386 nsCOMPtr<nsIRunnable> task(
387 NewRunnableMethod<uint32_t>("MediaDrmCDMProxy::OnCDMCreated", this,
388 &MediaDrmCDMProxy::OnCDMCreated, aPromiseId));
389 mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
390 }
391
md_CreateSession(UniquePtr<CreateSessionData> && aData)392 void MediaDrmCDMProxy::md_CreateSession(UniquePtr<CreateSessionData>&& aData) {
393 MOZ_ASSERT(IsOnOwnerThread());
394
395 if (!mCDM) {
396 RejectPromiseWithStateError(aData->mPromiseId,
397 "Null CDM in md_CreateSession"_ns);
398 return;
399 }
400
401 mCDM->CreateSession(aData->mCreateSessionToken, aData->mPromiseId,
402 aData->mInitDataType, aData->mInitData,
403 ToMediaDrmSessionType(aData->mSessionType));
404 }
405
md_SetServerCertificate(PromiseId aPromiseId,const nsTArray<uint8_t> & aCert)406 void MediaDrmCDMProxy::md_SetServerCertificate(PromiseId aPromiseId,
407 const nsTArray<uint8_t>& aCert) {
408 MOZ_ASSERT(IsOnOwnerThread());
409
410 if (!mCDM) {
411 RejectPromiseWithStateError(aPromiseId,
412 "Null CDM in md_SetServerCertificate"_ns);
413 return;
414 }
415
416 if (mCDM->SetServerCertificate(aCert)) {
417 ResolvePromiseWithResult(aPromiseId, true);
418 } else {
419 RejectPromiseWithStateError(
420 aPromiseId, "MediaDrmCDMProxy unable to set server certificate"_ns);
421 }
422 }
423
md_UpdateSession(UniquePtr<UpdateSessionData> && aData)424 void MediaDrmCDMProxy::md_UpdateSession(UniquePtr<UpdateSessionData>&& aData) {
425 MOZ_ASSERT(IsOnOwnerThread());
426
427 if (!mCDM) {
428 RejectPromiseWithStateError(aData->mPromiseId,
429 "Null CDM in md_UpdateSession"_ns);
430 return;
431 }
432 mCDM->UpdateSession(aData->mPromiseId, aData->mSessionId, aData->mResponse);
433 }
434
md_CloseSession(UniquePtr<SessionOpData> && aData)435 void MediaDrmCDMProxy::md_CloseSession(UniquePtr<SessionOpData>&& aData) {
436 MOZ_ASSERT(IsOnOwnerThread());
437
438 if (!mCDM) {
439 RejectPromiseWithStateError(aData->mPromiseId,
440 "Null CDM in md_CloseSession"_ns);
441 return;
442 }
443 mCDM->CloseSession(aData->mPromiseId, aData->mSessionId);
444 }
445
md_Shutdown()446 void MediaDrmCDMProxy::md_Shutdown() {
447 MOZ_ASSERT(IsOnOwnerThread());
448 MOZ_ASSERT(mCDM);
449 if (mShutdownCalled) {
450 return;
451 }
452 mShutdownCalled = true;
453 mCDM->Shutdown();
454 mCDM = nullptr;
455 }
456
457 } // namespace mozilla
458