Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "GMPDecryptorParent.h"
7 : #include "GMPContentParent.h"
8 : #include "MediaData.h"
9 : #include "mozilla/SizePrintfMacros.h"
10 : #include "mozilla/Unused.h"
11 :
12 : namespace mozilla {
13 :
14 : #ifdef LOG
15 : #undef LOG
16 : #endif
17 :
18 : extern LogModule* GetGMPLog();
19 :
20 : #define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg)
21 : #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
22 : #define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
23 :
24 : namespace gmp {
25 :
26 0 : GMPDecryptorParent::GMPDecryptorParent(GMPContentParent* aPlugin)
27 : : mIsOpen(false)
28 : , mShuttingDown(false)
29 : , mActorDestroyed(false)
30 : , mPlugin(aPlugin)
31 0 : , mPluginId(aPlugin->GetPluginId())
32 : , mCallback(nullptr)
33 : #ifdef DEBUG
34 0 : , mGMPEventTarget(aPlugin->GMPEventTarget())
35 : #endif
36 : {
37 0 : MOZ_ASSERT(mPlugin && mGMPEventTarget);
38 0 : }
39 :
40 0 : GMPDecryptorParent::~GMPDecryptorParent()
41 : {
42 0 : }
43 :
44 : mozilla::ipc::IPCResult
45 0 : GMPDecryptorParent::RecvSetDecryptorId(const uint32_t& aId)
46 : {
47 0 : if (!mIsOpen) {
48 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
49 0 : return IPC_FAIL_NO_REASON(this);
50 : }
51 0 : mCallback->SetDecryptorId(aId);
52 0 : return IPC_OK();
53 : }
54 :
55 : nsresult
56 0 : GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback,
57 : bool aDistinctiveIdentifierRequired,
58 : bool aPersistentStateRequired)
59 : {
60 0 : LOGD(("GMPDecryptorParent[%p]::Init()", this));
61 :
62 0 : if (mIsOpen) {
63 0 : NS_WARNING("Trying to re-use an in-use GMP decrypter!");
64 0 : return NS_ERROR_FAILURE;
65 : }
66 0 : mCallback = aCallback;
67 0 : if (!SendInit(aDistinctiveIdentifierRequired, aPersistentStateRequired)) {
68 0 : return NS_ERROR_FAILURE;
69 : }
70 0 : mIsOpen = true;
71 0 : return NS_OK;
72 : }
73 :
74 : void
75 0 : GMPDecryptorParent::CreateSession(uint32_t aCreateSessionToken,
76 : uint32_t aPromiseId,
77 : const nsCString& aInitDataType,
78 : const nsTArray<uint8_t>& aInitData,
79 : GMPSessionType aSessionType)
80 : {
81 0 : LOGD(("GMPDecryptorParent[%p]::CreateSession(token=%u, promiseId=%u, aInitData='%s')",
82 : this, aCreateSessionToken, aPromiseId, ToHexString(aInitData).get()));
83 :
84 0 : if (!mIsOpen) {
85 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
86 0 : return;
87 : }
88 : // Caller should ensure parameters passed in from JS are valid.
89 0 : MOZ_ASSERT(!aInitDataType.IsEmpty() && !aInitData.IsEmpty());
90 0 : Unused << SendCreateSession(aCreateSessionToken, aPromiseId, aInitDataType, aInitData, aSessionType);
91 : }
92 :
93 : void
94 0 : GMPDecryptorParent::LoadSession(uint32_t aPromiseId,
95 : const nsCString& aSessionId)
96 : {
97 0 : LOGD(("GMPDecryptorParent[%p]::LoadSession(sessionId='%s', promiseId=%u)",
98 : this, aSessionId.get(), aPromiseId));
99 0 : if (!mIsOpen) {
100 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
101 0 : return;
102 : }
103 : // Caller should ensure parameters passed in from JS are valid.
104 0 : MOZ_ASSERT(!aSessionId.IsEmpty());
105 0 : Unused << SendLoadSession(aPromiseId, aSessionId);
106 : }
107 :
108 : void
109 0 : GMPDecryptorParent::UpdateSession(uint32_t aPromiseId,
110 : const nsCString& aSessionId,
111 : const nsTArray<uint8_t>& aResponse)
112 : {
113 0 : LOGD(("GMPDecryptorParent[%p]::UpdateSession(sessionId='%s', promiseId=%u response='%s')",
114 : this, aSessionId.get(), aPromiseId, ToHexString(aResponse).get()));
115 :
116 0 : if (!mIsOpen) {
117 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
118 0 : return;
119 : }
120 : // Caller should ensure parameters passed in from JS are valid.
121 0 : MOZ_ASSERT(!aSessionId.IsEmpty() && !aResponse.IsEmpty());
122 0 : Unused << SendUpdateSession(aPromiseId, aSessionId, aResponse);
123 : }
124 :
125 : void
126 0 : GMPDecryptorParent::CloseSession(uint32_t aPromiseId,
127 : const nsCString& aSessionId)
128 : {
129 0 : LOGD(("GMPDecryptorParent[%p]::CloseSession(sessionId='%s', promiseId=%u)",
130 : this, aSessionId.get(), aPromiseId));
131 :
132 0 : if (!mIsOpen) {
133 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
134 0 : return;
135 : }
136 : // Caller should ensure parameters passed in from JS are valid.
137 0 : MOZ_ASSERT(!aSessionId.IsEmpty());
138 0 : Unused << SendCloseSession(aPromiseId, aSessionId);
139 : }
140 :
141 : void
142 0 : GMPDecryptorParent::RemoveSession(uint32_t aPromiseId,
143 : const nsCString& aSessionId)
144 : {
145 0 : LOGD(("GMPDecryptorParent[%p]::RemoveSession(sessionId='%s', promiseId=%u)",
146 : this, aSessionId.get(), aPromiseId));
147 :
148 0 : if (!mIsOpen) {
149 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
150 0 : return;
151 : }
152 : // Caller should ensure parameters passed in from JS are valid.
153 0 : MOZ_ASSERT(!aSessionId.IsEmpty());
154 0 : Unused << SendRemoveSession(aPromiseId, aSessionId);
155 : }
156 :
157 : void
158 0 : GMPDecryptorParent::SetServerCertificate(uint32_t aPromiseId,
159 : const nsTArray<uint8_t>& aServerCert)
160 : {
161 0 : LOGD(("GMPDecryptorParent[%p]::SetServerCertificate(promiseId=%u)",
162 : this, aPromiseId));
163 :
164 0 : if (!mIsOpen) {
165 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
166 0 : return;
167 : }
168 : // Caller should ensure parameters passed in from JS are valid.
169 0 : MOZ_ASSERT(!aServerCert.IsEmpty());
170 0 : Unused << SendSetServerCertificate(aPromiseId, aServerCert);
171 : }
172 :
173 : void
174 0 : GMPDecryptorParent::Decrypt(uint32_t aId,
175 : const CryptoSample& aCrypto,
176 : const nsTArray<uint8_t>& aBuffer)
177 : {
178 0 : LOGV(("GMPDecryptorParent[%p]::Decrypt(id=%d)", this, aId));
179 :
180 0 : if (!mIsOpen) {
181 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
182 0 : return;
183 : }
184 :
185 : // Caller should ensure parameters passed in are valid.
186 0 : MOZ_ASSERT(!aBuffer.IsEmpty());
187 :
188 0 : if (aCrypto.mValid) {
189 : GMPDecryptionData data(aCrypto.mKeyId,
190 : aCrypto.mIV,
191 : aCrypto.mPlainSizes,
192 : aCrypto.mEncryptedSizes,
193 0 : aCrypto.mSessionIds);
194 :
195 0 : Unused << SendDecrypt(aId, aBuffer, data);
196 : } else {
197 0 : GMPDecryptionData data;
198 0 : Unused << SendDecrypt(aId, aBuffer, data);
199 : }
200 : }
201 :
202 : mozilla::ipc::IPCResult
203 0 : GMPDecryptorParent::RecvSetSessionId(const uint32_t& aCreateSessionId,
204 : const nsCString& aSessionId)
205 : {
206 0 : LOGD(("GMPDecryptorParent[%p]::RecvSetSessionId(token=%u, sessionId='%s')",
207 : this, aCreateSessionId, aSessionId.get()));
208 :
209 0 : if (!mIsOpen) {
210 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
211 0 : return IPC_FAIL_NO_REASON(this);
212 : }
213 0 : mCallback->SetSessionId(aCreateSessionId, aSessionId);
214 0 : return IPC_OK();
215 : }
216 :
217 : mozilla::ipc::IPCResult
218 0 : GMPDecryptorParent::RecvResolveLoadSessionPromise(const uint32_t& aPromiseId,
219 : const bool& aSuccess)
220 : {
221 0 : LOGD(("GMPDecryptorParent[%p]::RecvResolveLoadSessionPromise(promiseId=%u)",
222 : this, aPromiseId));
223 :
224 0 : if (!mIsOpen) {
225 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
226 0 : return IPC_FAIL_NO_REASON(this);
227 : }
228 0 : mCallback->ResolveLoadSessionPromise(aPromiseId, aSuccess);
229 0 : return IPC_OK();
230 : }
231 :
232 : mozilla::ipc::IPCResult
233 0 : GMPDecryptorParent::RecvResolvePromise(const uint32_t& aPromiseId)
234 : {
235 0 : LOGD(("GMPDecryptorParent[%p]::RecvResolvePromise(promiseId=%u)",
236 : this, aPromiseId));
237 :
238 0 : if (!mIsOpen) {
239 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
240 0 : return IPC_FAIL_NO_REASON(this);
241 : }
242 0 : mCallback->ResolvePromise(aPromiseId);
243 0 : return IPC_OK();
244 : }
245 :
246 : nsresult
247 0 : GMPExToNsresult(GMPDOMException aDomException) {
248 0 : switch (aDomException) {
249 0 : case kGMPNoModificationAllowedError: return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
250 0 : case kGMPNotFoundError: return NS_ERROR_DOM_NOT_FOUND_ERR;
251 0 : case kGMPNotSupportedError: return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
252 0 : case kGMPInvalidStateError: return NS_ERROR_DOM_INVALID_STATE_ERR;
253 0 : case kGMPSyntaxError: return NS_ERROR_DOM_SYNTAX_ERR;
254 0 : case kGMPInvalidModificationError: return NS_ERROR_DOM_INVALID_MODIFICATION_ERR;
255 0 : case kGMPInvalidAccessError: return NS_ERROR_DOM_INVALID_ACCESS_ERR;
256 0 : case kGMPSecurityError: return NS_ERROR_DOM_SECURITY_ERR;
257 0 : case kGMPAbortError: return NS_ERROR_DOM_ABORT_ERR;
258 0 : case kGMPQuotaExceededError: return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
259 0 : case kGMPTimeoutError: return NS_ERROR_DOM_TIMEOUT_ERR;
260 0 : case kGMPTypeError: return NS_ERROR_DOM_TYPE_ERR;
261 0 : default: return NS_ERROR_DOM_UNKNOWN_ERR;
262 : }
263 : }
264 :
265 : mozilla::ipc::IPCResult
266 0 : GMPDecryptorParent::RecvRejectPromise(const uint32_t& aPromiseId,
267 : const GMPDOMException& aException,
268 : const nsCString& aMessage)
269 : {
270 0 : LOGD(("GMPDecryptorParent[%p]::RecvRejectPromise(promiseId=%u, exception=%d, msg='%s')",
271 : this, aPromiseId, aException, aMessage.get()));
272 :
273 0 : if (!mIsOpen) {
274 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
275 0 : return IPC_FAIL_NO_REASON(this);
276 : }
277 0 : mCallback->RejectPromise(aPromiseId, GMPExToNsresult(aException), aMessage);
278 0 : return IPC_OK();
279 : }
280 :
281 :
282 : static dom::MediaKeyMessageType
283 0 : ToMediaKeyMessageType(GMPSessionMessageType aMessageType) {
284 0 : switch (aMessageType) {
285 0 : case kGMPLicenseRequest: return dom::MediaKeyMessageType::License_request;
286 0 : case kGMPLicenseRenewal: return dom::MediaKeyMessageType::License_renewal;
287 0 : case kGMPLicenseRelease: return dom::MediaKeyMessageType::License_release;
288 0 : case kGMPIndividualizationRequest: return dom::MediaKeyMessageType::Individualization_request;
289 0 : default: return dom::MediaKeyMessageType::License_request;
290 : };
291 : };
292 :
293 : mozilla::ipc::IPCResult
294 0 : GMPDecryptorParent::RecvSessionMessage(const nsCString& aSessionId,
295 : const GMPSessionMessageType& aMessageType,
296 : nsTArray<uint8_t>&& aMessage)
297 : {
298 0 : LOGD(("GMPDecryptorParent[%p]::RecvSessionMessage(sessionId='%s', type=%d, msg='%s')",
299 : this, aSessionId.get(), aMessageType, ToHexString(aMessage).get()));
300 :
301 0 : if (!mIsOpen) {
302 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
303 0 : return IPC_FAIL_NO_REASON(this);
304 : }
305 0 : mCallback->SessionMessage(aSessionId, ToMediaKeyMessageType(aMessageType), aMessage);
306 0 : return IPC_OK();
307 : }
308 :
309 : mozilla::ipc::IPCResult
310 0 : GMPDecryptorParent::RecvExpirationChange(const nsCString& aSessionId,
311 : const double& aExpiryTime)
312 : {
313 0 : LOGD(("GMPDecryptorParent[%p]::RecvExpirationChange(sessionId='%s', expiry=%lf)",
314 : this, aSessionId.get(), aExpiryTime));
315 :
316 0 : if (!mIsOpen) {
317 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
318 0 : return IPC_FAIL_NO_REASON(this);
319 : }
320 0 : mCallback->ExpirationChange(aSessionId, aExpiryTime);
321 0 : return IPC_OK();
322 : }
323 :
324 : mozilla::ipc::IPCResult
325 0 : GMPDecryptorParent::RecvSessionClosed(const nsCString& aSessionId)
326 : {
327 0 : LOGD(("GMPDecryptorParent[%p]::RecvSessionClosed(sessionId='%s')",
328 : this, aSessionId.get()));
329 :
330 0 : if (!mIsOpen) {
331 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
332 0 : return IPC_FAIL_NO_REASON(this);
333 : }
334 0 : mCallback->SessionClosed(aSessionId);
335 0 : return IPC_OK();
336 : }
337 :
338 : mozilla::ipc::IPCResult
339 0 : GMPDecryptorParent::RecvSessionError(const nsCString& aSessionId,
340 : const GMPDOMException& aException,
341 : const uint32_t& aSystemCode,
342 : const nsCString& aMessage)
343 : {
344 0 : LOGD(("GMPDecryptorParent[%p]::RecvSessionError(sessionId='%s', exception=%d, sysCode=%d, msg='%s')",
345 : this, aSessionId.get(),
346 : aException, aSystemCode, aMessage.get()));
347 :
348 0 : if (!mIsOpen) {
349 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
350 0 : return IPC_FAIL_NO_REASON(this);
351 : }
352 0 : mCallback->SessionError(aSessionId,
353 : GMPExToNsresult(aException),
354 : aSystemCode,
355 0 : aMessage);
356 0 : return IPC_OK();
357 : }
358 :
359 : static dom::MediaKeyStatus
360 0 : ToMediaKeyStatus(GMPMediaKeyStatus aStatus) {
361 0 : switch (aStatus) {
362 0 : case kGMPUsable: return dom::MediaKeyStatus::Usable;
363 0 : case kGMPExpired: return dom::MediaKeyStatus::Expired;
364 0 : case kGMPOutputDownscaled: return dom::MediaKeyStatus::Output_downscaled;
365 0 : case kGMPOutputRestricted: return dom::MediaKeyStatus::Output_restricted;
366 0 : case kGMPInternalError: return dom::MediaKeyStatus::Internal_error;
367 0 : case kGMPReleased: return dom::MediaKeyStatus::Released;
368 0 : case kGMPStatusPending: return dom::MediaKeyStatus::Status_pending;
369 0 : default: return dom::MediaKeyStatus::Internal_error;
370 : }
371 : }
372 :
373 : mozilla::ipc::IPCResult
374 0 : GMPDecryptorParent::RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
375 : InfallibleTArray<GMPKeyInformation>&& aKeyInfos)
376 : {
377 0 : LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(sessionId='%s', KeyInfos len='%" PRIuSIZE "')",
378 : this, aSessionId.get(), aKeyInfos.Length()));
379 :
380 0 : if (mIsOpen) {
381 0 : nsTArray<CDMKeyInfo> cdmKeyInfos(aKeyInfos.Length());
382 0 : for (uint32_t i = 0; i < aKeyInfos.Length(); i++) {
383 0 : LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(keyId=%s, gmp-status=%d)",
384 : this, ToHexString(aKeyInfos[i].keyId()).get(), aKeyInfos[i].status()));
385 : // If the status is kGMPUnknown, we're going to forget(remove) that key info.
386 0 : if (aKeyInfos[i].status() != kGMPUnknown) {
387 0 : auto status = ToMediaKeyStatus(aKeyInfos[i].status());
388 0 : cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId(),
389 0 : dom::Optional<dom::MediaKeyStatus>(status)));
390 : } else {
391 0 : cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId()));
392 : }
393 : }
394 0 : mCallback->BatchedKeyStatusChanged(aSessionId, cdmKeyInfos);
395 : }
396 0 : return IPC_OK();
397 : }
398 :
399 : DecryptStatus
400 0 : ToDecryptStatus(GMPErr aError)
401 : {
402 0 : switch (aError) {
403 0 : case GMPNoErr: return eme::Ok;
404 0 : case GMPNoKeyErr: return eme::NoKeyErr;
405 0 : case GMPAbortedErr: return eme::AbortedErr;
406 0 : default: return eme::GenericErr;
407 : }
408 : }
409 :
410 : mozilla::ipc::IPCResult
411 0 : GMPDecryptorParent::RecvDecrypted(const uint32_t& aId,
412 : const GMPErr& aErr,
413 : InfallibleTArray<uint8_t>&& aBuffer)
414 : {
415 0 : LOGV(("GMPDecryptorParent[%p]::RecvDecrypted(id=%d, err=%d)",
416 : this, aId, aErr));
417 :
418 0 : if (!mIsOpen) {
419 0 : NS_WARNING("Trying to use a dead GMP decrypter!");
420 0 : return IPC_FAIL_NO_REASON(this);
421 : }
422 0 : mCallback->Decrypted(aId, ToDecryptStatus(aErr), aBuffer);
423 0 : return IPC_OK();
424 : }
425 :
426 : mozilla::ipc::IPCResult
427 0 : GMPDecryptorParent::RecvShutdown()
428 : {
429 0 : LOGD(("GMPDecryptorParent[%p]::RecvShutdown()", this));
430 :
431 0 : Shutdown();
432 0 : return IPC_OK();
433 : }
434 :
435 : // Note: may be called via Terminated()
436 : void
437 0 : GMPDecryptorParent::Close()
438 : {
439 0 : LOGD(("GMPDecryptorParent[%p]::Close()", this));
440 0 : MOZ_ASSERT(mGMPEventTarget->IsOnCurrentThread());
441 :
442 : // Consumer is done with us; we can shut down. No more callbacks should
443 : // be made to mCallback. Note: do this before Shutdown()!
444 0 : mCallback = nullptr;
445 : // Let Shutdown mark us as dead so it knows if we had been alive
446 :
447 : // In case this is the last reference
448 0 : RefPtr<GMPDecryptorParent> kungfudeathgrip(this);
449 0 : this->Release();
450 0 : Shutdown();
451 0 : }
452 :
453 : void
454 0 : GMPDecryptorParent::Shutdown()
455 : {
456 0 : LOGD(("GMPDecryptorParent[%p]::Shutdown()", this));
457 0 : MOZ_ASSERT(mGMPEventTarget->IsOnCurrentThread());
458 :
459 0 : if (mShuttingDown) {
460 0 : return;
461 : }
462 0 : mShuttingDown = true;
463 :
464 : // Notify client we're gone! Won't occur after Close()
465 0 : if (mCallback) {
466 0 : mCallback->Terminated();
467 0 : mCallback = nullptr;
468 : }
469 :
470 0 : mIsOpen = false;
471 0 : if (!mActorDestroyed) {
472 0 : Unused << SendDecryptingComplete();
473 : }
474 : }
475 :
476 : // Note: Keep this sync'd up with Shutdown
477 : void
478 0 : GMPDecryptorParent::ActorDestroy(ActorDestroyReason aWhy)
479 : {
480 0 : LOGD(("GMPDecryptorParent[%p]::ActorDestroy(reason=%d)", this, aWhy));
481 :
482 0 : mIsOpen = false;
483 0 : mActorDestroyed = true;
484 0 : if (mCallback) {
485 : // May call Close() (and Shutdown()) immediately or with a delay
486 0 : mCallback->Terminated();
487 0 : mCallback = nullptr;
488 : }
489 0 : if (mPlugin) {
490 0 : mPlugin->DecryptorDestroyed(this);
491 0 : mPlugin = nullptr;
492 : }
493 0 : MaybeDisconnect(aWhy == AbnormalShutdown);
494 0 : }
495 :
496 : mozilla::ipc::IPCResult
497 0 : GMPDecryptorParent::Recv__delete__()
498 : {
499 0 : LOGD(("GMPDecryptorParent[%p]::Recv__delete__()", this));
500 :
501 0 : if (mPlugin) {
502 0 : mPlugin->DecryptorDestroyed(this);
503 0 : mPlugin = nullptr;
504 : }
505 0 : return IPC_OK();
506 : }
507 :
508 : } // namespace gmp
509 : } // namespace mozilla
|