/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "WidevineDecryptor.h" #include "WidevineAdapter.h" #include "WidevineUtils.h" #include "WidevineFileIO.h" #include #include #include "base/time.h" using namespace cdm; using namespace std; namespace mozilla { static map> sDecryptors; /* static */ RefPtr WidevineDecryptor::GetInstance(uint32_t aInstanceId) { auto itr = sDecryptors.find(aInstanceId); if (itr != sDecryptors.end()) { return itr->second; } return nullptr; } WidevineDecryptor::WidevineDecryptor() : mCallback(nullptr) { Log("WidevineDecryptor created this=%p", this); AddRef(); // Released in DecryptingComplete(). } WidevineDecryptor::~WidevineDecryptor() { Log("WidevineDecryptor destroyed this=%p", this); } void WidevineDecryptor::SetCDM(RefPtr aCDM, uint32_t aInstanceId) { mCDM = aCDM; mInstanceId = aInstanceId; sDecryptors[mInstanceId] = aCDM; } void WidevineDecryptor::Init(GMPDecryptorCallback* aCallback, bool aDistinctiveIdentifierRequired, bool aPersistentStateRequired) { Log("WidevineDecryptor::Init() this=%p distinctiveId=%d persistentState=%d", this, aDistinctiveIdentifierRequired, aPersistentStateRequired); MOZ_ASSERT(aCallback); mCallback = aCallback; MOZ_ASSERT(mCDM); mDistinctiveIdentifierRequired = aDistinctiveIdentifierRequired; mPersistentStateRequired = aPersistentStateRequired; if (CDM()) { CDM()->Initialize(aDistinctiveIdentifierRequired, aPersistentStateRequired); } } static SessionType ToCDMSessionType(GMPSessionType aSessionType) { switch (aSessionType) { case kGMPTemporySession: return kTemporary; case kGMPPersistentSession: return kPersistentLicense; case kGMPSessionInvalid: return kTemporary; // TODO: kPersistentKeyRelease } MOZ_ASSERT(false); // Not supposed to get here. return kTemporary; } void WidevineDecryptor::CreateSession(uint32_t aCreateSessionToken, uint32_t aPromiseId, const char* aInitDataType, uint32_t aInitDataTypeSize, const uint8_t* aInitData, uint32_t aInitDataSize, GMPSessionType aSessionType) { Log("Decryptor::CreateSession(token=%d, pid=%d)", aCreateSessionToken, aPromiseId); InitDataType initDataType; if (!strcmp(aInitDataType, "cenc")) { initDataType = kCenc; } else if (!strcmp(aInitDataType, "webm")) { initDataType = kWebM; } else if (!strcmp(aInitDataType, "keyids")) { initDataType = kKeyIds; } else { // Invalid init data type const char* errorMsg = "Invalid init data type when creating session."; OnRejectPromise(aPromiseId, kNotSupportedError, 0, errorMsg, sizeof(errorMsg)); return; } mPromiseIdToNewSessionTokens[aPromiseId] = aCreateSessionToken; CDM()->CreateSessionAndGenerateRequest(aPromiseId, ToCDMSessionType(aSessionType), initDataType, aInitData, aInitDataSize); } void WidevineDecryptor::LoadSession(uint32_t aPromiseId, const char* aSessionId, uint32_t aSessionIdLength) { Log("Decryptor::LoadSession(pid=%d, %s)", aPromiseId, aSessionId); // TODO: session type?? CDM()->LoadSession(aPromiseId, kPersistentLicense, aSessionId, aSessionIdLength); } void WidevineDecryptor::UpdateSession(uint32_t aPromiseId, const char* aSessionId, uint32_t aSessionIdLength, const uint8_t* aResponse, uint32_t aResponseSize) { Log("Decryptor::UpdateSession(pid=%d, session=%s)", aPromiseId, aSessionId); CDM()->UpdateSession(aPromiseId, aSessionId, aSessionIdLength, aResponse, aResponseSize); } void WidevineDecryptor::CloseSession(uint32_t aPromiseId, const char* aSessionId, uint32_t aSessionIdLength) { Log("Decryptor::CloseSession(pid=%d, session=%s)", aPromiseId, aSessionId); CDM()->CloseSession(aPromiseId, aSessionId, aSessionIdLength); } void WidevineDecryptor::RemoveSession(uint32_t aPromiseId, const char* aSessionId, uint32_t aSessionIdLength) { Log("Decryptor::RemoveSession(%s)", aSessionId); CDM()->RemoveSession(aPromiseId, aSessionId, aSessionIdLength); } void WidevineDecryptor::SetServerCertificate(uint32_t aPromiseId, const uint8_t* aServerCert, uint32_t aServerCertSize) { Log("Decryptor::SetServerCertificate()"); CDM()->SetServerCertificate(aPromiseId, aServerCert, aServerCertSize); } class WidevineDecryptedBlock : public cdm::DecryptedBlock { public: WidevineDecryptedBlock() : mBuffer(nullptr) , mTimestamp(0) { } ~WidevineDecryptedBlock() { if (mBuffer) { mBuffer->Destroy(); mBuffer = nullptr; } } void SetDecryptedBuffer(cdm::Buffer* aBuffer) override { mBuffer = aBuffer; } cdm::Buffer* DecryptedBuffer() override { return mBuffer; } void SetTimestamp(int64_t aTimestamp) override { mTimestamp = aTimestamp; } int64_t Timestamp() const override { return mTimestamp; } private: cdm::Buffer* mBuffer; int64_t mTimestamp; }; void WidevineDecryptor::Decrypt(GMPBuffer* aBuffer, GMPEncryptedBufferMetadata* aMetadata) { if (!mCallback) { Log("WidevineDecryptor::Decrypt() this=%p FAIL; !mCallback", this); return; } const GMPEncryptedBufferMetadata* crypto = aMetadata; InputBuffer sample; nsTArray subsamples; InitInputBuffer(crypto, aBuffer->Id(), aBuffer->Data(), aBuffer->Size(), sample, subsamples); WidevineDecryptedBlock decrypted; Status rv = CDM()->Decrypt(sample, &decrypted); Log("Decryptor::Decrypt(timestamp=%lld) rv=%d sz=%d", sample.timestamp, rv, decrypted.DecryptedBuffer()->Size()); if (rv == kSuccess) { aBuffer->Resize(decrypted.DecryptedBuffer()->Size()); memcpy(aBuffer->Data(), decrypted.DecryptedBuffer()->Data(), decrypted.DecryptedBuffer()->Size()); } mCallback->Decrypted(aBuffer, ToGMPErr(rv)); } void WidevineDecryptor::DecryptingComplete() { Log("WidevineDecryptor::DecryptingComplete() this=%p", this); // Drop our references to the CDMWrapper. When any other references // held elsewhere are dropped (for example references held by a // WidevineVideoDecoder, or a runnable), the CDMWrapper destroys // the CDM. mCDM = nullptr; sDecryptors.erase(mInstanceId); mCallback = nullptr; Release(); } class WidevineBuffer : public cdm::Buffer { public: explicit WidevineBuffer(size_t aSize) { Log("WidevineBuffer(size=" PRIuSIZE ") created", aSize); mBuffer.SetLength(aSize); } ~WidevineBuffer() { Log("WidevineBuffer(size=" PRIuSIZE ") destroyed", Size()); } void Destroy() override { delete this; } uint32_t Capacity() const override { return mBuffer.Length(); }; uint8_t* Data() override { return mBuffer.Elements(); } void SetSize(uint32_t aSize) override { mBuffer.SetLength(aSize); } uint32_t Size() const override { return mBuffer.Length(); } private: WidevineBuffer(const WidevineBuffer&); void operator=(const WidevineBuffer&); nsTArray mBuffer; }; Buffer* WidevineDecryptor::Allocate(uint32_t aCapacity) { Log("Decryptor::Allocate(capacity=%u)", aCapacity); return new WidevineBuffer(aCapacity); } class TimerTask : public GMPTask { public: TimerTask(WidevineDecryptor* aDecryptor, RefPtr aCDM, void* aContext) : mDecryptor(aDecryptor) , mCDM(aCDM) , mContext(aContext) { } ~TimerTask() override {} void Run() override { mCDM->GetCDM()->TimerExpired(mContext); } void Destroy() override { delete this; } private: RefPtr mDecryptor; RefPtr mCDM; void* mContext; }; void WidevineDecryptor::SetTimer(int64_t aDelayMs, void* aContext) { Log("Decryptor::SetTimer(delay_ms=%lld, context=0x%x)", aDelayMs, aContext); if (mCDM) { GMPSetTimerOnMainThread(new TimerTask(this, mCDM, aContext), aDelayMs); } } Time WidevineDecryptor::GetCurrentWallTime() { return base::Time::Now().ToDoubleT(); } void ChromiumCDMChild::OnResolveKeyStatusPromise(uint32_t aPromiseId, cdm::KeyStatus aKeyStatus) { //TODO: The callback of GetStatusForPolicy. See Mozilla bug 1404230. } void WidevineDecryptor::OnResolveNewSessionPromise(uint32_t aPromiseId, const char* aSessionId, uint32_t aSessionIdSize) { if (!mCallback) { Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId); return; } Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d)", aPromiseId); auto iter = mPromiseIdToNewSessionTokens.find(aPromiseId); if (iter == mPromiseIdToNewSessionTokens.end()) { Log("FAIL: Decryptor::OnResolveNewSessionPromise(aPromiseId=%d) unknown aPromiseId", aPromiseId); return; } mCallback->SetSessionId(iter->second, aSessionId, aSessionIdSize); mCallback->ResolvePromise(aPromiseId); mPromiseIdToNewSessionTokens.erase(iter); } void WidevineDecryptor::OnResolvePromise(uint32_t aPromiseId) { if (!mCallback) { Log("Decryptor::OnResolvePromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId); return; } Log("Decryptor::OnResolvePromise(aPromiseId=%d)", aPromiseId); mCallback->ResolvePromise(aPromiseId); } // Align with spec, the Exceptions used by CDM to reject promises . // https://w3c.github.io/encrypted-media/#exceptions cdm::Exception ConvertCDMErrorToCDMException(cdm::Error error) { switch (error) { case cdm::kNotSupportedError: return cdm::Exception::kExceptionNotSupportedError; case cdm::kInvalidStateError: return cdm::Exception::kExceptionInvalidStateError; case cdm::kInvalidAccessError: return cdm::Exception::kExceptionTypeError; case cdm::kQuotaExceededError: return cdm::Exception::kExceptionQuotaExceededError; break; } return cdm::Exception::kExceptionInvalidStateError; } void WidevineDecryptor::OnRejectPromise(uint32_t aPromiseId, cdm::Exception aException, uint32_t aSystemCode, const char* aErrorMessage, uint32_t aErrorMessageSize) { if (!mCallback) { Log("Decryptor::OnRejectPromise(aPromiseId=%d, err=%d, sysCode=%u, msg=%s) FAIL; !mCallback", aPromiseId, (int)aException, aSystemCode, aErrorMessage); return; } Log("Decryptor::OnRejectPromise(aPromiseId=%d, err=%d, sysCode=%u, msg=%s)", aPromiseId, (int)aError, aSystemCode, aErrorMessage); mCallback->RejectPromise(aPromiseId, ToGMPDOMException(aException), !aErrorMessageSize ? "" : aErrorMessage, aErrorMessageSize); } static GMPSessionMessageType ToGMPMessageType(MessageType message_type) { switch (message_type) { case kLicenseRequest: return kGMPLicenseRequest; case kLicenseRenewal: return kGMPLicenseRenewal; case kLicenseRelease: return kGMPLicenseRelease; } return kGMPMessageInvalid; } void WidevineDecryptor::OnSessionMessage(const char* aSessionId, uint32_t aSessionIdSize, cdm::MessageType aMessageType, const char* aMessage, uint32_t aMessageSize) { if (!mCallback) { Log("Decryptor::OnSessionMessage() FAIL; !mCallback"); return; } Log("Decryptor::OnSessionMessage()"); mCallback->SessionMessage(aSessionId, aSessionIdSize, ToGMPMessageType(aMessageType), reinterpret_cast(aMessage), aMessageSize); } static GMPMediaKeyStatus ToGMPKeyStatus(KeyStatus aStatus) { switch (aStatus) { case kUsable: return kGMPUsable; case kInternalError: return kGMPInternalError; case kExpired: return kGMPExpired; case kOutputRestricted: return kGMPOutputRestricted; case kOutputDownscaled: return kGMPOutputDownscaled; case kStatusPending: return kGMPStatusPending; case kReleased: return kGMPReleased; } return kGMPUnknown; } void WidevineDecryptor::OnSessionKeysChange(const char* aSessionId, uint32_t aSessionIdSize, bool aHasAdditionalUsableKey, const KeyInformation* aKeysInfo, uint32_t aKeysInfoCount) { if (!mCallback) { Log("Decryptor::OnSessionKeysChange() FAIL; !mCallback"); return; } Log("Decryptor::OnSessionKeysChange()"); nsTArray key_infos; for (uint32_t i = 0; i < aKeysInfoCount; i++) { key_infos.AppendElement(GMPMediaKeyInfo(aKeysInfo[i].key_id, aKeysInfo[i].key_id_size, ToGMPKeyStatus(aKeysInfo[i].status))); } mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdSize, key_infos.Elements(), key_infos.Length()); } static GMPTimestamp ToGMPTime(Time aCDMTime) { return static_cast(aCDMTime * 1000); } void WidevineDecryptor::OnExpirationChange(const char* aSessionId, uint32_t aSessionIdSize, Time aNewExpiryTime) { if (!mCallback) { Log("Decryptor::OnExpirationChange(sid=%s) t=%lf FAIL; !mCallback", aSessionId, aNewExpiryTime); return; } Log("Decryptor::OnExpirationChange(sid=%s) t=%lf", aSessionId, aNewExpiryTime); GMPTimestamp expiry = ToGMPTime(aNewExpiryTime); if (aNewExpiryTime == 0) { return; } mCallback->ExpirationChange(aSessionId, aSessionIdSize, expiry); } void WidevineDecryptor::OnSessionClosed(const char* aSessionId, uint32_t aSessionIdSize) { if (!mCallback) { Log("Decryptor::OnSessionClosed(sid=%s) FAIL; !mCallback", aSessionId); return; } Log("Decryptor::OnSessionClosed(sid=%s)", aSessionId); mCallback->SessionClosed(aSessionId, aSessionIdSize); } void WidevineDecryptor::OnLegacySessionError(const char* aSessionId, uint32_t aSessionIdLength, Error aError, uint32_t aSystemCode, const char* aErrorMessage, uint32_t aErrorMessageLength) { if (!mCallback) { Log("Decryptor::OnLegacySessionError(sid=%s, error=%d) FAIL; !mCallback", aSessionId, (int)aError); return; } Log("Decryptor::OnLegacySessionError(sid=%s, error=%d)", aSessionId, (int)aError); mCallback->SessionError(aSessionId, aSessionIdLength, ToGMPDOMException(aError), aSystemCode, aErrorMessage, aErrorMessageLength); } void WidevineDecryptor::SendPlatformChallenge(const char* aServiceId, uint32_t aServiceIdSize, const char* aChallenge, uint32_t aChallengeSize) { Log("Decryptor::SendPlatformChallenge(service_id=%s)", aServiceId); } void WidevineDecryptor::EnableOutputProtection(uint32_t aDesiredProtectionMask) { Log("Decryptor::EnableOutputProtection(mask=0x%x)", aDesiredProtectionMask); } void WidevineDecryptor::QueryOutputProtectionStatus() { Log("Decryptor::QueryOutputProtectionStatus()"); } void WidevineDecryptor::OnDeferredInitializationDone(StreamType aStreamType, Status aDecoderStatus) { Log("Decryptor::OnDeferredInitializationDone()"); } FileIO* WidevineDecryptor::CreateFileIO(FileIOClient* aClient) { Log("Decryptor::CreateFileIO()"); if (!mPersistentStateRequired) { return nullptr; } return new WidevineFileIO(aClient); } void WidevineDecryptor::RequestStorageId(uint32_t aVersion) { Log("Decryptor::RequestStorageId() aVersion = %u", aVersion); if (aVersion >= 0x80000000) { mCDM->OnStorageId(aVersion, nullptr, 0); return; } //TODO: Need to provide a menaingful buffer instead of a dummy one. mCDM->OnStorageId(aVersion, new uint8_t[1024*1024], 1024 * 1024); } } // namespace mozilla