ChromiumCDMAdapter.cpp (10316B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "ChromiumCDMAdapter.h" 7 8 #include <utility> 9 10 #include "GMPLog.h" 11 #include "WidevineUtils.h" 12 #include "content_decryption_module.h" 13 #include "content_decryption_module_ext.h" 14 #include "gmp-api/gmp-entrypoints.h" 15 #include "gmp-api/gmp-video-codec.h" 16 #include "mozilla/HelperMacros.h" 17 #include "mozilla/PodOperations.h" 18 #include "mozilla/dom/KeySystemNames.h" 19 20 #ifdef XP_WIN 21 # include <strsafe.h> 22 # include <windows.h> 23 24 # include <unordered_map> 25 # include <vector> 26 27 # include "WinUtils.h" 28 # include "nsWindowsDllInterceptor.h" 29 #else 30 # include <fcntl.h> 31 # include <sys/stat.h> 32 # include <sys/types.h> 33 # include <unistd.h> 34 #endif 35 36 const GMPPlatformAPI* sPlatform = nullptr; 37 38 namespace mozilla { 39 40 #ifdef XP_WIN 41 static void InitializeHooks(); 42 #endif 43 44 ChromiumCDMAdapter::ChromiumCDMAdapter( 45 nsTArray<std::pair<nsCString, nsCString>>&& aHostPathPairs) { 46 #ifdef XP_WIN 47 InitializeHooks(); 48 #endif 49 PopulateHostFiles(std::move(aHostPathPairs)); 50 } 51 52 void ChromiumCDMAdapter::SetAdaptee(PRLibrary* aLib) { mLib = aLib; } 53 54 void* ChromiumCdmHost(int aHostInterfaceVersion, void* aUserData) { 55 GMP_LOG_DEBUG("ChromiumCdmHostFunc(%d, %p)", aHostInterfaceVersion, 56 aUserData); 57 if (aHostInterfaceVersion != cdm::Host_11::kVersion) { 58 return nullptr; 59 } 60 return aUserData; 61 } 62 63 void* ChromiumCdmHostCompat(int aHostInterfaceVersion, void* aUserData) { 64 GMP_LOG_DEBUG("ChromiumCdmHostCompatFunc(%d, %p)", aHostInterfaceVersion, 65 aUserData); 66 if (aHostInterfaceVersion != cdm::Host_10::kVersion) { 67 return nullptr; 68 } 69 return aUserData; 70 } 71 72 #ifdef MOZILLA_OFFICIAL 73 static cdm::HostFile TakeToCDMHostFile(HostFileData& aHostFileData) { 74 return cdm::HostFile(aHostFileData.mBinary.Path().get(), 75 aHostFileData.mBinary.TakePlatformFile(), 76 aHostFileData.mSig.TakePlatformFile()); 77 } 78 #endif 79 80 GMPErr ChromiumCDMAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI) { 81 GMP_LOG_DEBUG("ChromiumCDMAdapter::GMPInit"); 82 sPlatform = aPlatformAPI; 83 if (NS_WARN_IF(!mLib)) { 84 MOZ_CRASH("Missing library!"); 85 return GMPGenericErr; 86 } 87 88 #ifdef MOZILLA_OFFICIAL 89 // Note: we must call the VerifyCdmHost_0 function if it's present before 90 // we call the initialize function. 91 auto verify = reinterpret_cast<decltype(::VerifyCdmHost_0)*>( 92 PR_FindFunctionSymbol(mLib, MOZ_STRINGIFY(VerifyCdmHost_0))); 93 if (verify) { 94 nsTArray<cdm::HostFile> files; 95 for (HostFileData& hostFile : mHostFiles) { 96 files.AppendElement(TakeToCDMHostFile(hostFile)); 97 } 98 bool result = verify(files.Elements(), files.Length()); 99 GMP_LOG_DEBUG("%s VerifyCdmHost_0 returned %d", __func__, result); 100 MOZ_DIAGNOSTIC_ASSERT(result, "Verification failed!"); 101 } 102 #endif 103 104 auto init = reinterpret_cast<decltype(::INITIALIZE_CDM_MODULE)*>( 105 PR_FindFunctionSymbol(mLib, MOZ_STRINGIFY(INITIALIZE_CDM_MODULE))); 106 if (!init) { 107 MOZ_CRASH("Missing init method!"); 108 return GMPGenericErr; 109 } 110 111 GMP_LOG_DEBUG(MOZ_STRINGIFY(INITIALIZE_CDM_MODULE) "()"); 112 init(); 113 114 return GMPNoErr; 115 } 116 117 GMPErr ChromiumCDMAdapter::GMPGetAPI(const char* aAPIName, void* aHostAPI, 118 void** aPluginAPI, 119 const nsACString& aKeySystem) { 120 MOZ_ASSERT( 121 aKeySystem.EqualsLiteral(kWidevineKeySystemName) || 122 aKeySystem.EqualsLiteral(kClearKeyKeySystemName) || 123 aKeySystem.EqualsLiteral(kClearKeyWithProtectionQueryKeySystemName) || 124 aKeySystem.EqualsLiteral("fake"), 125 "Should not get an unrecognized key system. Why didn't it get " 126 "blocked by MediaKeySystemAccess?"); 127 GMP_LOG_DEBUG("ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %s) this=0x%p", 128 aAPIName, aHostAPI, aPluginAPI, 129 PromiseFlatCString(aKeySystem).get(), this); 130 131 int version; 132 GetCdmHostFunc getCdmHostFunc; 133 if (!strcmp(aAPIName, CHROMIUM_CDM_API)) { 134 version = cdm::ContentDecryptionModule_11::kVersion; 135 getCdmHostFunc = &ChromiumCdmHost; 136 } else if (!strcmp(aAPIName, CHROMIUM_CDM_API_BACKWARD_COMPAT)) { 137 version = cdm::ContentDecryptionModule_10::kVersion; 138 getCdmHostFunc = &ChromiumCdmHostCompat; 139 } else { 140 MOZ_ASSERT_UNREACHABLE("We only support and expect cdm10/11!"); 141 GMP_LOG_DEBUG( 142 "ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p got " 143 "unsupported CDM version!", 144 aAPIName, aHostAPI, aPluginAPI, this); 145 return GMPGenericErr; 146 } 147 148 auto create = reinterpret_cast<decltype(::CreateCdmInstance)*>( 149 PR_FindFunctionSymbol(mLib, "CreateCdmInstance")); 150 if (!create) { 151 GMP_LOG_DEBUG( 152 "ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p " 153 "FAILED to find CreateCdmInstance", 154 aAPIName, aHostAPI, aPluginAPI, this); 155 return GMPGenericErr; 156 } 157 158 void* cdm = create(version, aKeySystem.BeginReading(), aKeySystem.Length(), 159 getCdmHostFunc, aHostAPI); 160 if (!cdm) { 161 GMP_LOG_DEBUG( 162 "ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p " 163 "FAILED to create cdm version %d", 164 aAPIName, aHostAPI, aPluginAPI, this, version); 165 return GMPGenericErr; 166 } 167 GMP_LOG_DEBUG("cdm: 0x%p, version: %d", cdm, version); 168 *aPluginAPI = cdm; 169 170 return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr; 171 } 172 173 void ChromiumCDMAdapter::GMPShutdown() { 174 GMP_LOG_DEBUG("ChromiumCDMAdapter::GMPShutdown()"); 175 176 decltype(::DeinitializeCdmModule)* deinit; 177 deinit = 178 (decltype(deinit))(PR_FindFunctionSymbol(mLib, "DeinitializeCdmModule")); 179 if (deinit) { 180 GMP_LOG_DEBUG("DeinitializeCdmModule()"); 181 deinit(); 182 } 183 } 184 185 /* static */ 186 bool ChromiumCDMAdapter::Supports(int32_t aModuleVersion, 187 int32_t aInterfaceVersion, 188 int32_t aHostVersion) { 189 return aModuleVersion == CDM_MODULE_VERSION && 190 ((aInterfaceVersion == cdm::ContentDecryptionModule_11::kVersion && 191 aHostVersion == cdm::Host_11::kVersion) || 192 (aInterfaceVersion == cdm::ContentDecryptionModule_10::kVersion && 193 aHostVersion == cdm::Host_10::kVersion)); 194 } 195 196 #ifdef XP_WIN 197 198 MOZ_RUNINIT static WindowsDllInterceptor sKernel32Intercept; 199 200 typedef DWORD(WINAPI* QueryDosDeviceWFnPtr)(_In_opt_ LPCWSTR lpDeviceName, 201 _Out_ LPWSTR lpTargetPath, 202 _In_ DWORD ucchMax); 203 204 static WindowsDllInterceptor::FuncHookType<QueryDosDeviceWFnPtr> 205 sOriginalQueryDosDeviceWFnPtr; 206 207 static std::unordered_map<std::wstring, std::wstring>* sDeviceNames = nullptr; 208 209 DWORD WINAPI QueryDosDeviceWHook(LPCWSTR lpDeviceName, LPWSTR lpTargetPath, 210 DWORD ucchMax) { 211 if (!sDeviceNames) { 212 return 0; 213 } 214 std::wstring name = std::wstring(lpDeviceName); 215 auto iter = sDeviceNames->find(name); 216 if (iter == sDeviceNames->end()) { 217 return 0; 218 } 219 const std::wstring& device = iter->second; 220 if (device.size() + 1 > ucchMax) { 221 return 0; 222 } 223 PodCopy(lpTargetPath, device.c_str(), device.size()); 224 lpTargetPath[device.size()] = 0; 225 GMP_LOG_DEBUG("QueryDosDeviceWHook %S -> %S", lpDeviceName, lpTargetPath); 226 return device.size(); 227 } 228 229 static std::vector<std::wstring> GetDosDeviceNames() { 230 std::vector<std::wstring> v; 231 std::vector<wchar_t> buf; 232 buf.resize(1024); 233 DWORD rv = GetLogicalDriveStringsW(buf.size(), buf.data()); 234 if (rv == 0 || rv > buf.size()) { 235 return v; 236 } 237 238 // buf will be a list of null terminated strings, with the last string 239 // being 0 length. 240 const wchar_t* p = buf.data(); 241 const wchar_t* end = &buf.back(); 242 size_t l; 243 while (p < end && (l = wcsnlen_s(p, end - p)) > 0) { 244 // The string is of the form "C:\". We need to strip off the trailing 245 // backslash. 246 std::wstring drive = std::wstring(p, p + l); 247 if (drive.back() == '\\') { 248 drive.erase(drive.end() - 1); 249 } 250 v.push_back(std::move(drive)); 251 p += l + 1; 252 } 253 return v; 254 } 255 256 static std::wstring GetDeviceMapping(const std::wstring& aDosDeviceName) { 257 wchar_t buf[MAX_PATH] = {0}; 258 DWORD rv = QueryDosDeviceW(aDosDeviceName.c_str(), buf, MAX_PATH); 259 if (rv == 0) { 260 return std::wstring(L""); 261 } 262 return std::wstring(buf, buf + rv); 263 } 264 265 static void InitializeHooks() { 266 static bool initialized = false; 267 if (initialized) { 268 return; 269 } 270 initialized = true; 271 sDeviceNames = new std::unordered_map<std::wstring, std::wstring>(); 272 for (const std::wstring& name : GetDosDeviceNames()) { 273 sDeviceNames->emplace(name, GetDeviceMapping(name)); 274 } 275 276 sKernel32Intercept.Init("kernelbase.dll"); 277 sOriginalQueryDosDeviceWFnPtr.Set(sKernel32Intercept, "QueryDosDeviceW", 278 &QueryDosDeviceWHook); 279 } 280 #endif 281 282 HostFile::HostFile(HostFile&& aOther) 283 : mPath(aOther.mPath), mFile(aOther.TakePlatformFile()) {} 284 285 HostFile::~HostFile() { 286 if (mFile != cdm::kInvalidPlatformFile) { 287 #ifdef XP_WIN 288 CloseHandle(mFile); 289 #else 290 close(mFile); 291 #endif 292 mFile = cdm::kInvalidPlatformFile; 293 } 294 } 295 296 #ifdef XP_WIN 297 HostFile::HostFile(const nsCString& aPath) 298 : mPath(NS_ConvertUTF8toUTF16(aPath)) { 299 HANDLE handle = 300 CreateFileW(mPath.get(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 301 NULL, OPEN_EXISTING, 0, NULL); 302 mFile = (handle == INVALID_HANDLE_VALUE) ? cdm::kInvalidPlatformFile : handle; 303 } 304 #endif 305 306 #ifndef XP_WIN 307 HostFile::HostFile(const nsCString& aPath) : mPath(aPath) { 308 // Note: open() returns -1 on failure; i.e. kInvalidPlatformFile. 309 mFile = open(aPath.get(), O_RDONLY); 310 } 311 #endif 312 313 cdm::PlatformFile HostFile::TakePlatformFile() { 314 cdm::PlatformFile f = mFile; 315 mFile = cdm::kInvalidPlatformFile; 316 return f; 317 } 318 319 void ChromiumCDMAdapter::PopulateHostFiles( 320 nsTArray<std::pair<nsCString, nsCString>>&& aHostPathPairs) { 321 for (const auto& pair : aHostPathPairs) { 322 mHostFiles.AppendElement(HostFileData(mozilla::HostFile(pair.first), 323 mozilla::HostFile(pair.second))); 324 } 325 } 326 327 } // namespace mozilla