gecko-dev/dom/media/gmp/ChromiumCDMAdapter.cpp
Bryce Seager van Dyk 83332521e5 Bug 1711912 - Move key system name constants into their own header. r=alwu
Move constants and fix up includes following doing so.

This is motivated by that future patches will want to use these constants in the
clear key CDM. If we don't move these constants and try to include the existing
VideoUtils in the clear key CDM this will break the build (because the clear key
CDM uses a restricted subset of the stuff we get in Gecko).

Differential Revision: https://phabricator.services.mozilla.com/D122631
2021-08-19 17:14:08 +00:00

304 lines
9.2 KiB
C++

/* -*- 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 "ChromiumCDMAdapter.h"
#include <utility>
#include "GMPLog.h"
#include "WidevineUtils.h"
#include "content_decryption_module.h"
#include "content_decryption_module_ext.h"
#include "gmp-api/gmp-entrypoints.h"
#include "gmp-api/gmp-video-codec.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/HelperMacros.h"
#include "mozilla/dom/KeySystemNames.h"
#ifdef XP_WIN
# include "WinUtils.h"
# include "nsWindowsDllInterceptor.h"
# include <windows.h>
# include <strsafe.h>
# include <unordered_map>
# include <vector>
#else
# include <sys/types.h>
# include <sys/stat.h>
# include <unistd.h>
# include <fcntl.h>
#endif
const GMPPlatformAPI* sPlatform = nullptr;
namespace mozilla {
#ifdef XP_WIN
static void InitializeHooks();
#endif
ChromiumCDMAdapter::ChromiumCDMAdapter(
nsTArray<std::pair<nsCString, nsCString>>&& aHostPathPairs) {
#ifdef XP_WIN
InitializeHooks();
#endif
PopulateHostFiles(std::move(aHostPathPairs));
}
void ChromiumCDMAdapter::SetAdaptee(PRLibrary* aLib) { mLib = aLib; }
void* ChromiumCdmHost(int aHostInterfaceVersion, void* aUserData) {
GMP_LOG_DEBUG("ChromiumCdmHostFunc(%d, %p)", aHostInterfaceVersion,
aUserData);
if (aHostInterfaceVersion != cdm::Host_10::kVersion) {
return nullptr;
}
return aUserData;
}
#ifdef MOZILLA_OFFICIAL
static cdm::HostFile TakeToCDMHostFile(HostFileData& aHostFileData) {
return cdm::HostFile(aHostFileData.mBinary.Path().get(),
aHostFileData.mBinary.TakePlatformFile(),
aHostFileData.mSig.TakePlatformFile());
}
#endif
GMPErr ChromiumCDMAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI) {
GMP_LOG_DEBUG("ChromiumCDMAdapter::GMPInit");
sPlatform = aPlatformAPI;
if (!mLib) {
return GMPGenericErr;
}
#ifdef MOZILLA_OFFICIAL
// Note: we must call the VerifyCdmHost_0 function if it's present before
// we call the initialize function.
auto verify = reinterpret_cast<decltype(::VerifyCdmHost_0)*>(
PR_FindFunctionSymbol(mLib, MOZ_STRINGIFY(VerifyCdmHost_0)));
if (verify) {
nsTArray<cdm::HostFile> files;
for (HostFileData& hostFile : mHostFiles) {
files.AppendElement(TakeToCDMHostFile(hostFile));
}
bool result = verify(files.Elements(), files.Length());
GMP_LOG_DEBUG("%s VerifyCdmHost_0 returned %d", __func__, result);
}
#endif
auto init = reinterpret_cast<decltype(::INITIALIZE_CDM_MODULE)*>(
PR_FindFunctionSymbol(mLib, MOZ_STRINGIFY(INITIALIZE_CDM_MODULE)));
if (!init) {
return GMPGenericErr;
}
GMP_LOG_DEBUG(MOZ_STRINGIFY(INITIALIZE_CDM_MODULE) "()");
init();
return GMPNoErr;
}
GMPErr ChromiumCDMAdapter::GMPGetAPI(const char* aAPIName, void* aHostAPI,
void** aPluginAPI,
const nsCString& aKeySystem) {
MOZ_ASSERT(
aKeySystem.EqualsLiteral(kWidevineKeySystemName) ||
aKeySystem.EqualsLiteral(kClearKeyKeySystemName) ||
aKeySystem.EqualsLiteral(kClearKeyWithProtectionQueryKeySystemName) ||
aKeySystem.EqualsLiteral("fake"),
"Should not get an unrecognized key system. Why didn't it get "
"blocked by MediaKeySystemAccess?");
GMP_LOG_DEBUG("ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %s) this=0x%p",
aAPIName, aHostAPI, aPluginAPI, aKeySystem.get(), this);
bool isCdm10 = !strcmp(aAPIName, CHROMIUM_CDM_API);
if (!isCdm10) {
MOZ_ASSERT_UNREACHABLE("We only support and expect cdm10!");
GMP_LOG_DEBUG(
"ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p got "
"unsupported CDM version!",
aAPIName, aHostAPI, aPluginAPI, this);
return GMPGenericErr;
}
auto create = reinterpret_cast<decltype(::CreateCdmInstance)*>(
PR_FindFunctionSymbol(mLib, "CreateCdmInstance"));
if (!create) {
GMP_LOG_DEBUG(
"ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p "
"FAILED to find CreateCdmInstance",
aAPIName, aHostAPI, aPluginAPI, this);
return GMPGenericErr;
}
const int version = cdm::ContentDecryptionModule_10::kVersion;
void* cdm = create(version, aKeySystem.get(), aKeySystem.Length(),
&ChromiumCdmHost, aHostAPI);
if (!cdm) {
GMP_LOG_DEBUG(
"ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p "
"FAILED to create cdm version %d",
aAPIName, aHostAPI, aPluginAPI, this, version);
return GMPGenericErr;
}
GMP_LOG_DEBUG("cdm: 0x%p, version: %d", cdm, version);
*aPluginAPI = cdm;
return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
}
void ChromiumCDMAdapter::GMPShutdown() {
GMP_LOG_DEBUG("ChromiumCDMAdapter::GMPShutdown()");
decltype(::DeinitializeCdmModule)* deinit;
deinit =
(decltype(deinit))(PR_FindFunctionSymbol(mLib, "DeinitializeCdmModule"));
if (deinit) {
GMP_LOG_DEBUG("DeinitializeCdmModule()");
deinit();
}
}
/* static */
bool ChromiumCDMAdapter::Supports(int32_t aModuleVersion,
int32_t aInterfaceVersion,
int32_t aHostVersion) {
return aModuleVersion == CDM_MODULE_VERSION &&
aInterfaceVersion == cdm::ContentDecryptionModule_10::kVersion &&
aHostVersion == cdm::Host_10::kVersion;
}
#ifdef XP_WIN
static WindowsDllInterceptor sKernel32Intercept;
typedef DWORD(WINAPI* QueryDosDeviceWFnPtr)(_In_opt_ LPCWSTR lpDeviceName,
_Out_ LPWSTR lpTargetPath,
_In_ DWORD ucchMax);
static WindowsDllInterceptor::FuncHookType<QueryDosDeviceWFnPtr>
sOriginalQueryDosDeviceWFnPtr;
static std::unordered_map<std::wstring, std::wstring>* sDeviceNames = nullptr;
DWORD WINAPI QueryDosDeviceWHook(LPCWSTR lpDeviceName, LPWSTR lpTargetPath,
DWORD ucchMax) {
if (!sDeviceNames) {
return 0;
}
std::wstring name = std::wstring(lpDeviceName);
auto iter = sDeviceNames->find(name);
if (iter == sDeviceNames->end()) {
return 0;
}
const std::wstring& device = iter->second;
if (device.size() + 1 > ucchMax) {
return 0;
}
PodCopy(lpTargetPath, device.c_str(), device.size());
lpTargetPath[device.size()] = 0;
GMP_LOG_DEBUG("QueryDosDeviceWHook %S -> %S", lpDeviceName, lpTargetPath);
return device.size();
}
static std::vector<std::wstring> GetDosDeviceNames() {
std::vector<std::wstring> v;
std::vector<wchar_t> buf;
buf.resize(1024);
DWORD rv = GetLogicalDriveStringsW(buf.size(), buf.data());
if (rv == 0 || rv > buf.size()) {
return v;
}
// buf will be a list of null terminated strings, with the last string
// being 0 length.
const wchar_t* p = buf.data();
const wchar_t* end = &buf.back();
size_t l;
while (p < end && (l = wcsnlen_s(p, end - p)) > 0) {
// The string is of the form "C:\". We need to strip off the trailing
// backslash.
std::wstring drive = std::wstring(p, p + l);
if (drive.back() == '\\') {
drive.erase(drive.end() - 1);
}
v.push_back(move(drive));
p += l + 1;
}
return v;
}
static std::wstring GetDeviceMapping(const std::wstring& aDosDeviceName) {
wchar_t buf[MAX_PATH] = {0};
DWORD rv = QueryDosDeviceW(aDosDeviceName.c_str(), buf, MAX_PATH);
if (rv == 0) {
return std::wstring(L"");
}
return std::wstring(buf, buf + rv);
}
static void InitializeHooks() {
static bool initialized = false;
if (initialized) {
return;
}
initialized = true;
sDeviceNames = new std::unordered_map<std::wstring, std::wstring>();
for (const std::wstring& name : GetDosDeviceNames()) {
sDeviceNames->emplace(name, GetDeviceMapping(name));
}
sKernel32Intercept.Init("kernelbase.dll");
sOriginalQueryDosDeviceWFnPtr.Set(sKernel32Intercept, "QueryDosDeviceW",
&QueryDosDeviceWHook);
}
#endif
HostFile::HostFile(HostFile&& aOther)
: mPath(aOther.mPath), mFile(aOther.TakePlatformFile()) {}
HostFile::~HostFile() {
if (mFile != cdm::kInvalidPlatformFile) {
#ifdef XP_WIN
CloseHandle(mFile);
#else
close(mFile);
#endif
mFile = cdm::kInvalidPlatformFile;
}
}
#ifdef XP_WIN
HostFile::HostFile(const nsCString& aPath)
: mPath(NS_ConvertUTF8toUTF16(aPath)) {
HANDLE handle =
CreateFileW(mPath.get(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
mFile = (handle == INVALID_HANDLE_VALUE) ? cdm::kInvalidPlatformFile : handle;
}
#endif
#ifndef XP_WIN
HostFile::HostFile(const nsCString& aPath) : mPath(aPath) {
// Note: open() returns -1 on failure; i.e. kInvalidPlatformFile.
mFile = open(aPath.get(), O_RDONLY);
}
#endif
cdm::PlatformFile HostFile::TakePlatformFile() {
cdm::PlatformFile f = mFile;
mFile = cdm::kInvalidPlatformFile;
return f;
}
void ChromiumCDMAdapter::PopulateHostFiles(
nsTArray<std::pair<nsCString, nsCString>>&& aHostPathPairs) {
for (const auto& pair : aHostPathPairs) {
mHostFiles.AppendElement(HostFileData(mozilla::HostFile(pair.first),
mozilla::HostFile(pair.second)));
}
}
} // namespace mozilla