tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

GMPUtils.cpp (11181B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "GMPUtils.h"
      8 
      9 #include "GMPLog.h"
     10 #include "GMPService.h"
     11 #include "VideoLimits.h"
     12 #include "gmp-video-frame-encoded.h"
     13 #include "mozIGeckoMediaPluginService.h"
     14 #include "mozilla/Base64.h"
     15 #include "mozilla/EndianUtils.h"
     16 #include "nsCOMPtr.h"
     17 #include "nsCRTGlue.h"
     18 #include "nsDirectoryServiceDefs.h"
     19 #include "nsIConsoleService.h"
     20 #include "nsIFile.h"
     21 #include "nsLiteralString.h"
     22 #include "prio.h"
     23 
     24 namespace mozilla {
     25 
     26 void SplitAt(const char* aDelims, const nsACString& aInput,
     27             nsTArray<nsCString>& aOutTokens) {
     28  nsAutoCString str(aInput);
     29  char* end = str.BeginWriting();
     30  const char* start = nullptr;
     31  while (!!(start = NS_strtok(aDelims, &end))) {
     32    aOutTokens.AppendElement(nsCString(start));
     33  }
     34 }
     35 
     36 nsCString ToHexString(const uint8_t* aBytes, uint32_t aLength) {
     37  static const char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7',
     38                             '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
     39  nsCString str;
     40  for (uint32_t i = 0; i < aLength; i++) {
     41    char buf[3];
     42    buf[0] = hex[(aBytes[i] & 0xf0) >> 4];
     43    buf[1] = hex[aBytes[i] & 0x0f];
     44    buf[2] = 0;
     45    str.AppendASCII(buf);
     46  }
     47  return str;
     48 }
     49 
     50 nsCString ToHexString(const nsTArray<uint8_t>& aBytes) {
     51  return ToHexString(aBytes.Elements(), aBytes.Length());
     52 }
     53 
     54 bool FileExists(nsIFile* aFile) {
     55  bool exists = false;
     56  return aFile && NS_SUCCEEDED(aFile->Exists(&exists)) && exists;
     57 }
     58 
     59 DirectoryEnumerator::DirectoryEnumerator(nsIFile* aPath, Mode aMode)
     60    : mMode(aMode) {
     61  aPath->GetDirectoryEntries(getter_AddRefs(mIter));
     62 }
     63 
     64 already_AddRefed<nsIFile> DirectoryEnumerator::Next() {
     65  if (!mIter) {
     66    return nullptr;
     67  }
     68  bool hasMore = false;
     69  while (NS_SUCCEEDED(mIter->HasMoreElements(&hasMore)) && hasMore) {
     70    nsCOMPtr<nsISupports> supports;
     71    nsresult rv = mIter->GetNext(getter_AddRefs(supports));
     72    if (NS_FAILED(rv)) {
     73      continue;
     74    }
     75 
     76    nsCOMPtr<nsIFile> path(do_QueryInterface(supports, &rv));
     77    if (NS_FAILED(rv)) {
     78      continue;
     79    }
     80 
     81    if (mMode == DirsOnly) {
     82      bool isDirectory = false;
     83      rv = path->IsDirectory(&isDirectory);
     84      if (NS_FAILED(rv) || !isDirectory) {
     85        continue;
     86      }
     87    }
     88    return path.forget();
     89  }
     90  return nullptr;
     91 }
     92 
     93 bool ReadIntoArray(nsIFile* aFile, nsTArray<uint8_t>& aOutDst,
     94                   size_t aMaxLength) {
     95  if (!FileExists(aFile)) {
     96    return false;
     97  }
     98 
     99  PRFileDesc* fd = nullptr;
    100  nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
    101  if (NS_WARN_IF(NS_FAILED(rv))) {
    102    return false;
    103  }
    104 
    105  int32_t length = PR_Seek(fd, 0, PR_SEEK_END);
    106  PR_Seek(fd, 0, PR_SEEK_SET);
    107 
    108  if (length < 0 || (size_t)length > aMaxLength) {
    109    NS_WARNING("EME file is longer than maximum allowed length");
    110    PR_Close(fd);
    111    return false;
    112  }
    113  aOutDst.SetLength(length);
    114  int32_t bytesRead = PR_Read(fd, aOutDst.Elements(), length);
    115  PR_Close(fd);
    116  return (bytesRead == length);
    117 }
    118 
    119 bool ReadIntoString(nsIFile* aFile, nsCString& aOutDst, size_t aMaxLength) {
    120  nsTArray<uint8_t> buf;
    121  bool rv = ReadIntoArray(aFile, buf, aMaxLength);
    122  if (rv) {
    123    buf.AppendElement(0);  // Append null terminator, required by nsC*String.
    124    aOutDst = nsDependentCString((const char*)buf.Elements(), buf.Length() - 1);
    125  }
    126  return rv;
    127 }
    128 
    129 bool GMPInfoFileParser::Init(nsIFile* aInfoFile) {
    130  nsTArray<nsCString> lines;
    131  static const size_t MAX_GMP_INFO_FILE_LENGTH = 5 * 1024;
    132 
    133  nsAutoCString info;
    134  if (!ReadIntoString(aInfoFile, info, MAX_GMP_INFO_FILE_LENGTH)) {
    135    NS_WARNING("Failed to read info file in GMP process.");
    136    return false;
    137  }
    138 
    139  // Note: we pass "\r\n" to SplitAt so that we'll split lines delimited
    140  // by \n (Unix), \r\n (Windows) and \r (old MacOSX).
    141  SplitAt("\r\n", info, lines);
    142 
    143  for (nsCString line : lines) {
    144    // Field name is the string up to but not including the first ':'
    145    // character on the line.
    146    int32_t colon = line.FindChar(':');
    147    if (colon <= 0) {
    148      // Not allowed to be the first character.
    149      // Info field name must be at least one character.
    150      continue;
    151    }
    152    nsAutoCString key(Substring(line, 0, colon));
    153    ToLowerCase(key);
    154    key.Trim(" ");
    155 
    156    auto value = MakeUnique<nsCString>(Substring(line, colon + 1));
    157    value->Trim(" ");
    158    mValues.InsertOrUpdate(
    159        key,
    160        std::move(value));  // Hashtable assumes ownership of value.
    161  }
    162 
    163  return true;
    164 }
    165 
    166 bool GMPInfoFileParser::Contains(const nsCString& aKey) const {
    167  nsCString key(aKey);
    168  ToLowerCase(key);
    169  return mValues.Contains(key);
    170 }
    171 
    172 nsCString GMPInfoFileParser::Get(const nsCString& aKey) const {
    173  MOZ_ASSERT(Contains(aKey));
    174  nsCString key(aKey);
    175  ToLowerCase(key);
    176  nsCString* p = nullptr;
    177  if (mValues.Get(key, &p)) {
    178    return nsCString(*p);
    179  }
    180  return ""_ns;
    181 }
    182 
    183 bool HaveGMPFor(const nsACString& aAPI, const nsTArray<nsCString>& aTags) {
    184  nsCOMPtr<mozIGeckoMediaPluginService> mps =
    185      do_GetService("@mozilla.org/gecko-media-plugin-service;1");
    186  if (NS_WARN_IF(!mps)) {
    187    return false;
    188  }
    189 
    190  bool hasPlugin = false;
    191  if (NS_FAILED(mps->HasPluginForAPI(aAPI, aTags, &hasPlugin))) {
    192    return false;
    193  }
    194  return hasPlugin;
    195 }
    196 
    197 bool IsOnGMPThread() {
    198  nsCOMPtr<mozIGeckoMediaPluginService> mps =
    199      do_GetService("@mozilla.org/gecko-media-plugin-service;1");
    200  MOZ_ASSERT(mps);
    201 
    202  nsCOMPtr<nsIThread> gmpThread;
    203  nsresult rv = mps->GetThread(getter_AddRefs(gmpThread));
    204  MOZ_ASSERT(gmpThread);
    205  return NS_SUCCEEDED(rv) && gmpThread && gmpThread->IsOnCurrentThread();
    206 }
    207 
    208 void LogToConsole(const nsAString& aMsg) {
    209  nsCOMPtr<nsIConsoleService> console(
    210      do_GetService("@mozilla.org/consoleservice;1"));
    211  if (!console) {
    212    NS_WARNING("Failed to log message to console.");
    213    return;
    214  }
    215  nsAutoString msg(aMsg);
    216  console->LogStringMessage(msg.get());
    217 }
    218 
    219 already_AddRefed<nsISerialEventTarget> GetGMPThread() {
    220  RefPtr<gmp::GeckoMediaPluginService> service =
    221      gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
    222  nsCOMPtr<nsISerialEventTarget> thread =
    223      service ? service->GetGMPThread() : nullptr;
    224  return thread.forget();
    225 }
    226 
    227 static size_t Align16(size_t aNumber) {
    228  const size_t mask = 15;  // Alignment - 1.
    229  return (aNumber + mask) & ~mask;
    230 }
    231 
    232 size_t I420FrameBufferSizePadded(int32_t aWidth, int32_t aHeight) {
    233  if (aWidth <= 0 || aHeight <= 0 || aWidth > MAX_VIDEO_WIDTH ||
    234      aHeight > MAX_VIDEO_HEIGHT) {
    235    return 0;
    236  }
    237 
    238  size_t ySize = Align16(aWidth) * Align16(aHeight);
    239  return ySize + (ySize / 4) * 2;
    240 }
    241 
    242 static int SizeNumBytes(GMPBufferType aBufferType) {
    243  switch (aBufferType) {
    244    case GMP_BufferSingle:
    245      return 0;
    246    case GMP_BufferLength8:
    247      return 1;
    248    case GMP_BufferLength16:
    249      return 2;
    250    case GMP_BufferLength24:
    251      return 3;
    252    case GMP_BufferLength32:
    253      return 4;
    254    default:
    255      MOZ_CRASH("Unexpected buffer type");
    256  }
    257 }
    258 
    259 bool AdjustOpenH264NALUSequence(GMPVideoEncodedFrame* aEncodedFrame) {
    260  MOZ_ASSERT(aEncodedFrame);
    261  MOZ_ASSERT(IsOnGMPThread());
    262 
    263  uint8_t* encodedBuffer = aEncodedFrame->Buffer();
    264  uint32_t encodedSize = aEncodedFrame->Size();
    265  GMPBufferType encodedType = aEncodedFrame->BufferType();
    266 
    267  if (NS_WARN_IF(!encodedBuffer)) {
    268    GMP_LOG_ERROR("GMP plugin returned null buffer");
    269    return false;
    270  }
    271 
    272  // Libwebrtc's RtpPacketizerH264 expects a 3- or 4-byte NALU start sequence
    273  // before the start of the NALU payload. {0,0,1} or {0,0,0,1}. We set this
    274  // in-place. Any other length of the length field we reject.
    275 
    276  const int sizeNumBytes = SizeNumBytes(encodedType);
    277  uint32_t unitOffset = 0;
    278  uint32_t unitSize = 0;
    279  // Make sure we don't read past the end of the buffer getting the size
    280  while (unitOffset + sizeNumBytes < encodedSize) {
    281    uint8_t* unitBuffer = encodedBuffer + unitOffset;
    282    switch (encodedType) {
    283      case GMP_BufferLength24: {
    284 #if MOZ_LITTLE_ENDIAN()
    285        unitSize = (static_cast<uint32_t>(*unitBuffer)) |
    286                   (static_cast<uint32_t>(*(unitBuffer + 1)) << 8) |
    287                   (static_cast<uint32_t>(*(unitBuffer + 2)) << 16);
    288 #else
    289        unitSize = (static_cast<uint32_t>(*unitBuffer) << 16) |
    290                   (static_cast<uint32_t>(*(unitBuffer + 1)) << 8) |
    291                   (static_cast<uint32_t>(*(unitBuffer + 2)));
    292 #endif
    293        const uint8_t startSequence[] = {0, 0, 1};
    294        if (memcmp(unitBuffer, startSequence, 3) == 0) {
    295          // This is a bug in OpenH264 where it misses to convert the NALU start
    296          // sequence to the NALU size per the GMP protocol. We mitigate this by
    297          // letting it through as this is what libwebrtc already expects and
    298          // scans for.
    299          unitSize = encodedSize - 3;
    300          break;
    301        }
    302        memcpy(unitBuffer, startSequence, 3);
    303        break;
    304      }
    305      case GMP_BufferLength32: {
    306 #if MOZ_LITTLE_ENDIAN()
    307        unitSize = LittleEndian::readUint32(unitBuffer);
    308 #else
    309        unitSize = BigEndian::readUint32(unitBuffer);
    310 #endif
    311        const uint8_t startSequence[] = {0, 0, 0, 1};
    312        if (memcmp(unitBuffer, startSequence, 4) == 0) {
    313          // This is a bug in OpenH264 where it misses to convert the NALU start
    314          // sequence to the NALU size per the GMP protocol. We mitigate this by
    315          // letting it through as this is what libwebrtc already expects and
    316          // scans for.
    317          unitSize = encodedSize - 4;
    318          break;
    319        }
    320        memcpy(unitBuffer, startSequence, 4);
    321        break;
    322      }
    323      default:
    324        GMP_LOG_ERROR("GMP plugin returned type we cannot handle (%d)",
    325                      encodedType);
    326        return false;
    327    }
    328 
    329    MOZ_ASSERT(unitSize != 0);
    330    MOZ_ASSERT(unitOffset + sizeNumBytes + unitSize <= encodedSize);
    331    if (unitSize == 0 || unitOffset + sizeNumBytes + unitSize > encodedSize) {
    332      // XXX Should we kill the plugin for returning extra bytes? Probably
    333      GMP_LOG_ERROR(
    334          "GMP plugin returned badly formatted encoded data: "
    335          "unitOffset=%u, sizeNumBytes=%d, unitSize=%u, size=%u",
    336          unitOffset, sizeNumBytes, unitSize, encodedSize);
    337      return false;
    338    }
    339 
    340    unitOffset += sizeNumBytes + unitSize;
    341  }
    342 
    343  if (unitOffset != encodedSize) {
    344    // At most 3 bytes can be left over, depending on buffertype
    345    GMP_LOG_DEBUG("GMP plugin returned %u extra bytes",
    346                  encodedSize - unitOffset);
    347  }
    348 
    349  return true;
    350 }
    351 
    352 MediaResult ToMediaResult(GMPErr aErr, const nsACString& aMessage) {
    353  nsPrintfCString msg("%s (GMPErr:%x)", aMessage.Data(), aErr);
    354  switch (aErr) {
    355    case GMPDecodeErr:
    356      return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, msg);
    357    case GMPNotImplementedErr:
    358      return MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, msg);
    359    case GMPAbortedErr:
    360      return MediaResult(NS_ERROR_DOM_MEDIA_ABORT_ERR, msg);
    361    default:
    362      return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, msg);
    363  }
    364 }
    365 
    366 }  // namespace mozilla