910e62b5创建于 1月15日历史提交
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/cdm/common/cdm_manifest.h"

#include <stddef.h>

#include <memory>
#include <string>
#include <string_view>
#include <vector>

#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/json/json_file_value_serializer.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/values.h"
#include "base/version.h"
#include "content/public/common/cdm_info.h"
#include "media/base/cdm_capability.h"
#include "media/base/content_decryption_module.h"
#include "media/base/decrypt_config.h"
#include "media/base/video_codecs.h"
#include "media/cdm/supported_audio_codecs.h"
#include "media/cdm/supported_cdm_versions.h"
#include "media/media_buildflags.h"

#if !BUILDFLAG(ENABLE_LIBRARY_CDMS)
#error This file should only be compiled when library CDMs are enabled
#endif

namespace {

// The CDM manifest includes several custom values, all beginning with "x-cdm-".
// They are:
//   x-cdm-module-versions
//   x-cdm-interface-versions
//   x-cdm-host-versions
//   x-cdm-codecs
//   x-cdm-persistent-license-support
//   x-cdm-supported-encryption-schemes
// What they represent is listed below. They should never have non-backwards
// compatible changes. All values are strings. All values that are lists are
// delimited by commas. No trailing commas. For example, "1,2,4".
const char kCdmValueDelimiter[] = ",";

// This field in the manifest is parsed by component updater code (e.g.
// `ComponentInstaller::FindPreinstallation()`) as well as in this file.
const char kCdmVersion[] = "version";

// The following entries are required.
//  Interface versions are lists of integers (e.g. "1" or "1,2,4").
//  All match the interface versions from content_decryption_module.h that the
//  CDM supports.
//    Matches CDM_MODULE_VERSION.
const char kCdmModuleVersionsName[] = "x-cdm-module-versions";
//    Matches supported ContentDecryptionModule_* version(s).
const char kCdmInterfaceVersionsName[] = "x-cdm-interface-versions";
//    Matches supported Host_* version(s).
const char kCdmHostVersionsName[] = "x-cdm-host-versions";
//  The codecs list is a list of simple codec names (e.g. "vp8,vorbis").
const char kCdmCodecsListName[] = "x-cdm-codecs";
//  Whether persistent license is supported by the CDM: "true" or "false".
const char kCdmPersistentLicenseSupportName[] =
    "x-cdm-persistent-license-support";
//  The list of supported encryption schemes (e.g. ["cenc","cbcs"]).
const char kCdmSupportedEncryptionSchemesName[] =
    "x-cdm-supported-encryption-schemes";

// The following strings are used to specify supported codecs in the
// parameter |kCdmCodecsListName|.
const char kCdmSupportedCodecVp8[] = "vp8";
// Supports at least VP9 profile 0 and profile 2.
const char kCdmSupportedCodecVp9[] = "vp09";
const char kCdmSupportedCodecAv1[] = "av01";
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
const char kCdmSupportedCodecAvc1[] = "avc1";
#endif

// The following strings are used to specify supported encryption schemes in
// the parameter |kCdmSupportedEncryptionSchemesName|.
const char kCdmSupportedEncryptionSchemeCenc[] = "cenc";
const char kCdmSupportedEncryptionSchemeCbcs[] = "cbcs";

typedef bool (*VersionCheckFunc)(int version);

// Returns whether the CDM's API version, as specified in the manifest by
// |version_name|, is supported in this Chrome binary and not disabled at run
// time by calling |version_check_func|. If the manifest entry contains multiple
// values, each one is checked sequentially, and if any one is supported, this
// function returns true. If all values in the manifest entry are not supported,
// then return false.
bool CheckForCompatibleVersion(const base::Value::Dict& manifest,
                               const std::string version_name,
                               VersionCheckFunc version_check_func) {
  auto* version_string = manifest.FindString(version_name);
  if (!version_string) {
    DVLOG(1) << "CDM manifest missing " << version_name;
    return false;
  }

  DVLOG_IF(1, version_string->empty())
      << "CDM manifest has empty " << version_name;

  for (std::string_view ver_str :
       base::SplitStringPiece(*version_string, kCdmValueDelimiter,
                              base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
    int version = 0;
    if (base::StringToInt(ver_str, &version) && version_check_func(version))
      return true;
  }

  DVLOG(1) << "CDM manifest has no supported " << version_name << " in '"
           << *version_string << "'";
  return false;
}

// Returns true and updates |encryption_schemes| if the appropriate manifest
// entry is valid. Returns false and does not modify |encryption_schemes| if the
// manifest entry is incorrectly formatted. It is assumed that all CDMs support
// 'cenc', so if the manifest entry is missing, the result will indicate support
// for 'cenc' only. Incorrect types in the manifest entry will log the error and
// fail. Unrecognized values will be reported but otherwise ignored.
bool GetEncryptionSchemes(
    const base::Value::Dict& manifest,
    base::flat_set<media::EncryptionScheme>* encryption_schemes) {
  DCHECK(encryption_schemes);

  const base::Value* value = manifest.Find(kCdmSupportedEncryptionSchemesName);
  if (!value) {
    // No manifest entry found, so assume only 'cenc' supported for backwards
    // compatibility.
    encryption_schemes->insert(media::EncryptionScheme::kCenc);
    return true;
  }

  if (!value->is_list()) {
    DLOG(ERROR) << "CDM manifest entry " << kCdmSupportedEncryptionSchemesName
                << " is not a list.";
    return false;
  }

  base::flat_set<media::EncryptionScheme> result;
  for (const auto& item : value->GetList()) {
    if (!item.is_string()) {
      DLOG(ERROR) << "Unrecognized item type in CDM manifest entry "
                  << kCdmSupportedEncryptionSchemesName;
      return false;
    }

    const std::string& scheme = item.GetString();
    if (scheme == kCdmSupportedEncryptionSchemeCenc) {
      result.insert(media::EncryptionScheme::kCenc);
    } else if (scheme == kCdmSupportedEncryptionSchemeCbcs) {
      result.insert(media::EncryptionScheme::kCbcs);
    } else {
      DLOG(WARNING) << "Unrecognized encryption scheme '" << scheme
                    << "' in CDM manifest entry "
                    << kCdmSupportedEncryptionSchemesName;
    }
  }

  // As the manifest entry exists, it must specify at least one valid value.
  if (result.empty())
    return false;

  encryption_schemes->swap(result);
  return true;
}

// Returns true and updates |audio_codecs| with the full set of audio
// codecs that support decryption.
bool GetAudioCodecs(const base::Value::Dict& manifest,
                    base::flat_set<media::AudioCodec>* audio_codecs) {
  DCHECK(audio_codecs);

  // Note that desktop CDMs only support decryption of audio content,
  // no decoding. Manifest does not contain any audio codecs, so return
  // the standard set of audio codecs supported only if there is at least
  // one encryption scheme specified.
  base::flat_set<media::EncryptionScheme> encryption_schemes;
  if (!GetEncryptionSchemes(manifest, &encryption_schemes)) {
    return false;
  }

  DCHECK(!encryption_schemes.empty());
  *audio_codecs = media::GetCdmSupportedAudioCodecs();
  return true;
}

// Returns true and updates |video_codecs| if the appropriate manifest entry is
// valid. Returns false and does not modify |video_codecs| if the manifest entry
// is incorrectly formatted.
bool GetVideoCodecs(const base::Value::Dict& manifest,
                    media::CdmCapability::VideoCodecMap* video_codecs) {
  DCHECK(video_codecs);

  const base::Value* value = manifest.Find(kCdmCodecsListName);
  if (!value) {
    DLOG(WARNING) << "CDM manifest is missing codecs.";
    return true;
  }

  if (!value->is_string()) {
    DLOG(ERROR) << "CDM manifest entry " << kCdmCodecsListName
                << " is not a string.";
    return false;
  }

  const std::string& codecs = value->GetString();
  if (codecs.empty()) {
    DLOG(WARNING) << "CDM manifest has empty codecs list.";
    return true;
  }

  const std::vector<std::string_view> supported_codecs =
      base::SplitStringPiece(codecs, kCdmValueDelimiter, base::TRIM_WHITESPACE,
                             base::SPLIT_WANT_NONEMPTY);

  // As the manifest string does not include profiles, specify {} to indicate
  // that all relevant profiles should be considered supported.
  media::CdmCapability::VideoCodecMap result;
  const media::VideoCodecInfo kAllProfiles;
  for (const auto& codec : supported_codecs) {
    if (codec == kCdmSupportedCodecVp8) {
      result.emplace(media::VideoCodec::kVP8, kAllProfiles);
    } else if (codec == kCdmSupportedCodecVp9) {
      result.emplace(media::VideoCodec::kVP9, kAllProfiles);
    } else if (codec == kCdmSupportedCodecAv1) {
      result.emplace(media::VideoCodec::kAV1, kAllProfiles);
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
    } else if (codec == kCdmSupportedCodecAvc1) {
      result.emplace(media::VideoCodec::kH264, kAllProfiles);
#endif
    }
  }

  video_codecs->swap(result);
  return true;
}

// Returns true and updates |session_types| if the appropriate manifest entry is
// valid. Returns false if the manifest entry is incorrectly formatted.
bool GetSessionTypes(const base::Value::Dict& manifest,
                     base::flat_set<media::CdmSessionType>* session_types) {
  DCHECK(session_types);

  bool is_persistent_license_supported = false;
  const base::Value* value = manifest.Find(kCdmPersistentLicenseSupportName);
  if (value) {
    if (!value->is_bool())
      return false;
    is_persistent_license_supported = value->GetBool();
  }

  // Temporary session is always supported.
  session_types->insert(media::CdmSessionType::kTemporary);

  if (is_persistent_license_supported)
    session_types->insert(media::CdmSessionType::kPersistentLicense);

  return true;
}

bool GetVersion(const base::Value::Dict& manifest, base::Version* version) {
  auto* version_string = manifest.FindString(kCdmVersion);
  if (!version_string) {
    DLOG(ERROR) << "CDM manifest missing " << kCdmVersion;
    return false;
  }

  *version = base::Version(*version_string);
  if (!version->IsValid()) {
    DLOG(ERROR) << "CDM manifest version " << version_string << " is invalid.";
    return false;
  }

  return true;
}

}  // namespace

bool IsCdmManifestCompatibleWithChrome(const base::Value::Dict& manifest) {
  return CheckForCompatibleVersion(manifest, kCdmModuleVersionsName,
                                   media::IsSupportedCdmModuleVersion) &&
         CheckForCompatibleVersion(
             manifest, kCdmInterfaceVersionsName,
             media::IsSupportedAndEnabledCdmInterfaceVersion) &&
         CheckForCompatibleVersion(manifest, kCdmHostVersionsName,
                                   media::IsSupportedCdmHostVersion);
}

bool ParseCdmManifest(const base::Value::Dict& manifest,
                      media::CdmCapability* capability) {
  return GetAudioCodecs(manifest, &capability->audio_codecs) &&
         GetVideoCodecs(manifest, &capability->video_codecs) &&
         GetEncryptionSchemes(manifest, &capability->encryption_schemes) &&
         GetSessionTypes(manifest, &capability->session_types) &&
         GetVersion(manifest, &capability->version);
}

bool ParseCdmManifestFromPath(const base::FilePath& manifest_path,
                              media::CdmCapability* capability) {
  JSONFileValueDeserializer deserializer(manifest_path);
  int error_code;
  std::string error_message;
  std::unique_ptr<base::Value> manifest =
      deserializer.Deserialize(&error_code, &error_message);
  if (!manifest || !manifest->is_dict()) {
    DLOG(ERROR) << "Could not deserialize CDM manifest from " << manifest_path
                << ". Error: " << error_code << " / " << error_message;
    return false;
  }
  base::Value::Dict& manifest_dict = manifest->GetDict();

  return IsCdmManifestCompatibleWithChrome(manifest_dict) &&
         ParseCdmManifest(manifest_dict, capability);
}