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 "ChromiumCDMChild.h"
7 : #include "GMPContentChild.h"
8 : #include "WidevineUtils.h"
9 : #include "WidevineFileIO.h"
10 : #include "WidevineVideoFrame.h"
11 : #include "GMPLog.h"
12 : #include "GMPPlatform.h"
13 : #include "mozilla/Unused.h"
14 : #include "nsPrintfCString.h"
15 : #include "base/time.h"
16 : #include "GMPUtils.h"
17 : #include "mozilla/ScopeExit.h"
18 : #include "mozilla/SizePrintfMacros.h"
19 :
20 : namespace mozilla {
21 : namespace gmp {
22 :
23 0 : ChromiumCDMChild::ChromiumCDMChild(GMPContentChild* aPlugin)
24 0 : : mPlugin(aPlugin)
25 : {
26 0 : MOZ_ASSERT(IsOnMessageLoopThread());
27 0 : GMP_LOG("ChromiumCDMChild:: ctor this=%p", this);
28 0 : }
29 :
30 : void
31 0 : ChromiumCDMChild::Init(cdm::ContentDecryptionModule_8* aCDM)
32 : {
33 0 : MOZ_ASSERT(IsOnMessageLoopThread());
34 0 : mCDM = aCDM;
35 0 : MOZ_ASSERT(mCDM);
36 0 : }
37 :
38 : void
39 0 : ChromiumCDMChild::TimerExpired(void* aContext)
40 : {
41 0 : MOZ_ASSERT(IsOnMessageLoopThread());
42 0 : GMP_LOG("ChromiumCDMChild::TimerExpired(context=0x%p)", aContext);
43 0 : if (mCDM) {
44 0 : mCDM->TimerExpired(aContext);
45 : }
46 0 : }
47 :
48 : class CDMShmemBuffer : public CDMBuffer
49 : {
50 : public:
51 0 : CDMShmemBuffer(ChromiumCDMChild* aProtocol, ipc::Shmem aShmem)
52 0 : : mProtocol(aProtocol)
53 0 : , mSize(aShmem.Size<uint8_t>())
54 0 : , mShmem(aShmem)
55 : {
56 0 : CDM_LOG("CDMShmemBuffer(size=%" PRIu32 ") created", Size());
57 : // Note: Chrome initializes the size of a buffer to it capacity. We do the same.
58 0 : }
59 :
60 : CDMShmemBuffer(ChromiumCDMChild* aProtocol,
61 : ipc::Shmem aShmem,
62 : WidevineBuffer* aLocalBuffer)
63 : : CDMShmemBuffer(aProtocol, aShmem)
64 : {
65 : MOZ_ASSERT(aLocalBuffer->Size() == Size());
66 : memcpy(Data(),
67 : aLocalBuffer->Data(),
68 : std::min<uint32_t>(aLocalBuffer->Size(), Size()));
69 : }
70 :
71 0 : ~CDMShmemBuffer() override
72 0 : {
73 0 : CDM_LOG("CDMShmemBuffer(size=%" PRIu32 ") destructed writable=%d",
74 : Size(),
75 : mShmem.IsWritable());
76 0 : if (mShmem.IsWritable()) {
77 : // The shmem wasn't extracted to send its data back up to the parent process,
78 : // so we can reuse the shmem.
79 0 : mProtocol->GiveBuffer(Move(mShmem));
80 : }
81 0 : }
82 :
83 0 : void Destroy() override
84 : {
85 0 : CDM_LOG("CDMShmemBuffer::Destroy(size=%" PRIu32 ")", Size());
86 0 : delete this;
87 0 : }
88 0 : uint32_t Capacity() const override { return mShmem.Size<uint8_t>(); }
89 :
90 0 : uint8_t* Data() override { return mShmem.get<uint8_t>(); }
91 :
92 0 : void SetSize(uint32_t aSize) override
93 : {
94 0 : MOZ_ASSERT(aSize <= Capacity());
95 : // Note: We can't use the shmem's size member after ExtractShmem(),
96 : // has been called, so we track the size exlicitly so that we can use
97 : // Size() in logging after we've called ExtractShmem().
98 0 : CDM_LOG("CDMShmemBuffer::SetSize(size=%" PRIu32 ")", Size());
99 0 : mSize = aSize;
100 0 : }
101 :
102 0 : uint32_t Size() const override { return mSize; }
103 :
104 0 : ipc::Shmem ExtractShmem()
105 : {
106 0 : ipc::Shmem shmem = mShmem;
107 0 : mShmem = ipc::Shmem();
108 0 : return shmem;
109 : }
110 :
111 0 : CDMShmemBuffer* AsShmemBuffer() override { return this; }
112 :
113 : private:
114 : RefPtr<ChromiumCDMChild> mProtocol;
115 : uint32_t mSize;
116 : mozilla::ipc::Shmem mShmem;
117 : CDMShmemBuffer(const CDMShmemBuffer&);
118 : void operator=(const CDMShmemBuffer&);
119 : };
120 :
121 : static nsCString
122 0 : ToString(const nsTArray<ipc::Shmem>& aBuffers)
123 : {
124 0 : nsCString s;
125 0 : for (const ipc::Shmem& shmem : aBuffers) {
126 0 : if (!s.IsEmpty()) {
127 0 : s.AppendLiteral(",");
128 : }
129 0 : s.AppendInt(static_cast<uint32_t>(shmem.Size<uint8_t>()));
130 : }
131 0 : return s;
132 : }
133 :
134 : cdm::Buffer*
135 0 : ChromiumCDMChild::Allocate(uint32_t aCapacity)
136 : {
137 0 : GMP_LOG("ChromiumCDMChild::Allocate(capacity=%" PRIu32 ") bufferSizes={%s}",
138 : aCapacity,
139 : ToString(mBuffers).get());
140 0 : MOZ_ASSERT(IsOnMessageLoopThread());
141 :
142 : // Find the shmem with the least amount of wasted space if we were to
143 : // select it for this sized allocation. We need to do this because shmems
144 : // for decrypted audio as well as video frames are both stored in this
145 : // list, and we don't want to use the video frame shmems for audio samples.
146 0 : const size_t invalid = std::numeric_limits<size_t>::max();
147 0 : size_t best = invalid;
148 0 : auto wastedSpace = [this, aCapacity](size_t index) {
149 0 : return mBuffers[index].Size<uint8_t>() - aCapacity;
150 0 : };
151 0 : for (size_t i = 0; i < mBuffers.Length(); i++) {
152 0 : if (mBuffers[i].Size<uint8_t>() >= aCapacity &&
153 0 : (best == invalid || wastedSpace(i) < wastedSpace(best))) {
154 0 : best = i;
155 : }
156 : }
157 0 : if (best == invalid) {
158 : // The parent process should have bestowed upon us a shmem of appropriate
159 : // size, but did not! Do a "dive and catch", and create an non-shared
160 : // memory buffer. The parent will detect this and send us an extra shmem
161 : // so future frames can be in shmems, i.e. returned on the fast path.
162 0 : return new WidevineBuffer(aCapacity);
163 : }
164 0 : ipc::Shmem shmem = mBuffers[best];
165 0 : mBuffers.RemoveElementAt(best);
166 0 : return new CDMShmemBuffer(this, shmem);
167 : }
168 :
169 : void
170 0 : ChromiumCDMChild::SetTimer(int64_t aDelayMs, void* aContext)
171 : {
172 0 : MOZ_ASSERT(IsOnMessageLoopThread());
173 0 : GMP_LOG("ChromiumCDMChild::SetTimer(delay=%" PRId64 ", context=0x%p)",
174 : aDelayMs,
175 : aContext);
176 0 : RefPtr<ChromiumCDMChild> self(this);
177 0 : SetTimerOnMainThread(NewGMPTask([self, aContext]() {
178 0 : self->TimerExpired(aContext);
179 0 : }), aDelayMs);
180 0 : }
181 :
182 : cdm::Time
183 0 : ChromiumCDMChild::GetCurrentWallTime()
184 : {
185 0 : MOZ_ASSERT(IsOnMessageLoopThread());
186 0 : return base::Time::Now().ToDoubleT();
187 : }
188 :
189 : void
190 0 : ChromiumCDMChild::OnResolveNewSessionPromise(uint32_t aPromiseId,
191 : const char* aSessionId,
192 : uint32_t aSessionIdSize)
193 : {
194 0 : MOZ_ASSERT(IsOnMessageLoopThread());
195 0 : GMP_LOG("ChromiumCDMChild::OnResolveNewSessionPromise(pid=%" PRIu32
196 : ", sid=%s)",
197 : aPromiseId,
198 : aSessionId);
199 :
200 0 : if (mLoadSessionPromiseIds.Contains(aPromiseId)) {
201 : // As laid out in the Chromium CDM API, if the CDM fails to load
202 : // a session it calls OnResolveNewSessionPromise with nullptr as the sessionId.
203 : // We can safely assume this means that we have failed to load a session
204 : // as the other methods specify calling 'OnRejectPromise' when they fail.
205 0 : bool loadSuccessful = aSessionId != nullptr;
206 0 : GMP_LOG("ChromiumCDMChild::OnResolveNewSessionPromise(pid=%u, sid=%s) "
207 : "resolving %s load session ",
208 : aPromiseId,
209 : aSessionId,
210 : (loadSuccessful ? "successful" : "failed"));
211 0 : Unused << SendResolveLoadSessionPromise(aPromiseId, loadSuccessful);
212 0 : mLoadSessionPromiseIds.RemoveElement(aPromiseId);
213 0 : return;
214 : }
215 :
216 0 : Unused << SendOnResolveNewSessionPromise(aPromiseId,
217 0 : nsCString(aSessionId, aSessionIdSize));
218 : }
219 :
220 0 : void ChromiumCDMChild::OnResolvePromise(uint32_t aPromiseId)
221 : {
222 0 : MOZ_ASSERT(IsOnMessageLoopThread());
223 0 : GMP_LOG("ChromiumCDMChild::OnResolvePromise(pid=%" PRIu32 ")", aPromiseId);
224 0 : Unused << SendOnResolvePromise(aPromiseId);
225 0 : }
226 :
227 : void
228 0 : ChromiumCDMChild::OnRejectPromise(uint32_t aPromiseId,
229 : cdm::Error aError,
230 : uint32_t aSystemCode,
231 : const char* aErrorMessage,
232 : uint32_t aErrorMessageSize)
233 : {
234 0 : MOZ_ASSERT(IsOnMessageLoopThread());
235 0 : GMP_LOG("ChromiumCDMChild::OnRejectPromise(pid=%" PRIu32 ", err=%" PRIu32
236 : " code=%" PRIu32 ", msg='%s')",
237 : aPromiseId,
238 : aError,
239 : aSystemCode,
240 : aErrorMessage);
241 0 : Unused << SendOnRejectPromise(aPromiseId,
242 0 : static_cast<uint32_t>(aError),
243 : aSystemCode,
244 0 : nsCString(aErrorMessage, aErrorMessageSize));
245 0 : }
246 :
247 : void
248 0 : ChromiumCDMChild::OnSessionMessage(const char* aSessionId,
249 : uint32_t aSessionIdSize,
250 : cdm::MessageType aMessageType,
251 : const char* aMessage,
252 : uint32_t aMessageSize,
253 : const char* aLegacyDestinationUrl,
254 : uint32_t aLegacyDestinationUrlLength)
255 : {
256 0 : MOZ_ASSERT(IsOnMessageLoopThread());
257 0 : GMP_LOG("ChromiumCDMChild::OnSessionMessage(sid=%s, type=%" PRIu32
258 : " size=%" PRIu32 ")",
259 : aSessionId,
260 : aMessageType,
261 : aMessageSize);
262 0 : nsTArray<uint8_t> message;
263 0 : message.AppendElements(aMessage, aMessageSize);
264 0 : Unused << SendOnSessionMessage(nsCString(aSessionId, aSessionIdSize),
265 0 : static_cast<uint32_t>(aMessageType),
266 : message);
267 0 : }
268 :
269 : static nsCString
270 0 : ToString(const cdm::KeyInformation* aKeysInfo, uint32_t aKeysInfoCount)
271 : {
272 0 : nsCString str;
273 0 : for (uint32_t i = 0; i < aKeysInfoCount; i++) {
274 0 : if (!str.IsEmpty()) {
275 0 : str.AppendLiteral(",");
276 : }
277 0 : const cdm::KeyInformation& key = aKeysInfo[i];
278 0 : str.Append(ToHexString(key.key_id, key.key_id_size));
279 0 : str.AppendLiteral("=");
280 0 : str.AppendInt(key.status);
281 : }
282 0 : return str;
283 : }
284 :
285 : void
286 0 : ChromiumCDMChild::OnSessionKeysChange(const char *aSessionId,
287 : uint32_t aSessionIdSize,
288 : bool aHasAdditionalUsableKey,
289 : const cdm::KeyInformation* aKeysInfo,
290 : uint32_t aKeysInfoCount)
291 : {
292 0 : MOZ_ASSERT(IsOnMessageLoopThread());
293 0 : GMP_LOG("ChromiumCDMChild::OnSessionKeysChange(sid=%s) keys={%s}",
294 : aSessionId,
295 : ToString(aKeysInfo, aKeysInfoCount).get());
296 :
297 0 : nsTArray<CDMKeyInformation> keys;
298 0 : keys.SetCapacity(aKeysInfoCount);
299 0 : for (uint32_t i = 0; i < aKeysInfoCount; i++) {
300 0 : const cdm::KeyInformation& key = aKeysInfo[i];
301 0 : nsTArray<uint8_t> kid;
302 0 : kid.AppendElements(key.key_id, key.key_id_size);
303 0 : keys.AppendElement(CDMKeyInformation(kid, key.status, key.system_code));
304 : }
305 0 : Unused << SendOnSessionKeysChange(nsCString(aSessionId, aSessionIdSize),
306 : keys);
307 0 : }
308 :
309 : void
310 0 : ChromiumCDMChild::OnExpirationChange(const char* aSessionId,
311 : uint32_t aSessionIdSize,
312 : cdm::Time aNewExpiryTime)
313 : {
314 0 : MOZ_ASSERT(IsOnMessageLoopThread());
315 0 : GMP_LOG("ChromiumCDMChild::OnExpirationChange(sid=%s, time=%lf)",
316 : aSessionId,
317 : aNewExpiryTime);
318 0 : Unused << SendOnExpirationChange(nsCString(aSessionId, aSessionIdSize),
319 : aNewExpiryTime);
320 0 : }
321 :
322 : void
323 0 : ChromiumCDMChild::OnSessionClosed(const char* aSessionId,
324 : uint32_t aSessionIdSize)
325 : {
326 0 : MOZ_ASSERT(IsOnMessageLoopThread());
327 0 : GMP_LOG("ChromiumCDMChild::OnSessionClosed(sid=%s)", aSessionId);
328 0 : Unused << SendOnSessionClosed(nsCString(aSessionId, aSessionIdSize));
329 0 : }
330 :
331 : void
332 0 : ChromiumCDMChild::OnLegacySessionError(const char* aSessionId,
333 : uint32_t aSessionIdLength,
334 : cdm::Error aError,
335 : uint32_t aSystemCode,
336 : const char* aErrorMessage,
337 : uint32_t aErrorMessageLength)
338 : {
339 0 : MOZ_ASSERT(IsOnMessageLoopThread());
340 0 : GMP_LOG("ChromiumCDMChild::OnLegacySessionError(sid=%s, error=%" PRIu32
341 : " msg='%s')",
342 : aSessionId,
343 : aError,
344 : aErrorMessage);
345 0 : Unused << SendOnLegacySessionError(
346 0 : nsCString(aSessionId, aSessionIdLength),
347 0 : static_cast<uint32_t>(aError),
348 : aSystemCode,
349 0 : nsCString(aErrorMessage, aErrorMessageLength));
350 0 : }
351 :
352 : cdm::FileIO*
353 0 : ChromiumCDMChild::CreateFileIO(cdm::FileIOClient * aClient)
354 : {
355 0 : MOZ_ASSERT(IsOnMessageLoopThread());
356 0 : GMP_LOG("ChromiumCDMChild::CreateFileIO()");
357 0 : if (!mPersistentStateAllowed) {
358 0 : return nullptr;
359 : }
360 0 : return new WidevineFileIO(aClient);
361 : }
362 :
363 0 : ChromiumCDMChild::~ChromiumCDMChild()
364 : {
365 0 : GMP_LOG("ChromiumCDMChild:: dtor this=%p", this);
366 0 : }
367 :
368 : bool
369 0 : ChromiumCDMChild::IsOnMessageLoopThread()
370 : {
371 0 : return mPlugin && mPlugin->GMPMessageLoop() == MessageLoop::current();
372 : }
373 :
374 : void
375 0 : ChromiumCDMChild::PurgeShmems()
376 : {
377 0 : for (ipc::Shmem& shmem : mBuffers) {
378 0 : DeallocShmem(shmem);
379 : }
380 0 : mBuffers.Clear();
381 0 : }
382 :
383 : ipc::IPCResult
384 0 : ChromiumCDMChild::RecvPurgeShmems()
385 : {
386 0 : PurgeShmems();
387 0 : return IPC_OK();
388 : }
389 :
390 : mozilla::ipc::IPCResult
391 0 : ChromiumCDMChild::RecvInit(const bool& aAllowDistinctiveIdentifier,
392 : const bool& aAllowPersistentState)
393 : {
394 0 : MOZ_ASSERT(IsOnMessageLoopThread());
395 0 : GMP_LOG("ChromiumCDMChild::RecvInit(distinctiveId=%d, persistentState=%d)",
396 : aAllowDistinctiveIdentifier,
397 : aAllowPersistentState);
398 0 : mPersistentStateAllowed = aAllowPersistentState;
399 0 : if (mCDM) {
400 0 : mCDM->Initialize(aAllowDistinctiveIdentifier, aAllowPersistentState);
401 : }
402 0 : return IPC_OK();
403 : }
404 :
405 : mozilla::ipc::IPCResult
406 0 : ChromiumCDMChild::RecvSetServerCertificate(const uint32_t& aPromiseId,
407 : nsTArray<uint8_t>&& aServerCert)
408 :
409 : {
410 0 : MOZ_ASSERT(IsOnMessageLoopThread());
411 0 : GMP_LOG("ChromiumCDMChild::RecvSetServerCertificate() certlen=%zu",
412 : aServerCert.Length());
413 0 : if (mCDM) {
414 0 : mCDM->SetServerCertificate(aPromiseId,
415 0 : aServerCert.Elements(),
416 0 : aServerCert.Length());
417 : }
418 0 : return IPC_OK();
419 : }
420 :
421 : mozilla::ipc::IPCResult
422 0 : ChromiumCDMChild::RecvCreateSessionAndGenerateRequest(
423 : const uint32_t& aPromiseId,
424 : const uint32_t& aSessionType,
425 : const uint32_t& aInitDataType,
426 : nsTArray<uint8_t>&& aInitData)
427 : {
428 0 : MOZ_ASSERT(IsOnMessageLoopThread());
429 0 : GMP_LOG("ChromiumCDMChild::RecvCreateSessionAndGenerateRequest("
430 : "pid=%" PRIu32 ", sessionType=%" PRIu32 ", initDataType=%" PRIu32
431 : ") initDataLen=%zu",
432 : aPromiseId,
433 : aSessionType,
434 : aInitDataType,
435 : aInitData.Length());
436 0 : MOZ_ASSERT(aSessionType <= cdm::SessionType::kPersistentKeyRelease);
437 0 : MOZ_ASSERT(aInitDataType <= cdm::InitDataType::kWebM);
438 0 : if (mCDM) {
439 0 : mCDM->CreateSessionAndGenerateRequest(aPromiseId,
440 : static_cast<cdm::SessionType>(aSessionType),
441 : static_cast<cdm::InitDataType>(aInitDataType),
442 0 : aInitData.Elements(),
443 0 : aInitData.Length());
444 : }
445 0 : return IPC_OK();
446 : }
447 :
448 : mozilla::ipc::IPCResult
449 0 : ChromiumCDMChild::RecvLoadSession(const uint32_t& aPromiseId,
450 : const uint32_t& aSessionType,
451 : const nsCString& aSessionId)
452 : {
453 0 : GMP_LOG("ChromiumCDMChild::RecvLoadSession(pid=%u, type=%u, sessionId=%s)",
454 : aPromiseId,
455 : aSessionType,
456 : aSessionId.get());
457 0 : if (mCDM) {
458 0 : mLoadSessionPromiseIds.AppendElement(aPromiseId);
459 0 : mCDM->LoadSession(aPromiseId,
460 : static_cast<cdm::SessionType>(aSessionType),
461 : aSessionId.get(),
462 0 : aSessionId.Length());
463 : }
464 0 : return IPC_OK();
465 : }
466 :
467 : mozilla::ipc::IPCResult
468 0 : ChromiumCDMChild::RecvUpdateSession(const uint32_t& aPromiseId,
469 : const nsCString& aSessionId,
470 : nsTArray<uint8_t>&& aResponse)
471 : {
472 0 : MOZ_ASSERT(IsOnMessageLoopThread());
473 0 : GMP_LOG("ChromiumCDMChild::RecvUpdateSession(pid=%" PRIu32
474 : ", sid=%s) responseLen=%zu",
475 : aPromiseId,
476 : aSessionId.get(),
477 : aResponse.Length());
478 0 : if (mCDM) {
479 0 : mCDM->UpdateSession(aPromiseId,
480 : aSessionId.get(),
481 : aSessionId.Length(),
482 0 : aResponse.Elements(),
483 0 : aResponse.Length());
484 : }
485 0 : return IPC_OK();
486 : }
487 :
488 : mozilla::ipc::IPCResult
489 0 : ChromiumCDMChild::RecvCloseSession(const uint32_t& aPromiseId,
490 : const nsCString& aSessionId)
491 : {
492 0 : MOZ_ASSERT(IsOnMessageLoopThread());
493 0 : GMP_LOG("ChromiumCDMChild::RecvCloseSession(pid=%" PRIu32 ", sid=%s)",
494 : aPromiseId,
495 : aSessionId.get());
496 0 : if (mCDM) {
497 0 : mCDM->CloseSession(aPromiseId, aSessionId.get(), aSessionId.Length());
498 : }
499 0 : return IPC_OK();
500 : }
501 :
502 : mozilla::ipc::IPCResult
503 0 : ChromiumCDMChild::RecvRemoveSession(const uint32_t& aPromiseId,
504 : const nsCString& aSessionId)
505 : {
506 0 : MOZ_ASSERT(IsOnMessageLoopThread());
507 0 : GMP_LOG("ChromiumCDMChild::RecvRemoveSession(pid=%" PRIu32 ", sid=%s)",
508 : aPromiseId,
509 : aSessionId.get());
510 0 : if (mCDM) {
511 0 : mCDM->RemoveSession(aPromiseId, aSessionId.get(), aSessionId.Length());
512 : }
513 0 : return IPC_OK();
514 : }
515 :
516 : static void
517 0 : InitInputBuffer(const CDMInputBuffer& aBuffer,
518 : nsTArray<cdm::SubsampleEntry>& aSubSamples,
519 : cdm::InputBuffer& aInputBuffer)
520 : {
521 0 : aInputBuffer.data = aBuffer.mData().get<uint8_t>();
522 0 : aInputBuffer.data_size = aBuffer.mData().Size<uint8_t>();
523 :
524 0 : if (aBuffer.mIsEncrypted()) {
525 0 : aInputBuffer.key_id = aBuffer.mKeyId().Elements();
526 0 : aInputBuffer.key_id_size = aBuffer.mKeyId().Length();
527 :
528 0 : aInputBuffer.iv = aBuffer.mIV().Elements();
529 0 : aInputBuffer.iv_size = aBuffer.mIV().Length();
530 :
531 0 : aSubSamples.SetCapacity(aBuffer.mClearBytes().Length());
532 0 : for (size_t i = 0; i < aBuffer.mCipherBytes().Length(); i++) {
533 0 : aSubSamples.AppendElement(cdm::SubsampleEntry(aBuffer.mClearBytes()[i],
534 0 : aBuffer.mCipherBytes()[i]));
535 : }
536 0 : aInputBuffer.subsamples = aSubSamples.Elements();
537 0 : aInputBuffer.num_subsamples = aSubSamples.Length();
538 : }
539 0 : aInputBuffer.timestamp = aBuffer.mTimestamp();
540 0 : }
541 :
542 : bool
543 0 : ChromiumCDMChild::HasShmemOfSize(size_t aSize) const
544 : {
545 0 : for (const ipc::Shmem& shmem : mBuffers) {
546 0 : if (shmem.Size<uint8_t>() == aSize) {
547 0 : return true;
548 : }
549 : }
550 0 : return false;
551 : }
552 :
553 : mozilla::ipc::IPCResult
554 0 : ChromiumCDMChild::RecvDecrypt(const uint32_t& aId,
555 : const CDMInputBuffer& aBuffer)
556 : {
557 0 : MOZ_ASSERT(IsOnMessageLoopThread());
558 0 : GMP_LOG("ChromiumCDMChild::RecvDecrypt()");
559 :
560 : // Parent should have already gifted us a shmem to use as output.
561 0 : size_t outputShmemSize = aBuffer.mData().Size<uint8_t>();
562 0 : MOZ_ASSERT(HasShmemOfSize(outputShmemSize));
563 :
564 : // Ensure we deallocate the shmem used to send input.
565 0 : RefPtr<ChromiumCDMChild> self = this;
566 : auto autoDeallocateInputShmem =
567 0 : MakeScopeExit([&, self] { self->DeallocShmem(aBuffer.mData()); });
568 :
569 : // On failure, we need to ensure that the shmem that the parent sent
570 : // for the CDM to use to return output back to the parent is deallocated.
571 : // Otherwise, it will leak.
572 0 : auto autoDeallocateOutputShmem = MakeScopeExit([self, outputShmemSize] {
573 0 : self->mBuffers.RemoveElementsBy([outputShmemSize, self](ipc::Shmem& aShmem) {
574 0 : if (aShmem.Size<uint8_t>() != outputShmemSize) {
575 0 : return false;
576 : }
577 0 : self->DeallocShmem(aShmem);
578 0 : return true;
579 0 : });
580 0 : });
581 :
582 0 : if (!mCDM) {
583 0 : GMP_LOG("ChromiumCDMChild::RecvDecrypt() no CDM");
584 0 : Unused << SendDecryptFailed(aId, cdm::kDecryptError);
585 0 : return IPC_OK();
586 : }
587 0 : if (aBuffer.mClearBytes().Length() != aBuffer.mCipherBytes().Length()) {
588 0 : GMP_LOG("ChromiumCDMChild::RecvDecrypt() clear/cipher bytes length doesn't "
589 : "match");
590 0 : Unused << SendDecryptFailed(aId, cdm::kDecryptError);
591 0 : return IPC_OK();
592 : }
593 :
594 0 : cdm::InputBuffer input;
595 0 : nsTArray<cdm::SubsampleEntry> subsamples;
596 0 : InitInputBuffer(aBuffer, subsamples, input);
597 :
598 0 : WidevineDecryptedBlock output;
599 0 : cdm::Status status = mCDM->Decrypt(input, &output);
600 :
601 : // CDM should have allocated a cdm::Buffer for output.
602 : CDMShmemBuffer* buffer =
603 0 : output.DecryptedBuffer()
604 0 : ? static_cast<CDMShmemBuffer*>(output.DecryptedBuffer())
605 0 : : nullptr;
606 0 : MOZ_ASSERT_IF(buffer, buffer->AsShmemBuffer());
607 0 : if (status != cdm::kSuccess || !buffer) {
608 0 : Unused << SendDecryptFailed(aId, status);
609 0 : return IPC_OK();
610 : }
611 :
612 : // Success! Return the decrypted sample to parent.
613 0 : MOZ_ASSERT(!HasShmemOfSize(outputShmemSize));
614 0 : ipc::Shmem shmem = buffer->ExtractShmem();
615 0 : if (SendDecrypted(aId, cdm::kSuccess, shmem)) {
616 : // No need to deallocate the output shmem; it should have been returned
617 : // to the content process.
618 0 : autoDeallocateOutputShmem.release();
619 : }
620 :
621 0 : return IPC_OK();
622 : }
623 :
624 : mozilla::ipc::IPCResult
625 0 : ChromiumCDMChild::RecvInitializeVideoDecoder(
626 : const CDMVideoDecoderConfig& aConfig)
627 : {
628 0 : MOZ_ASSERT(IsOnMessageLoopThread());
629 0 : MOZ_ASSERT(!mDecoderInitialized);
630 0 : cdm::VideoDecoderConfig config;
631 0 : config.codec =
632 0 : static_cast<cdm::VideoDecoderConfig::VideoCodec>(aConfig.mCodec());
633 0 : config.profile =
634 0 : static_cast<cdm::VideoDecoderConfig::VideoCodecProfile>(aConfig.mProfile());
635 0 : config.format = static_cast<cdm::VideoFormat>(aConfig.mFormat());
636 0 : config.coded_size =
637 0 : mCodedSize = { aConfig.mImageWidth(), aConfig.mImageHeight() };
638 0 : nsTArray<uint8_t> extraData(aConfig.mExtraData());
639 0 : config.extra_data = extraData.Elements();
640 0 : config.extra_data_size = extraData.Length();
641 0 : cdm::Status status = mCDM->InitializeVideoDecoder(config);
642 0 : GMP_LOG("ChromiumCDMChild::RecvInitializeVideoDecoder() status=%u", status);
643 0 : Unused << SendOnDecoderInitDone(status);
644 0 : mDecoderInitialized = status == cdm::kSuccess;
645 0 : return IPC_OK();
646 : }
647 :
648 : mozilla::ipc::IPCResult
649 0 : ChromiumCDMChild::RecvDeinitializeVideoDecoder()
650 : {
651 0 : MOZ_ASSERT(IsOnMessageLoopThread());
652 0 : GMP_LOG("ChromiumCDMChild::RecvDeinitializeVideoDecoder()");
653 0 : MOZ_ASSERT(mDecoderInitialized);
654 0 : if (mDecoderInitialized) {
655 0 : mDecoderInitialized = false;
656 0 : mCDM->DeinitializeDecoder(cdm::kStreamTypeVideo);
657 : }
658 0 : PurgeShmems();
659 0 : return IPC_OK();
660 : }
661 :
662 : mozilla::ipc::IPCResult
663 0 : ChromiumCDMChild::RecvResetVideoDecoder()
664 : {
665 0 : MOZ_ASSERT(IsOnMessageLoopThread());
666 0 : GMP_LOG("ChromiumCDMChild::RecvResetVideoDecoder()");
667 0 : if (mDecoderInitialized) {
668 0 : mCDM->ResetDecoder(cdm::kStreamTypeVideo);
669 : }
670 0 : Unused << SendResetVideoDecoderComplete();
671 0 : return IPC_OK();
672 : }
673 :
674 : mozilla::ipc::IPCResult
675 0 : ChromiumCDMChild::RecvDecryptAndDecodeFrame(const CDMInputBuffer& aBuffer)
676 : {
677 0 : MOZ_ASSERT(IsOnMessageLoopThread());
678 0 : GMP_LOG("ChromiumCDMChild::RecvDecryptAndDecodeFrame() t=%" PRId64 ")",
679 : aBuffer.mTimestamp());
680 0 : MOZ_ASSERT(mDecoderInitialized);
681 :
682 0 : RefPtr<ChromiumCDMChild> self = this;
683 0 : auto autoDeallocateShmem = MakeScopeExit([&, self] {
684 0 : self->DeallocShmem(aBuffer.mData());
685 0 : });
686 :
687 : // The output frame may not have the same timestamp as the frame we put in.
688 : // We may need to input a number of frames before we receive output. The
689 : // CDM's decoder reorders to ensure frames output are in presentation order.
690 : // So we need to store the durations of the frames input, and retrieve them
691 : // on output.
692 0 : mFrameDurations.Insert(aBuffer.mTimestamp(), aBuffer.mDuration());
693 :
694 0 : cdm::InputBuffer input;
695 0 : nsTArray<cdm::SubsampleEntry> subsamples;
696 0 : InitInputBuffer(aBuffer, subsamples, input);
697 :
698 0 : WidevineVideoFrame frame;
699 0 : cdm::Status rv = mCDM->DecryptAndDecodeFrame(input, &frame);
700 0 : GMP_LOG("ChromiumCDMChild::RecvDecryptAndDecodeFrame() t=%" PRId64
701 : " CDM decoder rv=%d",
702 : aBuffer.mTimestamp(),
703 : rv);
704 :
705 0 : switch (rv) {
706 : case cdm::kNeedMoreData:
707 0 : Unused << SendDecodeFailed(rv);
708 0 : break;
709 : case cdm::kNoKey:
710 0 : GMP_LOG("NoKey for sample at time=%" PRId64 "!", input.timestamp);
711 : // Somehow our key became unusable. Typically this would happen when
712 : // a stream requires output protection, and the configuration changed
713 : // such that output protection is no longer available. For example, a
714 : // non-compliant monitor was attached. The JS player should notice the
715 : // key status changing to "output-restricted", and is supposed to switch
716 : // to a stream that doesn't require OP. In order to keep the playback
717 : // pipeline rolling, just output a black frame. See bug 1343140.
718 0 : if (!frame.InitToBlack(mCodedSize.width, mCodedSize.height,
719 : input.timestamp)) {
720 0 : Unused << SendDecodeFailed(cdm::kDecodeError);
721 0 : break;
722 : }
723 : MOZ_FALLTHROUGH;
724 : case cdm::kSuccess:
725 0 : if (frame.FrameBuffer()) {
726 0 : ReturnOutput(frame);
727 0 : break;
728 : }
729 : // CDM didn't set a frame buffer on the sample, report it as an error.
730 : MOZ_FALLTHROUGH;
731 : default:
732 0 : Unused << SendDecodeFailed(rv);
733 0 : break;
734 : }
735 :
736 0 : return IPC_OK();
737 : }
738 :
739 : void
740 0 : ChromiumCDMChild::ReturnOutput(WidevineVideoFrame& aFrame)
741 : {
742 0 : MOZ_ASSERT(IsOnMessageLoopThread());
743 0 : MOZ_ASSERT(aFrame.FrameBuffer());
744 0 : gmp::CDMVideoFrame output;
745 0 : output.mFormat() = static_cast<cdm::VideoFormat>(aFrame.Format());
746 0 : output.mImageWidth() = aFrame.Size().width;
747 0 : output.mImageHeight() = aFrame.Size().height;
748 0 : output.mYPlane() = { aFrame.PlaneOffset(cdm::VideoFrame::kYPlane),
749 0 : aFrame.Stride(cdm::VideoFrame::kYPlane) };
750 0 : output.mUPlane() = { aFrame.PlaneOffset(cdm::VideoFrame::kUPlane),
751 0 : aFrame.Stride(cdm::VideoFrame::kUPlane) };
752 0 : output.mVPlane() = { aFrame.PlaneOffset(cdm::VideoFrame::kVPlane),
753 0 : aFrame.Stride(cdm::VideoFrame::kVPlane) };
754 0 : output.mTimestamp() = aFrame.Timestamp();
755 :
756 0 : uint64_t duration = 0;
757 0 : if (mFrameDurations.Find(aFrame.Timestamp(), duration)) {
758 0 : output.mDuration() = duration;
759 : }
760 :
761 0 : CDMBuffer* base = reinterpret_cast<CDMBuffer*>(aFrame.FrameBuffer());
762 0 : if (base->AsShmemBuffer()) {
763 0 : ipc::Shmem shmem = base->AsShmemBuffer()->ExtractShmem();
764 0 : Unused << SendDecodedShmem(output, shmem);
765 : } else {
766 0 : MOZ_ASSERT(base->AsArrayBuffer());
767 0 : Unused << SendDecodedData(output, base->AsArrayBuffer()->ExtractBuffer());
768 : }
769 0 : }
770 :
771 : mozilla::ipc::IPCResult
772 0 : ChromiumCDMChild::RecvDrain()
773 : {
774 0 : MOZ_ASSERT(IsOnMessageLoopThread());
775 0 : WidevineVideoFrame frame;
776 0 : cdm::InputBuffer sample;
777 0 : cdm::Status rv = mCDM->DecryptAndDecodeFrame(sample, &frame);
778 0 : CDM_LOG("ChromiumCDMChild::RecvDrain(); DecryptAndDecodeFrame() rv=%d", rv);
779 0 : if (rv == cdm::kSuccess) {
780 0 : MOZ_ASSERT(frame.Format() != cdm::kUnknownVideoFormat);
781 0 : ReturnOutput(frame);
782 : } else {
783 0 : Unused << SendDrainComplete();
784 : }
785 0 : return IPC_OK();
786 : }
787 :
788 : mozilla::ipc::IPCResult
789 0 : ChromiumCDMChild::RecvDestroy()
790 : {
791 0 : MOZ_ASSERT(IsOnMessageLoopThread());
792 0 : GMP_LOG("ChromiumCDMChild::RecvDestroy()");
793 :
794 0 : MOZ_ASSERT(!mDecoderInitialized);
795 :
796 0 : if (mCDM) {
797 0 : mCDM->Destroy();
798 0 : mCDM = nullptr;
799 : }
800 :
801 0 : Unused << Send__delete__(this);
802 :
803 0 : return IPC_OK();
804 : }
805 :
806 : mozilla::ipc::IPCResult
807 0 : ChromiumCDMChild::RecvGiveBuffer(ipc::Shmem&& aBuffer)
808 : {
809 0 : MOZ_ASSERT(IsOnMessageLoopThread());
810 :
811 0 : GiveBuffer(Move(aBuffer));
812 0 : return IPC_OK();
813 : }
814 :
815 : void
816 0 : ChromiumCDMChild::GiveBuffer(ipc::Shmem&& aBuffer)
817 : {
818 0 : MOZ_ASSERT(IsOnMessageLoopThread());
819 0 : size_t sz = aBuffer.Size<uint8_t>();
820 0 : mBuffers.AppendElement(Move(aBuffer));
821 0 : GMP_LOG("ChromiumCDMChild::RecvGiveBuffer(capacity=%" PRIuSIZE
822 : ") bufferSizes={%s} mDecoderInitialized=%d",
823 : sz,
824 : ToString(mBuffers).get(),
825 : mDecoderInitialized);
826 0 : }
827 :
828 : } // namespace gmp
829 : } // namespace mozilla
|