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