packages feed

folly-clib-20250713.1537: folly/folly/io/Cursor.h

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <cassert>
#include <cstdarg>
#include <cstdint>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <type_traits>

#include <folly/Likely.h>
#include <folly/Memory.h>
#include <folly/Portability.h>
#include <folly/Range.h>
#include <folly/container/span.h>
#include <folly/io/IOBuf.h>
#include <folly/io/IOBufQueue.h>
#include <folly/lang/Bits.h>
#include <folly/lang/Exception.h>

/**
 * IOBuf Cursors provide fast iteration over IOBuf chains.
 *
 * - Cursor          - Read-only access
 * - RWPrivateCursor - Read-write access, assumes private access to IOBuf chain
 * - RWUnshareCursor - Read-write access, calls unshare on write (COW)
 * - Appender        - Write access, assumes private access to IOBuf chain
 *
 * Note that RW cursors write in the preallocated part of buffers (that is,
 * between the buffer's data() and tail()), while Appenders append to the end
 * of the buffer (between the buffer's tail() and bufferEnd()).  Appenders
 * automatically adjust the buffer pointers, so you may only use one
 * Appender with a buffer chain; for this reason, Appenders assume private
 * access to the buffer (you need to call unshare() yourself if necessary).
 *
 * @file Cursor.h
 */

namespace folly {
namespace io {

class Cursor;
class ThinCursor;

// This is very useful in development, but the size perturbation is currently
// causing some previously undetected bugs in unrelated projects to manifest in
// CI-breaking ways.
// TODO(davidgoldblatt): Fix this.
#define FOLLY_IO_CURSOR_BORROW_CHECKING 0
#if FOLLY_IO_CURSOR_BORROW_CHECKING
#define FOLLY_IO_CURSOR_BORROW_DCHECK DCHECK
#else
#define FOLLY_IO_CURSOR_BORROW_DCHECK(ignored)
#endif

template <class Derived, class BufType>
class CursorBase {
  // Make all the templated classes friends for copy constructor.
  template <class D, typename B>
  friend class CursorBase;

 protected:
  /**
   * Construct a cursor wrapping an IOBuf.
   */
  explicit CursorBase(BufType* buf) noexcept : crtBuf_(buf), buffer_(buf) {
    if (crtBuf_) {
      crtPos_ = crtBegin_ = crtBuf_->data();
      crtEnd_ = crtBuf_->tail();
    }
  }

  /**
   * Constuct a bounded cursor wrapping an IOBuf.
   *
   * @param len An upper bound on the number of bytes available to this cursor.
   */
  CursorBase(BufType* buf, size_t len) noexcept : crtBuf_(buf), buffer_(buf) {
    if (crtBuf_) {
      crtPos_ = crtBegin_ = crtBuf_->data();
      crtEnd_ = crtBuf_->tail();
      if (uintptr_t(crtPos_) + len < uintptr_t(crtEnd_)) {
        crtEnd_ = crtPos_ + len;
      }
      remainingLen_ = len - (crtEnd_ - crtPos_);
    }
  }

  /**
   * Copy constructor.
   *
   * This also allows constructing a CursorBase from other derived types.
   * For instance, this allows constructing a Cursor from an RWPrivateCursor.
   */
  template <class OtherDerived, class OtherBuf>
  explicit CursorBase(const CursorBase<OtherDerived, OtherBuf>& cursor) noexcept
      : crtBuf_(cursor.crtBuf_),
        buffer_(cursor.buffer_),
        crtBegin_(cursor.crtBegin_),
        crtEnd_(cursor.crtEnd_),
        crtPos_(cursor.crtPos_),
        absolutePos_(cursor.absolutePos_),
        remainingLen_(cursor.remainingLen_) {}

  template <class OtherDerived, class OtherBuf>
  explicit CursorBase(
      const CursorBase<OtherDerived, OtherBuf>& cursor, size_t len)
      : crtBuf_(cursor.crtBuf_),
        buffer_(cursor.buffer_),
        crtBegin_(cursor.crtBegin_),
        crtEnd_(cursor.crtEnd_),
        crtPos_(cursor.crtPos_),
        absolutePos_(cursor.absolutePos_) {
    if (cursor.isBounded() && len > cursor.remainingLen_ + cursor.length()) {
      throw_exception<std::out_of_range>("underflow");
    }
    if (uintptr_t(crtPos_) + len < uintptr_t(crtEnd_)) {
      crtEnd_ = crtPos_ + len;
    }
    remainingLen_ = len - (crtEnd_ - crtPos_);
  }

 public:
  /**
   * Reset cursor to point to a new buffer.
   *
   * @methodset Modifiers
   */
  void reset(BufType* buf) noexcept {
    crtBuf_ = buf;
    buffer_ = buf;
    absolutePos_ = 0;
    remainingLen_ = std::numeric_limits<size_t>::max();
    if (crtBuf_) {
      crtPos_ = crtBegin_ = crtBuf_->data();
      crtEnd_ = crtBuf_->tail();
    }
  }

  /**
   * Get the Cursor position relative to the IOBuf chain's currentBuffer().
   *
   * @methodset Advanced
   */
  size_t getPositionInCurrentBuffer() const noexcept {
    dcheckIntegrity();
    return crtPos_ - crtBegin_;
  }

  /**
   * Get the current Cursor position relative to the head of IOBuf chain.
   *
   * @methodset Capacity
   */
  size_t getCurrentPosition() const noexcept {
    return getPositionInCurrentBuffer() + absolutePos_;
  }

  /**
   * Get the data at the current cursor position.
   *
   * @methodset Accessors
   */
  const uint8_t* data() const noexcept {
    dcheckIntegrity();
    return crtPos_;
  }

  /**
   * Get the buffer in the IOBuf chain this Cursor currently points into.
   *
   * @methodset Advanced
   */
  const folly::IOBuf* currentBuffer() const noexcept { return crtBuf_; }

  /**
   * Return the remaining space available in the current IOBuf.
   *
   * @methodset Capacity
   *
   * May return 0 if the cursor is at the end of an IOBuf.  Use peekBytes()
   * instead if you want to avoid this.  peekBytes() will advance to the next
   * non-empty IOBuf (up to the end of the chain) if the cursor is currently
   * pointing at the end of a buffer.
   */
  size_t length() const noexcept {
    dcheckIntegrity();
    return crtEnd_ - crtPos_;
  }

  /**
   * Return the space available until the end of the entire IOBuf chain.
   *
   * @methodset Capacity
   *
   * For bounded Cursors, return the available space until the boundary.
   */
  size_t totalLength() const noexcept {
    size_t len = 0;
    const IOBuf* buf = crtBuf_->next();
    while (buf != buffer_ && len < remainingLen_) {
      len += buf->length();
      buf = buf->next();
    }
    return std::min(len, remainingLen_) + length();
  }

  /**
   * Return true if the cursor could advance the specified number of bytes
   * from its current position.
   *
   * @methodset Capacity
   *
   * This is useful for applications that want to do checked reads instead of
   * catching exceptions and is more efficient than using totalLength as it
   * walks the minimal set of buffers in the chain to determine the result.
   */
  bool canAdvance(size_t amount) const noexcept {
    if (isBounded() && amount > remainingLen_ + length()) {
      return false;
    }
    const IOBuf* nextBuf = crtBuf_;
    size_t available = length();
    do {
      if (available >= amount) {
        return true;
      }
      amount -= available;
      nextBuf = nextBuf->next();
      available = nextBuf->length();
    } while (nextBuf != buffer_);
    return false;
  }

  /**
   * Return true if the cursor is at the end of the entire IOBuf chain.
   *
   * @methodset Capacity
   */
  bool isAtEnd() const noexcept {
    dcheckIntegrity();
    // Check for the simple cases first.
    if (crtPos_ != crtEnd_) {
      return false;
    }
    if (crtBuf_ == buffer_->prev()) {
      return true;
    }
    if (isBounded() && remainingLen_ == 0) {
      return true;
    }
    // We are at the end of a buffer, but it isn't the last buffer.
    // We might still be at the end if the remaining buffers in the chain are
    // empty.
    const IOBuf* buf = crtBuf_->next();
    while (buf != buffer_) {
      if (buf->length() > 0) {
        return false;
      }
      buf = buf->next();
    }
    return true;
  }

  /**
   * Advances the cursor to the end of the entire IOBuf chain.
   *
   * @methodset Modifiers
   */
  void advanceToEnd() {
    // Simple case, we're already in the last IOBuf.
    if (crtBuf_ == buffer_->prev()) {
      crtPos_ = crtEnd_;
      return;
    }

    auto* nextBuf = crtBuf_->next();
    while (nextBuf != buffer_) {
      if (isBounded() && remainingLen_ == 0) {
        crtPos_ = crtEnd_;
        return;
      }
      absolutePos_ += crtEnd_ - crtBegin_;

      crtBuf_ = nextBuf;
      nextBuf = crtBuf_->next();
      crtBegin_ = crtBuf_->data();
      crtEnd_ = crtBuf_->tail();
      if (isBounded()) {
        if (uintptr_t(crtBegin_) + remainingLen_ < uintptr_t(crtEnd_)) {
          crtEnd_ = crtBegin_ + remainingLen_;
        }
        remainingLen_ -= crtEnd_ - crtBegin_;
      }
      crtPos_ = crtEnd_;
      derived().advanceDone();
    }
  }

  /// Advance the cursor
  ///
  /// @methodset Modifiers
  Derived& operator+=(size_t offset) {
    Derived* p = static_cast<Derived*>(this);
    p->skip(offset);
    return *p;
  }
  /// Get a new cursor, advanced by offset from this cursor.
  ///
  /// @methodset Modifiers
  Derived operator+(size_t offset) const {
    Derived other(*this);
    other.skip(offset);
    return other;
  }

  /// Retreat the cursor
  ///
  /// @methodset Modifiers
  Derived& operator-=(size_t offset) {
    Derived* p = static_cast<Derived*>(this);
    p->retreat(offset);
    return *p;
  }
  /// Get a new cursor, retreated by offset from this cursor.
  ///
  /// @methodset Modifiers
  Derived operator-(size_t offset) const {
    Derived other(*this);
    other.retreat(offset);
    return other;
  }

  /**
   * Compare cursors for equality/inequality.
   *
   * @methodset Comparison
   *
   * Two cursors are equal if they are pointing to the same location in the
   * same IOBuf chain.
   */
  bool operator==(const CursorBase& other) const {
    const IOBuf* crtBuf = crtBuf_;
    auto crtPos = crtPos_;
    // We can be pointing to the end of a buffer chunk, find first non-empty.
    while (crtPos == crtBuf->tail() && crtBuf != buffer_->prev()) {
      crtBuf = crtBuf->next();
      crtPos = crtBuf->data();
    }

    const IOBuf* crtBufOther = other.crtBuf_;
    auto crtPosOther = other.crtPos_;
    // We can be pointing to the end of a buffer chunk, find first non-empty.
    while (crtPosOther == crtBufOther->tail() &&
           crtBufOther != other.buffer_->prev()) {
      crtBufOther = crtBufOther->next();
      crtPosOther = crtBufOther->data();
    }
    return (crtPos == crtPosOther) && (crtBuf == crtBufOther);
  }

  /// @copydoc operator==
  bool operator!=(const CursorBase& other) const { return !operator==(other); }

  /**
   * Attempt to read from the cursor.
   *
   * @methodset Consumers
   *
   * @param[out] val  Store the read value in this location.
   * @return True iff successful; If there are not enough bytes left in the
   * cursor, return false.
   * @note val might be modified even if tryRead returns false.
   */
  template <class T>
  typename std::enable_if<std::is_arithmetic<T>::value, bool>::type tryRead(
      T& val) {
    if (FOLLY_LIKELY(uintptr_t(crtPos_) + sizeof(T) <= uintptr_t(crtEnd_))) {
      val = loadUnaligned<T>(data());
      crtPos_ += sizeof(T);
      return true;
    }
    return pullAtMostSlow(&val, sizeof(T)) == sizeof(T);
  }

  /**
   * Attempt to read a Big-Endian integral from the cursor.
   *
   * @methodset Consumers
   *
   * @see tryRead
   */
  template <class T>
  bool tryReadBE(T& val) {
    const bool result = tryRead(val);
    val = Endian::big(val);
    return result;
  }

  /**
   * Attempt to read a Little-Endian integral from the cursor.
   *
   * @methodset Consumers
   *
   * @see tryRead
   */
  template <class T>
  bool tryReadLE(T& val) {
    const bool result = tryRead(val);
    val = Endian::little(val);
    return result;
  }

  /**
   * Read a value from the cursor.
   *
   * @methodset Consumers
   *
   * This function only works with types that are bit-copyable: it calls memcpy
   * to reinterpret bits from the IOBuf as T.
   *
   * @throws out_of_range if there aren't enough bytes left in the cursor.
   */
  template <class T>
  T read() {
    if (FOLLY_LIKELY(uintptr_t(crtPos_) + sizeof(T) <= uintptr_t(crtEnd_))) {
      T val = loadUnaligned<T>(data());
      crtPos_ += sizeof(T);
      return val;
    } else {
      return readSlow<T>();
    }
  }

  /**
   * Read a Big-Endian integral from the cursor.
   *
   * @methodset Consumers
   *
   * @see read
   */
  template <class T>
  T readBE() {
    return Endian::big(read<T>());
  }

  /**
   * Read a Little-Endian integral from the cursor.
   *
   * @methodset Consumers
   *
   * @see read
   */
  template <class T>
  T readLE() {
    return Endian::little(read<T>());
  }

  /**
   * Read a fixed-length string.
   *
   * @methodset Consumers
   *
   * The std::string-based APIs should probably be avoided unless you
   * ultimately want the data to live in an std::string. You're better off
   * using the pull() APIs to copy into a raw buffer otherwise.
   */
  std::string readFixedString(size_t len) {
    std::string str;
    str.reserve(len);
    if (FOLLY_LIKELY(length() >= len)) {
      str.append(reinterpret_cast<const char*>(data()), len);
      crtPos_ += len;
    } else {
      readFixedStringSlow(&str, len);
    }
    return str;
  }

  /**
   * Read a string of bytes until the given terminator character is seen.
   *
   * @methodset Consumers
   *
   * Raises an std::length_error if maxLength bytes have been processed
   * before the terminator is seen.
   *
   * See comments in readFixedString() about when it's appropriate to use this
   * vs. using pull().
   */
  std::string readTerminatedString(
      char termChar = '\0',
      size_t maxLength = std::numeric_limits<size_t>::max());

  /**
   * @overloadbrief Read bytes until the specified predicate returns true.
   *
   * @methodset Consumers
   *
   * The predicate will be called on each byte in turn, until it returns false
   * or until the end of the IOBuf chain is reached.
   *
   * Returns the result as a string.
   */
  template <typename Predicate>
  std::string readWhile(const Predicate& predicate);

  /**
   * This is a more generic version of readWhile(). It takes an arbitrary Output
   * object, and calls Output::append() with each chunk of matching data.
   */
  template <typename Predicate, typename Output>
  void readWhile(const Predicate& predicate, Output& out);

  /**
   * Skip bytes until the specified predicate returns true.
   *
   * @methodset Consumers
   *
   * The predicate will be called on each byte in turn, until it returns false
   * or until the end of the IOBuf chain is reached.
   */
  template <typename Predicate>
  void skipWhile(const Predicate& predicate);

  /**
   * Advance the cursor by at most len bytes.
   *
   * @methodset Modifiers
   */
  size_t skipAtMost(size_t len) {
    dcheckIntegrity();
    if (FOLLY_LIKELY(uintptr_t(crtPos_) + len < uintptr_t(crtEnd_))) {
      crtPos_ += len;
      return len;
    }
    return skipAtMostSlow(len);
  }

  /**
   * Advance the cursor by len bytes.
   *
   * @methodset Modifiers
   *
   * @throws out_of_range if there aren't enough bytes left in the cursor.
   */
  void skip(size_t len) {
    dcheckIntegrity();
    if (FOLLY_LIKELY(uintptr_t(crtPos_) + len < uintptr_t(crtEnd_))) {
      crtPos_ += len;
    } else {
      skipSlow(len);
    }
  }

  /**
   * Skip bytes in the current IOBuf without advancing to the next one.
   *
   * @methodset Modifiers
   *
   * @pre length() >= len
   */
  void skipNoAdvance(size_t len) {
    DCHECK_LE(len, length());
    crtPos_ += len;
  }

  /**
   * Retreat the cursor by at most len bytes.
   *
   * @methodset Modifiers
   */
  size_t retreatAtMost(size_t len) {
    if (len <= getPositionInCurrentBuffer()) {
      crtPos_ -= len;
      return len;
    }
    return retreatAtMostSlow(len);
  }

  /**
   * Retreat the cursor by at most len bytes.
   *
   * @methodset Modifiers
   *
   * @throws out_of_range if the cursor doesn't have enough bytes to retreat.
   */
  void retreat(size_t len) {
    if (len <= getPositionInCurrentBuffer()) {
      crtPos_ -= len;
    } else {
      retreatSlow(len);
    }
  }

  /**
   * Copies at most len bytes from the cursor.
   *
   * @methodset Consumers
   *
   * The cursor will advance by the number of bytes read.
   *
   * @param[out] buf The buffer into which the bytes are copied.
   * @returns The number of bytes copied.
   */
  size_t pullAtMost(void* buf, size_t len) {
    if (FOLLY_UNLIKELY(len == 0)) {
      return 0;
    }
    dcheckIntegrity();
    // Fast path: it all fits in one buffer.
    if (FOLLY_LIKELY(uintptr_t(crtPos_) + len <= uintptr_t(crtEnd_))) {
      memcpy(buf, data(), len);
      crtPos_ += len;
      return len;
    }
    return pullAtMostSlow(buf, len);
  }

  /**
   * Copies len bytes from the cursor.
   *
   * @methodset Consumers
   *
   * The cursor will advance by len.
   *
   * @param[out] buf The buffer into which the bytes are copied.
   * @throw out_of_range if there aren't enough bytes in the cursor.
   */
  void pull(void* buf, size_t len) {
    if (FOLLY_UNLIKELY(len == 0)) {
      return;
    }
    dcheckIntegrity();
    if (FOLLY_LIKELY(uintptr_t(crtPos_) + len <= uintptr_t(crtEnd_))) {
      memcpy(buf, data(), len);
      crtPos_ += len;
    } else {
      pullSlow(buf, len);
    }
  }

  /**
   * Return the available data in the current IOBuf.
   *
   * @methodset Accessors
   *
   * Unlike data(), peekBytes() will advance to the next IOBuf while length()==0
   * (though it can still return an empty range if there are no bytes left to
   * read in the whole IOBuf chain).
   *
   * If you want to gather more data from the chain into a contiguous region
   * (for hopefully zero-copy access), use gather() before peekBytes().
   */
  ByteRange peekBytes() {
    // Ensure that we're pointing to valid data
    size_t available = length();
    if (FOLLY_UNLIKELY(!available)) {
      available = peekBytesSlow();
    }
    return ByteRange{data(), available};
  }

  /**
   * Alternate version of peekBytes() that returns a std::span
   * instead of a ByteRage.
   *
   * @methodset Accessors
   */
  span<uint8_t const> peek() {
    auto bytes = peekBytes();
    return {bytes.data(), bytes.size()};
  }

  /**
   * Clone len bytes from this cursor into an IOBuf.
   *
   * @methodset Accessors
   *
   * @param[out] buf The IOBuf into which to place the cloned data.
   * @throws out_of_range if there aren't enough bytes in this cursor.
   */
  void clone(std::unique_ptr<folly::IOBuf>& buf, size_t len) {
    if (FOLLY_UNLIKELY(cloneAtMost(buf, len) != len)) {
      throw_exception<std::out_of_range>("underflow");
    }
  }

  void clone(folly::IOBuf& buf, size_t len) {
    if (FOLLY_UNLIKELY(cloneAtMost(buf, len) != len)) {
      throw_exception<std::out_of_range>("underflow");
    }
  }

  /**
   * Clone at most len bytes from this cursor into an IOBuf.
   *
   * @methodset Accessors
   *
   * @param[out] buf The IOBuf into which to place the cloned data.
   * @return The number of bytes actually cloned.
   */
  size_t cloneAtMost(folly::IOBuf& buf, size_t len) {
    // We might be at the end of buffer.
    advanceBufferIfEmpty();

    std::unique_ptr<folly::IOBuf> tmp;
    size_t copied = 0;
    for (int loopCount = 0; true; ++loopCount) {
      // Fast path: it all fits in one buffer.
      size_t available = length();
      if (FOLLY_LIKELY(available >= len)) {
        if (loopCount == 0) {
          crtBuf_->cloneOneInto(buf);
          buf.trimStart(crtPos_ - crtBegin_);
          buf.trimEnd(buf.length() - len);
        } else {
          tmp = crtBuf_->cloneOne();
          tmp->trimStart(crtPos_ - crtBegin_);
          tmp->trimEnd(tmp->length() - len);
          buf.prependChain(std::move(tmp));
        }

        crtPos_ += len;
        advanceBufferIfEmpty();
        return copied + len;
      }

      if (loopCount == 0) {
        crtBuf_->cloneOneInto(buf);
        buf.trimStart(crtPos_ - crtBegin_);
      } else {
        tmp = crtBuf_->cloneOne();
        tmp->trimStart(crtPos_ - crtBegin_);
        buf.prependChain(std::move(tmp));
      }

      copied += available;
      if (FOLLY_UNLIKELY(!tryAdvanceBuffer())) {
        return copied;
      }
      len -= available;
    }
  }

  size_t cloneAtMost(std::unique_ptr<folly::IOBuf>& buf, size_t len) {
    if (!buf) {
      buf = std::make_unique<folly::IOBuf>();
    }
    return cloneAtMost(*buf, len);
  }

  /**
   * Return the distance between two cursors.
   */
  size_t operator-(const CursorBase& other) const {
    BufType* otherBuf = other.crtBuf_;
    size_t len = 0;

    if (otherBuf != crtBuf_) {
      if (other.remainingLen_ == 0) {
        len += otherBuf->tail() - other.crtPos_;
      } else {
        len += other.crtEnd_ - other.crtPos_;
      }

      for (otherBuf = otherBuf->next();
           otherBuf != crtBuf_ && otherBuf != other.buffer_;
           otherBuf = otherBuf->next()) {
        len += otherBuf->length();
      }

      if (otherBuf == other.buffer_) {
        throw_exception<std::out_of_range>("wrap-around");
      }

      len += crtPos_ - crtBegin_;
    } else {
      if (crtPos_ < other.crtPos_) {
        throw_exception<std::out_of_range>("underflow");
      }

      len += crtPos_ - other.crtPos_;
    }

    return len;
  }

  /**
   * Return the distance from the given IOBuf to the this cursor.
   */
  size_t operator-(const BufType* buf) const {
    size_t len = 0;

    const BufType* curBuf = buf;
    while (curBuf != crtBuf_) {
      len += curBuf->length();
      curBuf = curBuf->next();
      if (curBuf == buf || curBuf == buffer_) {
        throw_exception<std::out_of_range>("wrap-around");
      }
    }

    len += crtPos_ - crtBegin_;
    return len;
  }

  /**
   * Check if this cursor has a size limit imposed on it.
   *
   * @methodset Configuration
   */
  bool isBounded() const {
    return remainingLen_ != std::numeric_limits<size_t>::max();
  }

 protected:
  void dcheckIntegrity() const {
    FOLLY_IO_CURSOR_BORROW_DCHECK(!*borrowed());
    DCHECK(crtBegin_ <= crtPos_ && crtPos_ <= crtEnd_);
    DCHECK(crtBuf_ == nullptr || crtBegin_ == crtBuf_->data());
    DCHECK(
        crtBuf_ == nullptr ||
        (std::size_t)(crtEnd_ - crtBegin_) <= crtBuf_->length());
  }

  CursorBase(CursorBase const&) = default;
  CursorBase(CursorBase&&) = default;
  CursorBase& operator=(CursorBase const&) = default;
  CursorBase& operator=(CursorBase&&) = default;

  ~CursorBase() = default;

  BufType* head() { return buffer_; }

  bool tryAdvanceBuffer() {
    BufType* nextBuf = crtBuf_->next();
    if (FOLLY_UNLIKELY(nextBuf == buffer_) || remainingLen_ == 0) {
      crtPos_ = crtEnd_;
      return false;
    }

    absolutePos_ += crtEnd_ - crtBegin_;
    crtBuf_ = nextBuf;
    crtPos_ = crtBegin_ = crtBuf_->data();
    crtEnd_ = crtBuf_->tail();
    if (isBounded()) {
      if (uintptr_t(crtPos_) + remainingLen_ < uintptr_t(crtEnd_)) {
        crtEnd_ = crtPos_ + remainingLen_;
      }
      remainingLen_ -= crtEnd_ - crtPos_;
    }
    derived().advanceDone();
    return true;
  }

  bool tryRetreatBuffer() {
    if (FOLLY_UNLIKELY(crtBuf_ == buffer_)) {
      crtPos_ = crtBegin_;
      return false;
    }
    if (isBounded()) {
      remainingLen_ += crtEnd_ - crtBegin_;
    }
    crtBuf_ = crtBuf_->prev();
    crtBegin_ = crtBuf_->data();
    crtPos_ = crtEnd_ = crtBuf_->tail();
    absolutePos_ -= crtEnd_ - crtBegin_;
    derived().advanceDone();
    return true;
  }

  void advanceBufferIfEmpty() {
    dcheckIntegrity();
    if (crtPos_ == crtEnd_) {
      tryAdvanceBuffer();
    }
  }

  BufType* crtBuf_;
  BufType* buffer_;
  const uint8_t* crtBegin_{nullptr};
  const uint8_t* crtEnd_{nullptr};
  const uint8_t* crtPos_{nullptr};
  size_t absolutePos_{0};
  // For bounded Cursor, remainingLen_ is the remaining number of data bytes
  // in subsequent IOBufs in the chain. For unbounded Cursor, remainingLen_
  // is set to the max of size_t
  size_t remainingLen_{std::numeric_limits<size_t>::max()};

#if FOLLY_IO_CURSOR_BORROW_CHECKING
  bool borrowed_ = false;
  bool* borrowed() { return &borrowed_; }
  const bool* borrowed() const { return &borrowed_; }
#else
  bool* borrowed() { return nullptr; }
  const bool* borrowed() const { return nullptr; }
#endif

 private:
  Derived& derived() { return static_cast<Derived&>(*this); }

  Derived const& derived() const { return static_cast<const Derived&>(*this); }

  template <class T>
  FOLLY_NOINLINE T readSlow() {
    T val;
    pullSlow(&val, sizeof(T));
    return val;
  }

  FOLLY_NOINLINE void readFixedStringSlow(std::string* str, size_t len) {
    for (size_t available; (available = length()) < len;) {
      str->append(reinterpret_cast<const char*>(data()), available);
      if (FOLLY_UNLIKELY(!tryAdvanceBuffer())) {
        throw_exception<std::out_of_range>("string underflow");
      }
      len -= available;
    }
    str->append(reinterpret_cast<const char*>(data()), len);
    crtPos_ += len;
    advanceBufferIfEmpty();
  }

  FOLLY_NOINLINE size_t pullAtMostSlow(void* buf, size_t len) {
    uint8_t* p = reinterpret_cast<uint8_t*>(buf);
    size_t copied = 0;
    for (size_t available; (available = length()) < len;) {
      if (available > 0) {
        // Don't try to copy from 0-length buffers, since they could have
        // a null data() pointer.
        memcpy(p, data(), available);
        copied += available;
      }
      if (FOLLY_UNLIKELY(!tryAdvanceBuffer())) {
        return copied;
      }
      p += available;
      len -= available;
    }
    if (len > 0) {
      // Don't try to copy from 0-length buffers, since they could have
      // a null data() pointer.
      memcpy(p, data(), len);
      crtPos_ += len;
    }
    advanceBufferIfEmpty();
    return copied + len;
  }

  FOLLY_NOINLINE void pullSlow(void* buf, size_t len) {
    if (FOLLY_UNLIKELY(pullAtMostSlow(buf, len) != len)) {
      throw_exception<std::out_of_range>("underflow");
    }
  }

  FOLLY_NOINLINE size_t skipAtMostSlow(size_t len) {
    size_t skipped = 0;
    for (size_t available; (available = length()) < len;) {
      skipped += available;
      if (FOLLY_UNLIKELY(!tryAdvanceBuffer())) {
        return skipped;
      }
      len -= available;
    }
    crtPos_ += len;
    advanceBufferIfEmpty();
    return skipped + len;
  }

  FOLLY_NOINLINE void skipSlow(size_t len) {
    if (FOLLY_UNLIKELY(skipAtMostSlow(len) != len)) {
      throw_exception<std::out_of_range>("underflow");
    }
  }

  FOLLY_NOINLINE size_t retreatAtMostSlow(size_t len) {
    size_t retreated = 0;
    for (size_t available; (available = crtPos_ - crtBegin_) < len;) {
      retreated += available;
      if (FOLLY_UNLIKELY(!tryRetreatBuffer())) {
        return retreated;
      }
      len -= available;
    }
    crtPos_ -= len;
    return retreated + len;
  }

  FOLLY_NOINLINE void retreatSlow(size_t len) {
    if (FOLLY_UNLIKELY(retreatAtMostSlow(len) != len)) {
      throw_exception<std::out_of_range>("underflow");
    }
  }

  void advanceDone() {}

  FOLLY_NOINLINE size_t peekBytesSlow() {
    size_t available = 0;
    while (available == 0 && tryAdvanceBuffer()) {
      available = length();
    }
    return available;
  }
};

namespace detail {
template <typename T>
ThinCursor thinCursorReadSlow(ThinCursor, T&, Cursor&);
ThinCursor thinCursorSkipSlow(ThinCursor, Cursor&, size_t);
} // namespace detail

// A register-pass facade for a Cursor. It strips out state that's only needed
// down slow paths, so that it can be passed and returned efficiently in
// registers. This gives up some convenience (users need to maintain that state
// in a fallback Cursor), to gain performance.
class ThinCursor {
 public:
  ThinCursor() = default;
  ThinCursor(ThinCursor&&) = default;
  ThinCursor(const ThinCursor&) = delete;
  ThinCursor& operator=(ThinCursor&&) = default;
  ThinCursor& operator=(const ThinCursor&) = delete;

  const uint8_t* data() const {
    dcheckIntegrity();
    return crtPos_;
  }

  size_t length() const {
    dcheckIntegrity();
    return crtEnd_ - crtPos_;
  }

  bool canAdvance(size_t amount) const { return amount <= length(); }

  bool isAtEnd() const { return length() == 0; }

  template <class T>
  FOLLY_ALWAYS_INLINE T read(Cursor& fallback) {
    if (FOLLY_LIKELY((uintptr_t)crtEnd_ - (uintptr_t)crtPos_ >= sizeof(T))) {
      T result = loadUnaligned<T>(data());
      crtPos_ += sizeof(T);
      return result;
    } else {
      T result;
      *this = detail::thinCursorReadSlow(std::move(*this), result, fallback);
      return result;
    }
  }

  template <class T>
  T readBE(Cursor& fallback) {
    return Endian::big(read<T>(fallback));
  }

  template <class T>
  T readLE(Cursor& fallback) {
    return Endian::little(read<T>(fallback));
  }

  void skip(Cursor& fallback, size_t len) {
    dcheckIntegrity();
    if (FOLLY_LIKELY(uintptr_t(crtPos_) + len < uintptr_t(crtEnd_))) {
      crtPos_ += len;
    } else {
      *this = detail::thinCursorSkipSlow(std::move(*this), fallback, len);
    }
  }

  void skipNoAdvance(size_t len) {
    DCHECK_LE(len, length());
    crtPos_ += len;
  }

 private:
  void dcheckIntegrity() const { DCHECK(crtPos_ <= crtEnd_); }

  friend class Cursor;
  ThinCursor(const uint8_t* crtPos, const uint8_t* crtEnd) noexcept
      : crtPos_(crtPos), crtEnd_(crtEnd) {}
  // Note: these are the only fields we can have -- x86-64 calling convention
  // maxes out at returning 2 pointer-sized fields in registers, and we don't
  // want to have to use memory for returning these.
  const uint8_t* crtPos_;
  const uint8_t* crtEnd_;
};

class Cursor : public CursorBase<Cursor, const IOBuf> {
 public:
  explicit Cursor(const IOBuf* buf) noexcept
      : CursorBase<Cursor, const IOBuf>(buf) {}

  explicit Cursor(const IOBuf* buf, size_t len) noexcept
      : CursorBase<Cursor, const IOBuf>(buf, len) {}

  template <class OtherDerived, class OtherBuf>
  explicit Cursor(const CursorBase<OtherDerived, OtherBuf>& cursor) noexcept
      : CursorBase<Cursor, const IOBuf>(cursor) {}

  template <class OtherDerived, class OtherBuf>
  Cursor(const CursorBase<OtherDerived, OtherBuf>& cursor, size_t len)
      : CursorBase<Cursor, const IOBuf>(cursor, len) {}

  ThinCursor borrow() {
    FOLLY_IO_CURSOR_BORROW_DCHECK(!std::exchange(*borrowed(), true));
    return {crtPos_, crtEnd_};
  }

  void unborrow(ThinCursor&& cursor) {
    FOLLY_IO_CURSOR_BORROW_DCHECK(std::exchange(*borrowed(), false));
    DCHECK_EQ(cursor.crtEnd_, crtEnd_);
    crtPos_ = cursor.crtPos_;
  }
};

namespace detail {
template <typename T>
ThinCursor thinCursorReadSlow(ThinCursor borrowed, T& val, Cursor& fallback) {
  fallback.unborrow(std::move(borrowed));
  val = fallback.read<T>();
  return fallback.borrow();
}

inline ThinCursor thinCursorSkipSlow(
    ThinCursor borrowed, Cursor& fallback, size_t len) {
  fallback.unborrow(std::move(borrowed));
  fallback.skip(len);
  return fallback.borrow();
}
} // namespace detail

template <class Derived>
class Writable {
 public:
  /**
   * Write a value to the cursor.
   *
   * @methodset Writing
   *
   * May throw if there isn't enough space and the derived cursor type does not
   * support extending the IOBuf's writable range.
   */
  template <class T>
  typename std::enable_if<std::is_arithmetic<T>::value>::type write(
      T value, size_t n = sizeof(T)) {
    assert(n <= sizeof(T));
    const uint8_t* u8 = reinterpret_cast<const uint8_t*>(&value);
    Derived* d = static_cast<Derived*>(this);
    d->push(u8, n);
  }

  /**
   * Write a value to the cursor in Big-Endian.
   *
   * @methodset Writing
   *
   * May throw if there isn't enough space and the derived cursor type does not
   * support extending the IOBuf's writable range.
   */
  template <class T>
  void writeBE(T value) {
    Derived* d = static_cast<Derived*>(this);
    d->write(Endian::big(value));
  }

  /**
   * Write a value to the cursor in Little-Endian.
   *
   * @methodset Writing
   *
   * May throw if there isn't enough space and the derived cursor type does not
   * support extending the IOBuf's writable range.
   */
  template <class T>
  void writeLE(T value) {
    Derived* d = static_cast<Derived*>(this);
    d->write(Endian::little(value));
  }

  /**
   * Write bytes to the cursor.
   *
   * @methodset Writing
   *
   * @throw out_of_range if there isn't enough space in the cursor.
   */
  void push(const uint8_t* buf, size_t len) {
    Derived* d = static_cast<Derived*>(this);
    if (d->pushAtMost(buf, len) != len) {
      throw_exception<std::out_of_range>("overflow");
    }
  }

  void push(ByteRange buf) {
    if (this->pushAtMost(buf) != buf.size()) {
      throw_exception<std::out_of_range>("overflow");
    }
  }

  /**
   * Write bytes to the cursor; stop writing if the end of the cursor is
   * reached.
   *
   * @methodset Writing
   */
  size_t pushAtMost(ByteRange buf) {
    Derived* d = static_cast<Derived*>(this);
    return d->pushAtMost(buf.data(), buf.size());
  }

  void push(Cursor cursor, size_t len) {
    if (this->pushAtMost(cursor, len) != len) {
      throw_exception<std::out_of_range>("overflow");
    }
  }

  size_t pushAtMost(Cursor cursor, size_t len) {
    size_t written = 0;
    for (;;) {
      auto currentBuffer = cursor.peekBytes();
      const uint8_t* crtData = currentBuffer.data();
      size_t available = currentBuffer.size();
      if (available == 0) {
        // end of buffer chain
        return written;
      }
      // all data is in current buffer
      if (available >= len) {
        this->push(crtData, len);
        cursor.skip(len);
        return written + len;
      }

      // write the whole current IOBuf
      this->push(crtData, available);
      cursor.skip(available);
      written += available;
      len -= available;
    }
  }
};

enum class CursorAccess { PRIVATE, UNSHARE };

template <CursorAccess access>
class RWCursor
    : public CursorBase<RWCursor<access>, IOBuf>,
      public Writable<RWCursor<access>> {
  friend class CursorBase<RWCursor<access>, IOBuf>;

 public:
  explicit RWCursor(IOBuf* buf) noexcept
      : CursorBase<RWCursor<access>, IOBuf>(buf), maybeShared_(true) {}

  explicit RWCursor(IOBufQueue& queue) noexcept
      : RWCursor((queue.flushCache(), queue.head_.get())) {}

  // Efficient way to advance to position cursor to the end of the queue,
  // using cached length instead of a walk via advanceToEnd().
  struct AtEnd {};
  /**
   * Create the cursor initially pointing to the end of queue.
   */
  RWCursor(IOBufQueue& queue, AtEnd) noexcept : RWCursor(queue) {
    if (!queue.options().cacheChainLength) {
      this->advanceToEnd();
    } else {
      this->crtBuf_ = this->buffer_->prev();
      this->crtBegin_ = this->crtBuf_->data();
      this->crtEnd_ = this->crtBuf_->tail();
      this->crtPos_ = this->crtEnd_;
      this->absolutePos_ =
          queue.chainLength() - (this->crtPos_ - this->crtBegin_);
      DCHECK_EQ(this->getCurrentPosition(), queue.chainLength());
    }
  }

  template <class OtherDerived, class OtherBuf>
  explicit RWCursor(const CursorBase<OtherDerived, OtherBuf>& cursor) noexcept
      : CursorBase<RWCursor<access>, IOBuf>(cursor), maybeShared_(true) {
    CHECK(!cursor.isBounded())
        << "Creating RWCursor from bounded Cursor is not allowed";
  }
  /**
   * Gather at least n bytes contiguously into the current buffer,
   * by coalescing subsequent buffers from the chain as necessary.
   *
   * @methodset Modifiers
   *
   * @throw overflow_error if there aren't enough bytes to gather
   */
  void gather(size_t n) {
    // Forbid attempts to gather beyond the end of this IOBuf chain.
    // Otherwise we could try to coalesce the head of the chain and end up
    // accidentally freeing it, invalidating the pointer owned by external
    // code.
    //
    // If crtBuf_ == head() then IOBuf::gather() will perform all necessary
    // checking.  We only have to perform an explicit check here when calling
    // gather() on a non-head element.
    if (this->crtBuf_ != this->head() && this->totalLength() < n) {
      throw std::overflow_error("cannot gather() past the end of the chain");
    }
    size_t offset = this->crtPos_ - this->crtBegin_;
    this->crtBuf_->gather(offset + n);
    this->crtBegin_ = this->crtBuf_->data();
    this->crtEnd_ = this->crtBuf_->tail();
    this->crtPos_ = this->crtBegin_ + offset;
  }

  /**
   * Gather at most n bytes contiguously into the current buffer,
   * by coalescing subsequent buffers from the chain as necessary.
   *
   * @methodset Modifiers
   */
  void gatherAtMost(size_t n) {
    this->dcheckIntegrity();
    size_t size = std::min(n, this->totalLength());
    size_t offset = this->crtPos_ - this->crtBegin_;
    this->crtBuf_->gather(offset + size);
    this->crtBegin_ = this->crtBuf_->data();
    this->crtEnd_ = this->crtBuf_->tail();
    this->crtPos_ = this->crtBegin_ + offset;
  }

  using Writable<RWCursor<access>>::pushAtMost;
  size_t pushAtMost(const uint8_t* buf, size_t len) {
    // We have to explicitly check for an input length of 0.
    // We support buf being nullptr in this case, but we need to avoid calling
    // memcpy() with a null source pointer, since that is undefined behavior
    // even if the length is 0.
    if (len == 0) {
      return 0;
    }

    size_t copied = 0;
    for (;;) {
      // Fast path: the current buffer is big enough.
      size_t available = this->length();
      if (FOLLY_LIKELY(available >= len)) {
        if (access == CursorAccess::UNSHARE) {
          maybeUnshare();
        }
        memcpy(writableData(), buf, len);
        this->crtPos_ += len;
        return copied + len;
      }

      if (access == CursorAccess::UNSHARE) {
        maybeUnshare();
      }
      memcpy(writableData(), buf, available);
      copied += available;
      if (FOLLY_UNLIKELY(!this->tryAdvanceBuffer())) {
        return copied;
      }
      buf += available;
      len -= available;
    }
  }

  /**
   * Insert data at the cursor position.
   *
   * @methodset Writing
   *
   * Data in the IOBuf after the cursor will not be overwritten, though it might
   * be moved.
   *
   * After this operator, the cursor will point to the data just after the
   * inserted data.
   */
  void insert(std::unique_ptr<folly::IOBuf> buf) {
    this->dcheckIntegrity();
    this->absolutePos_ += buf->computeChainDataLength();
    if (this->crtPos_ == this->crtBegin_ && this->crtBuf_ != this->buffer_) {
      // Can just prepend
      this->crtBuf_->prependChain(std::move(buf));
    } else {
      IOBuf* nextBuf;
      std::unique_ptr<folly::IOBuf> remaining;
      if (this->crtPos_ != this->crtEnd_) {
        // Need to split current IOBuf in two.
        remaining = this->crtBuf_->cloneOne();
        remaining->trimStart(this->crtPos_ - this->crtBegin_);
        nextBuf = remaining.get();
        buf->prependChain(std::move(remaining));
      } else {
        // Can just append
        nextBuf = this->crtBuf_->next();
      }
      this->crtBuf_->trimEnd(this->length());
      this->absolutePos_ += this->crtPos_ - this->crtBegin_;
      this->crtBuf_->insertAfterThisOne(std::move(buf));

      if (nextBuf == this->buffer_) {
        // We've just appended to the end of the buffer, so advance to the end.
        this->crtBuf_ = this->buffer_->prev();
        this->crtBegin_ = this->crtBuf_->data();
        this->crtPos_ = this->crtEnd_ = this->crtBuf_->tail();
        // This has already been accounted for, so remove it.
        this->absolutePos_ -= this->crtEnd_ - this->crtBegin_;
      } else {
        // Jump past the new links
        this->crtBuf_ = nextBuf;
        this->crtPos_ = this->crtBegin_ = this->crtBuf_->data();
        this->crtEnd_ = this->crtBuf_->tail();
      }
    }
  }

  /**
   * Get a raw pointer to the writable section controlled by this cursor.
   *
   * @methodset Accessors
   */
  uint8_t* writableData() {
    this->dcheckIntegrity();
    return this->crtBuf_->writableData() + (this->crtPos_ - this->crtBegin_);
  }

 private:
  void maybeUnshare() {
    if (FOLLY_UNLIKELY(maybeShared_)) {
      size_t offset = this->crtPos_ - this->crtBegin_;
      this->crtBuf_->unshareOne();
      this->crtBegin_ = this->crtBuf_->data();
      this->crtEnd_ = this->crtBuf_->tail();
      this->crtPos_ = this->crtBegin_ + offset;
      maybeShared_ = false;
    }
  }

  void advanceDone() { maybeShared_ = true; }

  bool maybeShared_;
};

typedef RWCursor<CursorAccess::PRIVATE> RWPrivateCursor;
typedef RWCursor<CursorAccess::UNSHARE> RWUnshareCursor;

/**
 * Append to the end of a buffer chain, growing the chain (by allocating new
 * buffers) in increments of at least growth bytes every time.  Won't grow
 * (and push() and ensure() will throw) if growth == 0.
 *
 * TODO(tudorb): add a flavor of Appender that reallocates one IOBuf instead
 * of chaining.
 */
class Appender : public Writable<Appender> {
 public:
  Appender(IOBuf* buf, std::size_t growth) noexcept
      : buffer_(buf), crtBuf_(buf->prev()), growth_(growth) {}

  /**
   * Get the writable tail of the IOBuf this cursor points to.
   *
   * @methodset Accessors
   */
  uint8_t* writableData() noexcept { return crtBuf_->writableTail(); }

  /**
   * Get the amount of writable tailroom of the IOBuf this cursor points to.
   *
   * @methodset Capacity
   */
  size_t length() const noexcept { return crtBuf_->tailroom(); }

  /**
   * Mark n bytes (must be <= length()) as appended, as per the
   * IOBuf::append() method.
   *
   * @methodset Appending
   */
  void append(size_t n) { crtBuf_->append(n); }

  /**
   * Ensure at least n contiguous bytes available to write.
   * Postcondition: length() >= n.
   *
   * @methodset Appending
   */
  void ensure(std::size_t n) {
    if (FOLLY_LIKELY(length() >= n)) {
      return;
    }

    // Waste the rest of the current buffer and allocate a new one.
    // Don't make it too small, either.
    if (growth_ == 0) {
      throw_exception<std::out_of_range>("can't grow buffer chain");
    }

    n = std::max(n, growth_);
    buffer_->prependChain(IOBuf::create(n));
    crtBuf_ = buffer_->prev();
  }

  using Writable<Appender>::pushAtMost;
  size_t pushAtMost(const uint8_t* buf, size_t len) {
    // We have to explicitly check for an input length of 0.
    // We support buf being nullptr in this case, but we need to avoid calling
    // memcpy() with a null source pointer, since that is undefined behavior
    // even if the length is 0.
    if (len == 0) {
      return 0;
    }

    // If the length of this buffer is 0 try growing it.
    // Otherwise on the first iteration of the following loop memcpy is called
    // with a null source pointer.
    if (FOLLY_UNLIKELY(length() == 0 && !tryGrowChain())) {
      return 0;
    }

    size_t copied = 0;
    for (;;) {
      // Fast path: it all fits in one buffer.
      size_t available = length();
      if (FOLLY_LIKELY(available >= len)) {
        memcpy(writableData(), buf, len);
        append(len);
        return copied + len;
      }

      memcpy(writableData(), buf, available);
      append(available);
      copied += available;
      if (FOLLY_UNLIKELY(!tryGrowChain())) {
        return copied;
      }
      buf += available;
      len -= available;
    }
  }

  /**
   * Append to the end of this buffer, using a printf() style
   * format specifier.
   *
   * @methodset Appending
   *
   * Note that folly/Format.h provides nicer and more type-safe mechanisms
   * for formatting strings, which should generally be preferred over
   * printf-style formatting.  Appender objects can be used directly as an
   * output argument for Formatter objects.  For example:
   *
   *   Appender app(&iobuf);
   *   format("{} {}", "hello", "world")(app);
   *
   * However, printf-style strings are still needed when dealing with existing
   * third-party code in some cases.
   *
   * This will always add a nul-terminating character after the end
   * of the output.  However, the buffer data length will only be updated to
   * include the data itself.  The nul terminator will be the first byte in the
   * buffer tailroom.
   *
   * This method may throw exceptions on error.
   */
  void printf(FOLLY_PRINTF_FORMAT const char* fmt, ...)
      FOLLY_PRINTF_FORMAT_ATTR(2, 3);

  /// @methodset Appending
  void vprintf(const char* fmt, va_list ap);

  /**
   * Append a StringPiece to the buffer.
   *
   * @methodset Appending
   *
   * This allows Appender objects to be used directly with Formatter.
   */
  void operator()(StringPiece sp) { push(ByteRange(sp)); }

 private:
  bool tryGrowChain() {
    assert(crtBuf_->next() == buffer_);
    if (growth_ == 0) {
      return false;
    }

    buffer_->prependChain(IOBuf::create(growth_));
    crtBuf_ = buffer_->prev();
    return true;
  }

  IOBuf* buffer_;
  IOBuf* crtBuf_;
  std::size_t growth_;
};

class QueueAppender : public Writable<QueueAppender> {
 public:
  /**
   * Create an Appender that writes to a IOBufQueue.  When we allocate space in
   * the queue, each new buffer will be sized between minGrowth and maxGrowth,
   * with an exponential schedule (unless you call ensure() with a bigger value
   * yourself).
   */
  QueueAppender(
      IOBufQueue* queue, std::size_t minGrowth, std::size_t maxGrowth) noexcept
      : queueCache_(queue) {
    resetGrowth(minGrowth, maxGrowth);
  }

  /**
   * Convenience constructor to use constant buffer growth.
   */
  QueueAppender(IOBufQueue* queue, std::size_t growth) noexcept
      : QueueAppender(queue, growth, growth) {}

  /**
   * Resets this, as if constructed anew.
   */
  void reset(
      IOBufQueue* queue,
      std::size_t minGrowth,
      std::size_t maxGrowth) noexcept {
    queueCache_.reset(queue);
    resetGrowth(minGrowth, maxGrowth);
  }

  void reset(IOBufQueue* queue, std::size_t growth) noexcept {
    reset(queue, growth, growth);
  }

  /**
   * Get a pointer to the writable tail.
   *
   * @methodset Accessors
   */
  uint8_t* writableData() noexcept { return queueCache_.writableData(); }

  /**
   * Get the size of the writable tail.
   *
   * @methodset Capacity
   */
  size_t length() const noexcept { return queueCache_.length(); }

  /**
   * Append n bytes.
   *
   * @methodset Appending
   */
  void append(size_t n) { queueCache_.append(n); }

  /**
   * Ensure that there are at least n contiguous bytes available for writing.
   *
   * @methodset Modifiers
   *
   * Can go above maxGrowth.
   *
   * May throw if there isn't enough room.
   */
  void ensure(size_t n) {
    if (length() < n) {
      ensureSlowNoinline(n);
    }
  }

  /**
   * Ensures up to n contiguous bytes available, without surpassing maxGrowth_.
   *
   * @methodset Modifiers
   *
   * Cannot go above maxGrowth.
   *
   * May throw if there isn't enough room.
   */
  void ensureWithinMaxGrowth(size_t n) { ensure(std::min(n, maxGrowth_)); }

  /**
   * Write an object to the cursor.
   *
   * @param n The number of bytes of value to write; defaults to sizeof(T)
   */
  template <class T>
  typename std::enable_if<std::is_arithmetic<T>::value>::type write(
      T value, size_t n = sizeof(T)) {
    // We can't fail.
    assert(n <= sizeof(T));
    if (FOLLY_UNLIKELY(length() < sizeof(T))) {
      ensureSlowNoinline(sizeof(T));
    }
    storeUnaligned(queueCache_.writableData(), value);
    queueCache_.appendUnsafe(n);
  }

  using Writable<QueueAppender>::pushAtMost;
  size_t pushAtMost(const uint8_t* buf, size_t len) {
    // Fill the current buffer
    const size_t copyLength = std::min(len, length());
    if (copyLength != 0) {
      memcpy(writableData(), buf, copyLength);
      queueCache_.appendUnsafe(copyLength);
      buf += copyLength;
    }
    size_t remaining = len - copyLength;
    // Allocate more buffers as necessary
    while (remaining != 0) {
      ensureSlow(growth_);
      auto avail = std::min(length(), remaining);
      memcpy(writableData(), buf, avail);
      queueCache_.appendUnsafe(avail);
      buf += avail;
      remaining -= avail;
    }
    return len;
  }

  /**
   * Inserts data at the current cursor position.
   *
   * @methodset Writing
   */
  void insert(std::unique_ptr<folly::IOBuf> buf) {
    if (buf) {
      queueCache_.queue()->append(
          std::move(buf), /* pack */ true, /* allowTailReuse */ true);
    }
  }

  void insert(const folly::IOBuf& buf) {
    queueCache_.queue()->append(
        buf, /* pack */ true, /* allowTailReuse */ true);
  }

  /**
   * Get a RWCursor for this IOBufQueue.
   *
   * @methodset Accessors
   */
  template <CursorAccess access>
  explicit operator RWCursor<access>() {
    return RWCursor<access>(*queueCache_.queue());
  }

  /**
   * Get a RWCursor for the last n bytes of this IOBufQueue.
   *
   * @methodset Accessors
   */
  template <CursorAccess access>
  RWCursor<access> tail(size_t n) {
    RWCursor<access> result(
        *queueCache_.queue(), typename RWCursor<access>::AtEnd{});
    result -= n;
    return result;
  }

  /**
   * Remove n bytes from the end of this IOBufQueue.
   *
   * @methodset Modifiers
   */
  void trimEnd(size_t n) { queueCache_.queue()->trimEnd(n); }

 private:
  folly::IOBufQueue::WritableRangeCache queueCache_{nullptr};
  size_t growth_{0};
  size_t maxGrowth_{0};

  void resetGrowth(std::size_t minGrowth, std::size_t maxGrowth) noexcept {
    CHECK_LE(growth_, maxGrowth_);
    growth_ = minGrowth;
    maxGrowth_ = maxGrowth;
  }

  FOLLY_NOINLINE void ensureSlowNoinline(size_t n) { ensureSlow(n); }

  void ensureSlow(size_t n) {
    // Reinstall our cache in case something else invalidated it.
    queueCache_.fillCache();
    // Recheck with updated cache, to avoid unnecessary allocation.
    if (FOLLY_UNLIKELY(length() >= n)) {
      return;
    }
    auto minBufSize = std::exchange(growth_, std::min(growth_ * 2, maxGrowth_));
    queueCache_.queue()->append(IOBuf::create(std::max(n, minBufSize)));
    DCHECK_GE(length(), n);
  }
};

} // namespace io
} // namespace folly

#include <folly/io/Cursor-inl.h>