From 2ad5b06f219bd1835ea7459a86f33dc2ac22a35b Mon Sep 17 00:00:00 2001 From: alwu Date: Wed, 29 May 2024 18:27:04 +0000 Subject: [PATCH] Bug 1898588 - part4 : check scheme per codec if encryptionScheme in MediaKeySystemMediaCapability exists. r=jolin In this patch, we make ContainerSupport be able to store pairs of content type and encryption scheme so that we can check the encryptionScheme per type if that is mentioned in a EME request. Differential Revision: https://phabricator.services.mozilla.com/D211645 --- dom/media/MediaData.cpp | 23 +++++++ dom/media/MediaData.h | 1 + dom/media/eme/EMEUtils.cpp | 12 +++- dom/media/eme/KeySystemConfig.h | 91 ++++++++++++++++++++------ dom/media/eme/MediaKeySystemAccess.cpp | 67 ++++++++----------- 5 files changed, 134 insertions(+), 60 deletions(-) diff --git a/dom/media/MediaData.cpp b/dom/media/MediaData.cpp index e9a9e4a626db..902fbbf76379 100644 --- a/dom/media/MediaData.cpp +++ b/dom/media/MediaData.cpp @@ -610,6 +610,29 @@ const char* CryptoSchemeToString(const CryptoScheme& aScheme) { } } +nsCString CryptoSchemeSetToString(const CryptoSchemeSet& aSchemes) { + nsAutoCString rv; + if (aSchemes.contains(CryptoScheme::Cenc)) { + rv.AppendLiteral("cenc"); + } + if (aSchemes.contains(CryptoScheme::Cbcs)) { + if (!rv.IsEmpty()) { + rv.AppendLiteral("/"); + } + rv.AppendLiteral("cbcs"); + } + if (aSchemes.contains(CryptoScheme::Cbcs_1_9)) { + if (!rv.IsEmpty()) { + rv.AppendLiteral("/"); + } + rv.AppendLiteral("cbcs-1-9"); + } + if (rv.IsEmpty()) { + rv.AppendLiteral("none"); + } + return std::move(rv); +} + CryptoScheme StringToCryptoScheme(const nsAString& aString) { if (aString.EqualsLiteral("cenc")) { return CryptoScheme::Cenc; diff --git a/dom/media/MediaData.h b/dom/media/MediaData.h index b8f575725a52..237bd838a6ec 100644 --- a/dom/media/MediaData.h +++ b/dom/media/MediaData.h @@ -592,6 +592,7 @@ enum class CryptoScheme : uint8_t { using CryptoSchemeSet = EnumSet; const char* CryptoSchemeToString(const CryptoScheme& aScheme); +nsCString CryptoSchemeSetToString(const CryptoSchemeSet& aSchemes); CryptoScheme StringToCryptoScheme(const nsAString& aString); class CryptoTrack { diff --git a/dom/media/eme/EMEUtils.cpp b/dom/media/eme/EMEUtils.cpp index c1340c32e092..f43dd1b750a1 100644 --- a/dom/media/eme/EMEUtils.cpp +++ b/dom/media/eme/EMEUtils.cpp @@ -171,16 +171,24 @@ void MFCDMCapabilitiesIPDLToKeySystemConfig( !aKeySystemConfig.mVideoRobustness.Contains(c.robustness())) { aKeySystemConfig.mVideoRobustness.AppendElement(c.robustness()); } + CryptoSchemeSet schemes; + for (const auto& scheme : c.encryptionSchemes()) { + schemes += scheme; + } aKeySystemConfig.mMP4.SetCanDecryptAndDecode( - NS_ConvertUTF16toUTF8(c.contentType())); + NS_ConvertUTF16toUTF8(c.contentType()), Some(schemes)); } for (const auto& c : aCDMConfig.audioCapabilities()) { if (!c.robustness().IsEmpty() && !aKeySystemConfig.mAudioRobustness.Contains(c.robustness())) { aKeySystemConfig.mAudioRobustness.AppendElement(c.robustness()); } + CryptoSchemeSet schemes; + for (const auto& scheme : c.encryptionSchemes()) { + schemes += scheme; + } aKeySystemConfig.mMP4.SetCanDecryptAndDecode( - NS_ConvertUTF16toUTF8(c.contentType())); + NS_ConvertUTF16toUTF8(c.contentType()), Some(schemes)); } aKeySystemConfig.mPersistentState = aCDMConfig.persistentState(); aKeySystemConfig.mDistinctiveIdentifier = aCDMConfig.distinctiveID(); diff --git a/dom/media/eme/KeySystemConfig.h b/dom/media/eme/KeySystemConfig.h index 3bc274af9b96..eb94ed9e3aab 100644 --- a/dom/media/eme/KeySystemConfig.h +++ b/dom/media/eme/KeySystemConfig.h @@ -7,6 +7,7 @@ #ifndef DOM_MEDIA_EME_KEYSYSTEMCONFIG_H_ #define DOM_MEDIA_EME_KEYSYSTEMCONFIG_H_ +#include "MediaData.h" #include "nsString.h" #include "nsTArray.h" #include "mozilla/MozPromise.h" @@ -80,38 +81,52 @@ struct KeySystemConfig { return !mCodecsDecoded.IsEmpty() || !mCodecsDecrypted.IsEmpty(); } - // CDM decrypts and decodes using a DRM robust decoder, and passes decoded - // samples back to Gecko for rendering. - bool DecryptsAndDecodes(const EMECodecString& aCodec) const { - return mCodecsDecoded.Contains(aCodec); + // True if a codec and scheme pair can be decrypted and decoded + bool DecryptsAndDecodes( + const EMECodecString& aCodec, + const Maybe& aScheme = Nothing()) const { + return CheckCodecAndSchemePair(mCodecsDecoded, aCodec, aScheme); } - // CDM decrypts and passes the decrypted samples back to Gecko for decoding. - bool Decrypts(const EMECodecString& aCodec) const { - return mCodecsDecrypted.Contains(aCodec); + // True if a codec and scheme pair can be decrypted + bool Decrypts(const EMECodecString& aCodec, + const Maybe& aScheme = Nothing()) const { + return CheckCodecAndSchemePair(mCodecsDecrypted, aCodec, aScheme); } - void SetCanDecryptAndDecode(const EMECodecString& aCodec) { + void SetCanDecryptAndDecode( + const EMECodecString& aCodec, + const Maybe& aSchemes = Nothing{}) { // Can't both decrypt and decrypt-and-decode a codec. - MOZ_ASSERT(!Decrypts(aCodec)); + MOZ_ASSERT(!ContainsDecryptedOnlyCodec(aCodec)); // Prevent duplicates. - MOZ_ASSERT(!DecryptsAndDecodes(aCodec)); - mCodecsDecoded.AppendElement(aCodec); + MOZ_ASSERT(!ContainsDecryptedAndDecodedCodec(aCodec)); + + mCodecsDecoded.AppendElement(CodecSchemePair{aCodec, aSchemes}); } - void SetCanDecrypt(const EMECodecString& aCodec) { + void SetCanDecrypt(const EMECodecString& aCodec, + const Maybe& aSchemes = Nothing{}) { // Prevent duplicates. - MOZ_ASSERT(!Decrypts(aCodec)); + MOZ_ASSERT(!ContainsDecryptedOnlyCodec(aCodec)); // Can't both decrypt and decrypt-and-decode a codec. - MOZ_ASSERT(!DecryptsAndDecodes(aCodec)); - mCodecsDecrypted.AppendElement(aCodec); + MOZ_ASSERT(!ContainsDecryptedAndDecodedCodec(aCodec)); + mCodecsDecrypted.AppendElement(CodecSchemePair{aCodec, aSchemes}); } EMECodecString GetDebugInfo() const { EMECodecString info; info.AppendLiteral("decoding-and-decrypting:["); for (size_t idx = 0; idx < mCodecsDecoded.Length(); idx++) { - info.Append(mCodecsDecoded[idx]); + const auto& cur = mCodecsDecoded[idx]; + info.Append(cur.first); + if (cur.second) { + info.AppendLiteral("("); + info.Append(CryptoSchemeSetToString(*cur.second)); + info.AppendLiteral(")"); + } else { + info.AppendLiteral("(all)"); + } if (idx + 1 < mCodecsDecoded.Length()) { info.AppendLiteral(","); } @@ -119,7 +134,15 @@ struct KeySystemConfig { info.AppendLiteral("],"); info.AppendLiteral("decrypting-only:["); for (size_t idx = 0; idx < mCodecsDecrypted.Length(); idx++) { - info.Append(mCodecsDecrypted[idx]); + const auto& cur = mCodecsDecrypted[idx]; + info.Append(cur.first); + if (cur.second) { + info.AppendLiteral("("); + info.Append(CryptoSchemeSetToString(*cur.second)); + info.AppendLiteral(")"); + } else { + info.AppendLiteral("(all)"); + } if (idx + 1 < mCodecsDecrypted.Length()) { info.AppendLiteral(","); } @@ -129,8 +152,38 @@ struct KeySystemConfig { } private: - nsTArray mCodecsDecoded; - nsTArray mCodecsDecrypted; + using CodecSchemePair = std::pair>; + // These two arrays are exclusive, the codec in one array can't appear on + // another array. If CryptoSchemeSet is nothing, that means the codec has + // support for all schemes, which is our default. Setting CryptoSchemeSet + // explicitly can restrict avaiable schemes for a codec. + nsTArray mCodecsDecoded; + nsTArray mCodecsDecrypted; + + bool ContainsDecryptedOnlyCodec(const EMECodecString& aCodec) const { + return std::any_of( + mCodecsDecrypted.begin(), mCodecsDecrypted.end(), + [&](const auto& aPair) { return aPair.first.Equals(aCodec); }); + } + bool ContainsDecryptedAndDecodedCodec(const EMECodecString& aCodec) const { + return std::any_of( + mCodecsDecoded.begin(), mCodecsDecoded.end(), + [&](const auto& aPair) { return aPair.first.Equals(aCodec); }); + } + bool CheckCodecAndSchemePair(const nsTArray& aArray, + const EMECodecString& aCodec, + const Maybe& aScheme) const { + return std::any_of(aArray.begin(), aArray.end(), [&](const auto& aPair) { + if (!aPair.first.Equals(aCodec)) { + return false; + } + // No scheme is specified, which means accepting all schemes. + if (!aPair.second || !aScheme) { + return true; + } + return aPair.second->contains(*aScheme); + }); + } }; // Return true if given key system is supported on the current device. diff --git a/dom/media/eme/MediaKeySystemAccess.cpp b/dom/media/eme/MediaKeySystemAccess.cpp index 106373579f06..fa967e987fbf 100644 --- a/dom/media/eme/MediaKeySystemAccess.cpp +++ b/dom/media/eme/MediaKeySystemAccess.cpp @@ -319,17 +319,18 @@ static bool CanDecryptAndDecode( CodecType aCodecType, const KeySystemConfig::ContainerSupport& aContainerSupport, const nsTArray& aCodecs, + const Maybe& aScheme, DecoderDoctorDiagnostics* aDiagnostics) { MOZ_ASSERT(aCodecType != Invalid); for (const KeySystemConfig::EMECodecString& codec : aCodecs) { MOZ_ASSERT(!codec.IsEmpty()); - if (aContainerSupport.DecryptsAndDecodes(codec)) { + if (aContainerSupport.DecryptsAndDecodes(codec, aScheme)) { // GMP can decrypt-and-decode this codec. continue; } - if (aContainerSupport.Decrypts(codec)) { + if (aContainerSupport.Decrypts(codec, aScheme)) { IgnoredErrorResult rv; MediaSource::IsTypeSupported(aContentType, aDiagnostics, rv); if (!rv.Failed()) { @@ -361,29 +362,18 @@ static bool CanDecryptAndDecode( return true; } -// Returns if an encryption scheme is supported per: -// https://github.com/WICG/encrypted-media-encryption-scheme/blob/master/explainer.md -// To be supported the scheme should be one of: -// - null -// - missing (which will result in the nsString being set to void and thus null) -// - one of the schemes supported by the CDM -// If the pref to enable this behavior is not set, then the value should be -// empty/null, as the dict member will not be exposed. In this case we will -// always report support as we would before this feature was implemented. -static bool SupportsEncryptionScheme( - const nsString& aEncryptionScheme, - const nsTArray& aSupportedEncryptionSchemes) { - MOZ_ASSERT( - DOMStringIsNull(aEncryptionScheme) || - StaticPrefs::media_eme_encrypted_media_encryption_scheme_enabled(), - "Encryption scheme checking support must be preffed on for " - "encryptionScheme to be a non-null string"); +// https://w3c.github.io/encrypted-media/#dom-mediakeysystemmediacapability-encryptionscheme +// This convert `encryptionScheme` to the type of CryptoScheme, so that we can +// further check whether the scheme is supported or not in our media pipeline. +Maybe ConvertEncryptionSchemeStrToScheme( + const nsString& aEncryptionScheme) { if (DOMStringIsNull(aEncryptionScheme)) { // "A missing or null value indicates that any encryption scheme is // acceptable." - return true; + return Nothing(); } - return aSupportedEncryptionSchemes.Contains(aEncryptionScheme); + auto scheme = StringToCryptoScheme(aEncryptionScheme); + return Some(scheme); } static bool ToSessionType(const nsAString& aSessionType, @@ -478,7 +468,8 @@ static bool IsParameterUnrecognized(const nsAString& aContentType) { return false; } -// 3.1.1.3 Get Supported Capabilities for Audio/Video Type +// 3.2.2.3 Get Supported Capabilities for Audio/Video Type +// https://w3c.github.io/encrypted-media/#get-supported-capabilities-for-audio-video-type static Sequence GetSupportedCapabilities( const CodecType aCodecType, const nsTArray& aRequestedCapabilities, @@ -667,6 +658,20 @@ static Sequence GetSupportedCapabilities( NS_ConvertUTF16toUTF8(encryptionScheme).get()); continue; } + // If encryption scheme is non-null and is not recognized or not supported + // by implementation, continue to the next iteration. + const auto scheme = ConvertEncryptionSchemeStrToScheme(encryptionScheme); + if (scheme && *scheme == CryptoScheme::None) { + EME_LOG( + "MediaKeySystemConfiguration (label='%s') " + "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; " + "unsupported scheme string.", + NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), + NS_ConvertUTF16toUTF8(contentTypeString).get(), + NS_ConvertUTF16toUTF8(robustness).get(), + NS_ConvertUTF16toUTF8(encryptionScheme).get()); + continue; + } // If robustness is not the empty string and contains an unrecognized // value or a value not supported by implementation, continue to the // next iteration. String comparison is case-sensitive. @@ -698,22 +703,6 @@ static Sequence GetSupportedCapabilities( // Note: specified robustness requirements are satisfied. } - // If preffed on: "In the Get Supported Capabilities for Audio/Video Type - // algorithm, implementations must skip capabilities specifying unsupported - // encryption schemes." - if (!SupportsEncryptionScheme(encryptionScheme, - aKeySystem.mEncryptionSchemes)) { - EME_LOG( - "MediaKeySystemConfiguration (label='%s') " - "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; " - "encryption scheme unsupported by CDM requested.", - NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), - NS_ConvertUTF16toUTF8(contentTypeString).get(), - NS_ConvertUTF16toUTF8(robustness).get(), - NS_ConvertUTF16toUTF8(encryptionScheme).get()); - continue; - } - // If the user agent and implementation definitely support playback of // encrypted media data for the combination of container, media types, // robustness and local accumulated configuration in combination with @@ -721,7 +710,7 @@ static Sequence GetSupportedCapabilities( const auto& containerSupport = supportedInMP4 ? aKeySystem.mMP4 : aKeySystem.mWebM; if (!CanDecryptAndDecode(aKeySystem.mKeySystem, contentTypeString, - majorType, containerSupport, codecs, + majorType, containerSupport, codecs, scheme, aDiagnostics)) { EME_LOG( "MediaKeySystemConfiguration (label='%s') "