1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118

media / base / byte_queue.cc [blame]

// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "media/base/byte_queue.h"

#include <algorithm>
#include <cstring>

#include "base/check_op.h"
#include "base/numerics/checked_math.h"
#include "base/process/memory.h"

namespace media {

ByteQueue::ByteQueue() {
  uint8_t* new_buffer = nullptr;

  // Though ::Push() is allowed to fail memory allocation for `buffer_`, do not
  // allow memory allocation failure here during ByteQueue construction.
  // TODO(crbug.com/40204179): Consider refactoring to an Initialize() method
  // that does this allocation and that can indicate failure, so callers can
  // more gracefully handle the former OOM case that now fails this CHECK. For
  // example, some StreamParsers create additional ByteQueues during Parse, so
  // such handling could be a parse error in that case. Other handling
  // customization could be done where ByteQueues are created as part of
  // StreamParser creation.
  CHECK(base::UncheckedMalloc(size_, reinterpret_cast<void**>(&new_buffer)) &&
        new_buffer);
  buffer_.reset(new_buffer);
}

ByteQueue::~ByteQueue() = default;

void ByteQueue::Reset() {
  offset_ = 0;
  used_ = 0;
}

bool ByteQueue::Push(base::span<const uint8_t> data) {
  DCHECK(!data.empty());

  // This can never overflow since used and size are both ints.
  const size_t size_needed = static_cast<size_t>(used_) + data.size();

  // Check to see if we need a bigger buffer.
  if (size_needed > size_) {
    // Growth is based on base::circular_deque which grows at 25%.
    const size_t safe_size =
        (base::CheckedNumeric<size_t>(size_) + size_ / 4).ValueOrDie();
    const size_t new_size = std::max(size_needed, safe_size);

    // Try to obtain a new backing buffer of `new_size` capacity. Note: If
    // `used_` is positive, we could use realloc() here, but would need an
    // additional move to pack data at offset_ = 0 after a potential internal
    // new allocation + copy by realloc(). In local tests on a few top video
    // sites that ends up being the common case, so just prefer to copy and pack
    // ourselves. Further, we need to handle potential allocation failure, since
    // callers may have fallback paths for that scenario, and the allocation
    // path allowing this must not be used with realloc.
    uint8_t* new_buffer = nullptr;
    if (!base::UncheckedMalloc(new_size,
                               reinterpret_cast<void**>(&new_buffer)) ||
        !new_buffer) {
      return false;
    }

    // Note that the new array is purposely not initialized. Copy the data, if
    // any, from the old buffer to the start of the new one.
    if (used_ > 0) {
      memcpy(new_buffer, Front(), used_);
    }

    buffer_.reset(new_buffer);  // This also frees the previous `buffer_`.
    size_ = new_size;
    offset_ = 0;
  } else if ((offset_ + used_ + data.size()) > size_) {
    // The buffer is big enough, but we need to move the data in the queue.
    memmove(buffer_.get(), Front(), used_);
    offset_ = 0;
  }

  memcpy(Front() + used_, data.data(), data.size());
  used_ += data.size();

  return true;
}

void ByteQueue::Peek(const uint8_t** data, int* size) const {
  DCHECK(data);
  DCHECK(size);
  *data = Front();
  *size = used_;
}

void ByteQueue::Pop(int count) {
  DCHECK_LE(count, used_);

  offset_ += count;
  used_ -= count;

  // Move the offset back to 0 if we have reached the end of the buffer.
  if (offset_ == size_) {
    DCHECK_EQ(used_, 0);
    offset_ = 0;
  }
}

uint8_t* ByteQueue::Front() const {
  return buffer_.get() + offset_;
}

}  // namespace media