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