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
This commit is contained in:
alwu 2024-05-29 18:27:04 +00:00
parent 6dfee59a8f
commit 2ad5b06f21
5 changed files with 134 additions and 60 deletions

View file

@ -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;

View file

@ -592,6 +592,7 @@ enum class CryptoScheme : uint8_t {
using CryptoSchemeSet = EnumSet<CryptoScheme, uint8_t>;
const char* CryptoSchemeToString(const CryptoScheme& aScheme);
nsCString CryptoSchemeSetToString(const CryptoSchemeSet& aSchemes);
CryptoScheme StringToCryptoScheme(const nsAString& aString);
class CryptoTrack {

View file

@ -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();

View file

@ -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<CryptoScheme>& 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<CryptoScheme>& aScheme = Nothing()) const {
return CheckCodecAndSchemePair(mCodecsDecrypted, aCodec, aScheme);
}
void SetCanDecryptAndDecode(const EMECodecString& aCodec) {
void SetCanDecryptAndDecode(
const EMECodecString& aCodec,
const Maybe<CryptoSchemeSet>& 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<CryptoSchemeSet>& 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<EMECodecString> mCodecsDecoded;
nsTArray<EMECodecString> mCodecsDecrypted;
using CodecSchemePair = std::pair<EMECodecString, Maybe<CryptoSchemeSet>>;
// 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<CodecSchemePair> mCodecsDecoded;
nsTArray<CodecSchemePair> 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<CodecSchemePair>& aArray,
const EMECodecString& aCodec,
const Maybe<CryptoScheme>& 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.

View file

@ -319,17 +319,18 @@ static bool CanDecryptAndDecode(
CodecType aCodecType,
const KeySystemConfig::ContainerSupport& aContainerSupport,
const nsTArray<KeySystemConfig::EMECodecString>& aCodecs,
const Maybe<CryptoScheme>& 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<nsString>& 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<CryptoScheme> 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<MediaKeySystemMediaCapability> GetSupportedCapabilities(
const CodecType aCodecType,
const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities,
@ -667,6 +658,20 @@ static Sequence<MediaKeySystemMediaCapability> 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<MediaKeySystemMediaCapability> 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<MediaKeySystemMediaCapability> 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') "