folly/folly/io/test/IOBufTest.cpp

/*
 * 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.
 */

#include <folly/io/IOBuf.h>

#include <cstddef>
#include <random>

#include <folly/Range.h>
#include <folly/io/TypedIOBuf.h>
#include <folly/memory/Malloc.h>
#include <folly/portability/GTest.h>

using folly::ByteRange;
using folly::fbstring;
using folly::IOBuf;
using folly::ordering;
using folly::StringPiece;
using folly::TypedIOBuf;
using std::unique_ptr;

void append(std::unique_ptr<IOBuf>& buf, StringPiece str) {
  EXPECT_LE(str.size(), buf->tailroom());
  memcpy(buf->writableData(), str.data(), str.size());
  buf->append(str.size());
}

void prepend(std::unique_ptr<IOBuf>& buf, StringPiece str) {
  EXPECT_LE(str.size(), buf->headroom());
  memcpy(buf->writableData() - str.size(), str.data(), str.size());
  buf->prepend(str.size());
}

TEST(IOBuf, Simple) {
  unique_ptr<IOBuf> buf(IOBuf::create(100));
  uint32_t cap = buf->capacity();
  EXPECT_LE(100, cap);
  EXPECT_EQ(0, buf->headroom());
  EXPECT_EQ(0, buf->length());
  EXPECT_EQ(cap, buf->tailroom());

  append(buf, "world");
  buf->advance(10);
  EXPECT_EQ(10, buf->headroom());
  EXPECT_EQ(5, buf->length());
  EXPECT_EQ(cap - 15, buf->tailroom());

  prepend(buf, "hello ");
  EXPECT_EQ(4, buf->headroom());
  EXPECT_EQ(11, buf->length());
  EXPECT_EQ(cap - 15, buf->tailroom());

  const char* p = reinterpret_cast<const char*>(buf->data());
  EXPECT_EQ("hello world", std::string(p, buf->length()));

  buf->clear();
  EXPECT_EQ(0, buf->headroom());
  EXPECT_EQ(0, buf->length());
  EXPECT_EQ(cap, buf->tailroom());
}

void testAllocSize(uint32_t requestedCapacity) {
  unique_ptr<IOBuf> iobuf(IOBuf::create(requestedCapacity));
  EXPECT_GE(iobuf->capacity(), requestedCapacity);
}

TEST(IOBuf, AllocSizes) {
  // Cover small evil values exhaustively.
  for (uint32_t i = 0; i < 1234; i++) {
    testAllocSize(i);
  }

  // Try with large allocation sizes that will require an external buffer.
  testAllocSize(9000);
  testAllocSize(1048575);
  testAllocSize(1048576);
  testAllocSize(1048577);
}

void deleteArrayBuffer(void* buf, void* arg) {
  uint32_t* deleteCount = static_cast<uint32_t*>(arg);
  ++(*deleteCount);
  uint8_t* bufPtr = static_cast<uint8_t*>(buf);
  delete[] bufPtr;
}

TEST(IOBuf, TakeOwnership) {
  uint32_t size1 = 99;
  uint8_t* buf1 = static_cast<uint8_t*>(malloc(size1));
  unique_ptr<IOBuf> iobuf1(IOBuf::takeOwnership(buf1, size1));
  EXPECT_EQ(buf1, iobuf1->data());
  EXPECT_EQ(size1, iobuf1->length());
  EXPECT_EQ(buf1, iobuf1->buffer());
  EXPECT_EQ(size1, iobuf1->capacity());

  uint32_t deleteCount = 0;
  uint32_t size2 = 4321;
  uint8_t* buf2 = new uint8_t[size2];
  unique_ptr<IOBuf> iobuf2(
      IOBuf::takeOwnership(buf2, size2, deleteArrayBuffer, &deleteCount));
  EXPECT_EQ(buf2, iobuf2->data());
  EXPECT_EQ(size2, iobuf2->length());
  EXPECT_EQ(buf2, iobuf2->buffer());
  EXPECT_EQ(size2, iobuf2->capacity());
  EXPECT_EQ(0, deleteCount);
  iobuf2.reset();
  EXPECT_EQ(1, deleteCount);

  deleteCount = 0;
  uint32_t size3 = 3456;
  uint8_t* buf3 = new uint8_t[size3];
  uint32_t length3 = 48;
  unique_ptr<IOBuf> iobuf3(IOBuf::takeOwnership(
      buf3, size3, length3, deleteArrayBuffer, &deleteCount));
  EXPECT_EQ(buf3, iobuf3->data());
  EXPECT_EQ(length3, iobuf3->length());
  EXPECT_EQ(buf3, iobuf3->buffer());
  EXPECT_EQ(size3, iobuf3->capacity());
  EXPECT_EQ(0, deleteCount);
  iobuf3.reset();
  EXPECT_EQ(1, deleteCount);

  deleteCount = 0;
  {
    uint32_t size4 = 1234;
    uint8_t* buf4 = new uint8_t[size4];
    uint32_t length4 = 48;
    IOBuf iobuf4(
        IOBuf::TAKE_OWNERSHIP,
        buf4,
        size4,
        length4,
        deleteArrayBuffer,
        &deleteCount);
    EXPECT_EQ(buf4, iobuf4.data());
    EXPECT_EQ(length4, iobuf4.length());
    EXPECT_EQ(buf4, iobuf4.buffer());
    EXPECT_EQ(size4, iobuf4.capacity());

    IOBuf iobuf5 = std::move(iobuf4);
    EXPECT_EQ(buf4, iobuf5.data());
    EXPECT_EQ(length4, iobuf5.length());
    EXPECT_EQ(buf4, iobuf5.buffer());
    EXPECT_EQ(size4, iobuf5.capacity());
    EXPECT_EQ(0, deleteCount);
  }
  EXPECT_EQ(1, deleteCount);
  {
    uint32_t size = 2;
    uint8_t* buf = static_cast<uint8_t*>(malloc(size));
    buf[0] = 'A';
    unique_ptr<IOBuf> iobuf(IOBuf::takeOwnership(buf, size, 1));
    fbstring str = iobuf->moveToFbString();
    EXPECT_EQ(str, "A");
  }

  deleteCount = 0;
  uint32_t size6 = 100;
  uint8_t* buf6 = new uint8_t[size6];
  uint32_t offset6 = 48;
  uint32_t length6 = 48;
  unique_ptr<IOBuf> iobuf6(IOBuf::takeOwnership(
      buf6, size6, offset6, length6, deleteArrayBuffer, &deleteCount));
  EXPECT_EQ(buf6 + offset6, iobuf6->data());
  EXPECT_EQ(length6, iobuf6->length());
  EXPECT_EQ(buf6, iobuf6->buffer());
  EXPECT_EQ(size6, iobuf6->capacity());
  EXPECT_EQ(0, deleteCount);
  iobuf6.reset();
  EXPECT_EQ(1, deleteCount);
}

TEST(IOBuf, TakeOwnershipFreeOnErrorBugfix) {
  static void* freedBuf = nullptr;
  static void* freedUserData = nullptr;

  folly::IOBuf::FreeFunction freeFn = [](void* calledBuf,
                                         void* calledUserData) {
    freedBuf = calledBuf;
    freedUserData = calledUserData;
  };

  int userData = 0;
  char buf[1024];

  EXPECT_THROW(
      IOBuf::takeOwnership(
          buf, std::numeric_limits<size_t>::max(), freeFn, &userData, true),
      std::bad_alloc);

  EXPECT_EQ(freedBuf, buf);
  EXPECT_EQ(freedUserData, &userData);
}

TEST(IOBuf, GetUserData) {
  {
    const uint32_t size = 1234;
    uint8_t data[size] = {};
    unique_ptr<IOBuf> buf1(IOBuf::wrapBuffer(data, size));
    EXPECT_EQ(buf1->getUserData(), nullptr);
  }

  {
    size_t val = 0;
    uint32_t size = 4321;
    uint8_t* data = static_cast<uint8_t*>(malloc(size));
    unique_ptr<IOBuf> buf2(IOBuf::takeOwnership(
        data,
        size,
        [](void* buf, void* userData) {
          EXPECT_EQ(*static_cast<size_t*>(userData), 400);
          free(buf);
        },
        &val));
    EXPECT_EQ(buf2->getUserData(), &val);
    val = 200;
    EXPECT_EQ(*static_cast<size_t*>(buf2->getUserData()), 200);
    val = 400;
  }
}

TEST(IOBuf, GetFreeFn) {
  const uint32_t size = 4576;
  uint8_t* data = static_cast<uint8_t*>(malloc(size));
  std::memset(data, 0, size);
  folly::IOBuf::FreeFunction someFreeFn = [](void* buf, void* userData) {
    EXPECT_EQ(buf, userData);
    free(userData);
  };

  unique_ptr<IOBuf> someBuf(IOBuf::wrapBuffer(data, size));
  unique_ptr<IOBuf> someOtherBuf(
      IOBuf::takeOwnership(data, size, someFreeFn, data));

  EXPECT_EQ(someBuf->getFreeFn(), nullptr);
  EXPECT_EQ(someOtherBuf->getFreeFn(), someFreeFn);
}

TEST(IOBuf, WrapBuffer) {
  const uint32_t size1 = 1234;
  uint8_t buf1[size1] = {};
  unique_ptr<IOBuf> iobuf1(IOBuf::wrapBuffer(buf1, size1));
  EXPECT_EQ(buf1, iobuf1->data());
  EXPECT_EQ(size1, iobuf1->length());
  EXPECT_EQ(buf1, iobuf1->buffer());
  EXPECT_EQ(size1, iobuf1->capacity());

  uint32_t size2 = 0x1234;
  unique_ptr<uint8_t[]> buf2(new uint8_t[size2]);
  unique_ptr<IOBuf> iobuf2(IOBuf::wrapBuffer(buf2.get(), size2));
  EXPECT_EQ(buf2.get(), iobuf2->data());
  EXPECT_EQ(size2, iobuf2->length());
  EXPECT_EQ(buf2.get(), iobuf2->buffer());
  EXPECT_EQ(size2, iobuf2->capacity());

  uint32_t size3 = 4321;
  unique_ptr<uint8_t[]> buf3(new uint8_t[size3]);
  IOBuf iobuf3(IOBuf::WRAP_BUFFER, buf3.get(), size3);
  EXPECT_EQ(buf3.get(), iobuf3.data());
  EXPECT_EQ(size3, iobuf3.length());
  EXPECT_EQ(buf3.get(), iobuf3.buffer());
  EXPECT_EQ(size3, iobuf3.capacity());

  const uint32_t size4 = 2345;
  unique_ptr<uint8_t[]> buf4(new uint8_t[size4]);
  IOBuf iobuf4 = IOBuf::wrapBufferAsValue(buf4.get(), size4);
  EXPECT_EQ(buf4.get(), iobuf4.data());
  EXPECT_EQ(size4, iobuf4.length());
  EXPECT_EQ(buf4.get(), iobuf4.buffer());
  EXPECT_EQ(size4, iobuf4.capacity());

  if (folly::kIsSanitizeAddress) {
    const uint32_t size5 = 100;
    uint8_t buf5[size5];
    EXPECT_DEATH(IOBuf::wrapBuffer(buf5, size5 + 1), "stack-buffer-overflow");

    const uint32_t size6 = 100;
    std::vector<uint8_t> buf6(size6);
    EXPECT_DEATH(
        IOBuf::wrapBuffer(buf6.data(), size6 + 1), "heap-buffer-overflow");
  }
}

TEST(IOBuf, CreateCombined) {
  // Create a combined IOBuf, then destroy it.
  // The data buffer and IOBuf both become unused as part of the destruction
  {
    auto buf = IOBuf::createCombined(256);
    EXPECT_FALSE(buf->isShared());
  }

  // Create a combined IOBuf, clone from it, and then destroy the original
  // IOBuf.  The data buffer cannot be deleted until the clone is also
  // destroyed.
  {
    auto bufA = IOBuf::createCombined(256);
    EXPECT_FALSE(bufA->isShared());
    auto bufB = bufA->clone();
    EXPECT_TRUE(bufA->isShared());
    EXPECT_TRUE(bufB->isShared());
    bufA.reset();
    EXPECT_FALSE(bufB->isShared());
  }

  // Create a combined IOBuf, then call reserve() to get a larger buffer.
  // The IOBuf no longer points to the combined data buffer, but the
  // overall memory segment cannot be deleted until the IOBuf is also
  // destroyed.
  {
    auto buf = IOBuf::createCombined(256);
    buf->reserve(0, buf->capacity() + 100);
  }

  // Create a combined IOBuf, clone from it, then call unshare() on the original
  // buffer.  This creates a situation where bufB is pointing at the combined
  // buffer associated with bufA, but bufA is now using a different buffer.
  auto testSwap = [](bool resetAFirst) {
    auto bufA = IOBuf::createCombined(256);
    EXPECT_FALSE(bufA->isShared());
    auto bufB = bufA->clone();
    EXPECT_TRUE(bufA->isShared());
    EXPECT_TRUE(bufB->isShared());
    bufA->unshare();
    EXPECT_FALSE(bufA->isShared());
    EXPECT_FALSE(bufB->isShared());

    if (resetAFirst) {
      bufA.reset();
      bufB.reset();
    } else {
      bufB.reset();
      bufA.reset();
    }
  };
  testSwap(true);
  testSwap(false);
}

void fillBuf(uint8_t* buf, uint32_t length, std::mt19937& gen) {
  for (uint32_t n = 0; n < length; ++n) {
    buf[n] = static_cast<uint8_t>(gen() & 0xff);
  }
}

void fillBuf(IOBuf* buf, std::mt19937& gen) {
  buf->unshare();
  fillBuf(buf->writableData(), buf->length(), gen);
}

void checkBuf(const uint8_t* buf, uint32_t length, std::mt19937& gen) {
  // Rather than using EXPECT_EQ() to check each character,
  // count the number of differences and the first character that differs.
  // This way on error we'll report just that information, rather than tons of
  // failed checks for each byte in the buffer.
  uint32_t numDifferences = 0;
  uint32_t firstDiffIndex = 0;
  uint8_t firstDiffExpected = 0;
  for (uint32_t n = 0; n < length; ++n) {
    uint8_t expected = static_cast<uint8_t>(gen() & 0xff);
    if (buf[n] == expected) {
      continue;
    }

    if (numDifferences == 0) {
      firstDiffIndex = n;
      firstDiffExpected = expected;
    }
    ++numDifferences;
  }

  EXPECT_EQ(0, numDifferences);
  if (numDifferences > 0) {
    // Cast to int so it will be printed numerically
    // rather than as a char if the check fails
    EXPECT_EQ(
        static_cast<int>(buf[firstDiffIndex]),
        static_cast<int>(firstDiffExpected));
  }
}

void checkBuf(IOBuf* buf, std::mt19937& gen) {
  checkBuf(buf->data(), buf->length(), gen);
}

void checkBuf(ByteRange buf, std::mt19937& gen) {
  checkBuf(buf.data(), buf.size(), gen);
}

void checkChain(IOBuf* buf, std::mt19937& gen) {
  IOBuf* current = buf;
  do {
    checkBuf(current->data(), current->length(), gen);
    current = current->next();
  } while (current != buf);
}

TEST(IOBuf, Chaining) {
  uint32_t fillSeed = 0x12345678;
  std::mt19937 gen(fillSeed);

  // An IOBuf with external storage
  uint32_t headroom = 123;
  unique_ptr<IOBuf> iob1(IOBuf::create(2048));
  iob1->advance(headroom);
  iob1->append(1500);
  fillBuf(iob1.get(), gen);

  // An IOBuf with internal storage
  unique_ptr<IOBuf> iob2(IOBuf::create(20));
  iob2->append(20);
  fillBuf(iob2.get(), gen);

  // An IOBuf around a buffer it doesn't own
  uint8_t localbuf[1234];
  fillBuf(localbuf, 1234, gen);
  unique_ptr<IOBuf> iob3(IOBuf::wrapBuffer(localbuf, sizeof(localbuf)));

  // An IOBuf taking ownership of a user-supplied buffer
  uint32_t heapBufSize = 900;
  uint8_t* heapBuf = static_cast<uint8_t*>(malloc(heapBufSize));
  fillBuf(heapBuf, heapBufSize, gen);
  unique_ptr<IOBuf> iob4(IOBuf::takeOwnership(heapBuf, heapBufSize));

  // An IOBuf taking ownership of a user-supplied buffer with
  // a custom free function
  uint32_t arrayBufSize = 321;
  uint8_t* arrayBuf = new uint8_t[arrayBufSize];
  fillBuf(arrayBuf, arrayBufSize, gen);
  uint32_t arrayBufFreeCount = 0;
  unique_ptr<IOBuf> iob5(IOBuf::takeOwnership(
      arrayBuf, arrayBufSize, deleteArrayBuffer, &arrayBufFreeCount));

  EXPECT_FALSE(iob1->isChained());
  EXPECT_FALSE(iob2->isChained());
  EXPECT_FALSE(iob3->isChained());
  EXPECT_FALSE(iob4->isChained());
  EXPECT_FALSE(iob5->isChained());

  EXPECT_FALSE(iob1->isSharedOne());
  EXPECT_FALSE(iob2->isSharedOne());
  EXPECT_TRUE(iob3->isSharedOne()); // since we own the buffer
  EXPECT_FALSE(iob4->isSharedOne());
  EXPECT_FALSE(iob5->isSharedOne());

  // Chain the buffers all together
  // Since we are going to relinquish ownership of iob2-5 to the chain,
  // store raw pointers to them so we can reference them later.
  IOBuf* iob2ptr = iob2.get();
  IOBuf* iob3ptr = iob3.get();
  IOBuf* iob4ptr = iob4.get();
  IOBuf* iob5ptr = iob5.get();

  iob1->prependChain(std::move(iob2));
  iob1->prependChain(std::move(iob4));
  iob2ptr->insertAfterThisOne(std::move(iob3));
  iob1->prependChain(std::move(iob5));

  EXPECT_EQ(iob2ptr, iob1->next());
  EXPECT_EQ(iob3ptr, iob2ptr->next());
  EXPECT_EQ(iob4ptr, iob3ptr->next());
  EXPECT_EQ(iob5ptr, iob4ptr->next());
  EXPECT_EQ(iob1.get(), iob5ptr->next());

  EXPECT_EQ(iob5ptr, iob1->prev());
  EXPECT_EQ(iob1.get(), iob2ptr->prev());
  EXPECT_EQ(iob2ptr, iob3ptr->prev());
  EXPECT_EQ(iob3ptr, iob4ptr->prev());
  EXPECT_EQ(iob4ptr, iob5ptr->prev());

  EXPECT_TRUE(iob1->isChained());
  EXPECT_TRUE(iob2ptr->isChained());
  EXPECT_TRUE(iob3ptr->isChained());
  EXPECT_TRUE(iob4ptr->isChained());
  EXPECT_TRUE(iob5ptr->isChained());

  std::size_t fullLength =
      (iob1->length() + iob2ptr->length() + iob3ptr->length() +
       iob4ptr->length() + iob5ptr->length());
  EXPECT_EQ(5, iob1->countChainElements());
  EXPECT_EQ(fullLength, iob1->computeChainDataLength());

  // Since iob3 is shared, the entire buffer should report itself as shared
  EXPECT_TRUE(iob1->isShared());
  // Unshare just iob3
  iob3ptr->unshareOne();
  EXPECT_FALSE(iob3ptr->isSharedOne());
  // Now everything in the chain should be unshared.
  // Check on all members of the chain just for good measure
  EXPECT_FALSE(iob1->isShared());
  EXPECT_FALSE(iob2ptr->isShared());
  EXPECT_FALSE(iob3ptr->isShared());
  EXPECT_FALSE(iob4ptr->isShared());
  EXPECT_FALSE(iob5ptr->isShared());

  // Check iteration
  gen.seed(fillSeed);
  size_t count = 0;
  for (auto buf : *iob1) {
    checkBuf(buf, gen);
    ++count;
  }
  EXPECT_EQ(5, count);

  // Clone one of the IOBufs in the chain
  unique_ptr<IOBuf> iob4clone = iob4ptr->cloneOne();
  gen.seed(fillSeed);
  checkBuf(iob1.get(), gen);
  checkBuf(iob2ptr, gen);
  checkBuf(iob3ptr, gen);
  checkBuf(iob4clone.get(), gen);
  checkBuf(iob5ptr, gen);

  EXPECT_TRUE(iob1->isShared());
  EXPECT_TRUE(iob2ptr->isShared());
  EXPECT_TRUE(iob3ptr->isShared());
  EXPECT_TRUE(iob4ptr->isShared());
  EXPECT_TRUE(iob5ptr->isShared());

  EXPECT_FALSE(iob1->isSharedOne());
  EXPECT_FALSE(iob2ptr->isSharedOne());
  EXPECT_FALSE(iob3ptr->isSharedOne());
  EXPECT_TRUE(iob4ptr->isSharedOne());
  EXPECT_FALSE(iob5ptr->isSharedOne());

  // Unshare that clone
  EXPECT_TRUE(iob4clone->isSharedOne());
  iob4clone->unshare();
  EXPECT_FALSE(iob4clone->isSharedOne());
  EXPECT_FALSE(iob4ptr->isSharedOne());
  EXPECT_FALSE(iob1->isShared());
  iob4clone.reset();

  // Create a clone of a different IOBuf
  EXPECT_FALSE(iob1->isShared());
  EXPECT_FALSE(iob3ptr->isSharedOne());

  unique_ptr<IOBuf> iob3clone = iob3ptr->cloneOne();
  gen.seed(fillSeed);
  checkBuf(iob1.get(), gen);
  checkBuf(iob2ptr, gen);
  checkBuf(iob3clone.get(), gen);
  checkBuf(iob4ptr, gen);
  checkBuf(iob5ptr, gen);

  EXPECT_TRUE(iob1->isShared());
  EXPECT_TRUE(iob3ptr->isSharedOne());
  EXPECT_FALSE(iob1->isSharedOne());

  // Delete the clone and make sure the original is unshared
  iob3clone.reset();
  EXPECT_FALSE(iob1->isShared());
  EXPECT_FALSE(iob3ptr->isSharedOne());

  // Clone the entire chain
  unique_ptr<IOBuf> chainClone = iob1->clone();
  // Verify that the data is correct.
  EXPECT_EQ(fullLength, chainClone->computeChainDataLength());
  gen.seed(fillSeed);
  checkChain(chainClone.get(), gen);

  // Check that the buffers report sharing correctly
  EXPECT_TRUE(chainClone->isShared());
  EXPECT_TRUE(iob1->isShared());

  EXPECT_TRUE(iob1->isSharedOne());
  // Also verify the share count is consistent:
  EXPECT_LT(1, iob1->approximateShareCountOne());
  EXPECT_TRUE(iob2ptr->isSharedOne());
  EXPECT_TRUE(iob3ptr->isSharedOne());
  EXPECT_TRUE(iob4ptr->isSharedOne());
  EXPECT_TRUE(iob5ptr->isSharedOne());

  // Unshare the cloned chain
  chainClone->unshare();
  EXPECT_FALSE(chainClone->isShared());
  EXPECT_FALSE(iob1->isShared());
  // Also verify the share count is consistent:
  EXPECT_EQ(1, iob1->approximateShareCountOne());

  // Make sure the unshared result still has the same data
  EXPECT_EQ(fullLength, chainClone->computeChainDataLength());
  gen.seed(fillSeed);
  checkChain(chainClone.get(), gen);

  // Destroy this chain
  chainClone.reset();

  // Clone a new chain
  EXPECT_FALSE(iob1->isShared());
  chainClone = iob1->clone();
  EXPECT_TRUE(iob1->isShared());
  EXPECT_TRUE(chainClone->isShared());

  // Delete the original chain
  iob1.reset();
  EXPECT_FALSE(chainClone->isShared());

  // Coalesce the chain
  //
  // Coalescing this chain will create a new buffer and release the last
  // refcount on the original buffers we created.  Also make sure
  // that arrayBufFreeCount increases to one to indicate that arrayBuf was
  // freed.
  EXPECT_EQ(5, chainClone->countChainElements());
  EXPECT_EQ(0, arrayBufFreeCount);

  // Buffer lengths: 1500 20 1234 900 321
  // Attempting to gather more data than available should fail
  EXPECT_THROW(chainClone->gather(4000), std::overflow_error);
  // Coalesce the first 3 buffers
  chainClone->gather(1521);
  EXPECT_EQ(3, chainClone->countChainElements());
  EXPECT_EQ(0, arrayBufFreeCount);

  // Make sure the data is still the same after coalescing
  EXPECT_EQ(fullLength, chainClone->computeChainDataLength());
  gen.seed(fillSeed);
  checkChain(chainClone.get(), gen);

  // cloneCoalesced
  {
    auto chainCloneCoalesced = chainClone->cloneCoalesced();
    EXPECT_EQ(1, chainCloneCoalesced->countChainElements());
    EXPECT_EQ(fullLength, chainCloneCoalesced->computeChainDataLength());
    gen.seed(fillSeed);
    checkChain(chainCloneCoalesced.get(), gen);
  }

  // Coalesce the entire chain
  chainClone->coalesce();
  EXPECT_EQ(1, chainClone->countChainElements());
  EXPECT_EQ(1, arrayBufFreeCount);

  // Make sure the data is still the same after coalescing
  EXPECT_EQ(fullLength, chainClone->computeChainDataLength());
  gen.seed(fillSeed);
  checkChain(chainClone.get(), gen);

  // Make a new chain to test the unlink and pop operations
  iob1 = IOBuf::create(1);
  iob1->append(1);
  IOBuf* iob1ptr = iob1.get();
  iob2 = IOBuf::create(3);
  iob2->append(3);
  iob2ptr = iob2.get();
  iob3 = IOBuf::create(5);
  iob3->append(5);
  iob3ptr = iob3.get();
  iob4 = IOBuf::create(7);
  iob4->append(7);
  iob4ptr = iob4.get();
  iob1->insertAfterThisOne(std::move(iob2));
  iob1->prev()->insertAfterThisOne(std::move(iob3));
  iob1->prev()->insertAfterThisOne(std::move(iob4));
  EXPECT_EQ(4, iob1->countChainElements());
  EXPECT_EQ(16, iob1->computeChainDataLength());

  // Unlink from the middle of the chain
  iob3 = iob3ptr->unlink();
  EXPECT_TRUE(iob3.get() == iob3ptr);
  EXPECT_EQ(3, iob1->countChainElements());
  EXPECT_EQ(11, iob1->computeChainDataLength());

  // Unlink from the end of the chain
  iob4 = iob1->prev()->unlink();
  EXPECT_TRUE(iob4.get() == iob4ptr);
  EXPECT_EQ(2, iob1->countChainElements());
  EXPECT_TRUE(iob1->next() == iob2ptr);
  EXPECT_EQ(4, iob1->computeChainDataLength());

  // Pop from the front of the chain
  iob2 = iob1->pop();
  EXPECT_TRUE(iob1.get() == iob1ptr);
  EXPECT_EQ(1, iob1->countChainElements());
  EXPECT_EQ(1, iob1->computeChainDataLength());
  EXPECT_TRUE(iob2.get() == iob2ptr);
  EXPECT_EQ(1, iob2->countChainElements());
  EXPECT_EQ(3, iob2->computeChainDataLength());
}

void testFreeFn(void* buffer, void* ptr) {
  uint32_t* freeCount = static_cast<uint32_t*>(ptr);
  ;
  delete[] static_cast<uint8_t*>(buffer);
  if (freeCount) {
    ++(*freeCount);
  }
}

TEST(IOBuf, Reserve) {
  uint32_t fillSeed = 0x23456789;
  std::mt19937 gen(fillSeed);

  // Reserve does nothing if empty and doesn't have to grow the buffer
  {
    gen.seed(fillSeed);
    unique_ptr<IOBuf> iob(IOBuf::create(2000));
    EXPECT_EQ(0, iob->headroom());
    const void* p1 = iob->buffer();
    iob->reserve(5, 15);
    EXPECT_LE(5, iob->headroom());
    EXPECT_EQ(p1, iob->buffer());
  }

  // Reserve doesn't reallocate if we have enough total room
  {
    gen.seed(fillSeed);
    unique_ptr<IOBuf> iob(IOBuf::create(2000));
    iob->append(100);
    fillBuf(iob.get(), gen);
    EXPECT_EQ(0, iob->headroom());
    EXPECT_EQ(100, iob->length());
    const void* p1 = iob->buffer();
    const uint8_t* d1 = iob->data();
    iob->reserve(100, 1800);
    EXPECT_LE(100, iob->headroom());
    EXPECT_EQ(p1, iob->buffer());
    EXPECT_EQ(d1 + 100, iob->data());
    gen.seed(fillSeed);
    checkBuf(iob.get(), gen);
  }

  // Reserve reallocates if we don't have enough total room.
  // NOTE that, with jemalloc, we know that this won't reallocate in place
  // as the size is less than jemallocMinInPlaceExpanadable
  {
    gen.seed(fillSeed);
    unique_ptr<IOBuf> iob(IOBuf::create(2000));
    iob->append(100);
    fillBuf(iob.get(), gen);
    EXPECT_EQ(0, iob->headroom());
    EXPECT_EQ(100, iob->length());
    const void* p1 = iob->buffer();
    iob->reserve(100, 2512); // allocation sizes are multiples of 256
    EXPECT_LE(100, iob->headroom());
    if (folly::usingJEMalloc()) {
      EXPECT_NE(p1, iob->buffer());
    }
    gen.seed(fillSeed);
    checkBuf(iob.get(), gen);
  }

  // Test reserve from internal buffer, this used to segfault
  {
    unique_ptr<IOBuf> iob(IOBuf::create(0));
    iob->reserve(0, 2000);
    EXPECT_EQ(0, iob->headroom());
    EXPECT_LE(2000, iob->tailroom());
  }

  // Test reserving from a user-allocated buffer.
  {
    uint8_t* buf = static_cast<uint8_t*>(malloc(100));
    auto iob = IOBuf::takeOwnership(buf, 100);
    iob->reserve(0, 2000);
    EXPECT_EQ(0, iob->headroom());
    EXPECT_LE(2000, iob->tailroom());
  }

  // Test reserving from a user-allocated with a custom free function.
  {
    uint32_t freeCount{0};
    uint8_t* buf = new uint8_t[100];
    auto iob = IOBuf::takeOwnership(buf, 100, testFreeFn, &freeCount);
    iob->reserve(0, 2000);
    EXPECT_EQ(0, iob->headroom());
    EXPECT_LE(2000, iob->tailroom());
    EXPECT_EQ(1, freeCount);
  }
}

TEST(IOBuf, copyBuffer) {
  std::string s("hello");
  auto buf = IOBuf::copyBuffer(s.data(), s.size(), 1, 2);
  EXPECT_EQ(1, buf->headroom());
  EXPECT_EQ(
      s,
      std::string(reinterpret_cast<const char*>(buf->data()), buf->length()));
  EXPECT_LE(2, buf->tailroom());

  buf = IOBuf::copyBuffer(s, 5, 7);
  EXPECT_EQ(5, buf->headroom());
  EXPECT_EQ(
      s,
      std::string(reinterpret_cast<const char*>(buf->data()), buf->length()));
  EXPECT_LE(7, buf->tailroom());

  std::string empty;
  buf = IOBuf::copyBuffer(empty, 3, 6);
  EXPECT_EQ(3, buf->headroom());
  EXPECT_EQ(0, buf->length());
  EXPECT_LE(6, buf->tailroom());

  // A stack-allocated version
  IOBuf stackBuf(IOBuf::COPY_BUFFER, s, 1, 2);
  EXPECT_EQ(1, stackBuf.headroom());
  EXPECT_EQ(
      s,
      std::string(
          reinterpret_cast<const char*>(stackBuf.data()), stackBuf.length()));
  EXPECT_LE(2, stackBuf.tailroom());
}

TEST(IOBuf, maybeCopyBuffer) {
  std::string s("this is a test");
  auto buf = IOBuf::maybeCopyBuffer(s, 1, 2);
  EXPECT_EQ(1, buf->headroom());
  EXPECT_EQ(
      s,
      std::string(reinterpret_cast<const char*>(buf->data()), buf->length()));
  EXPECT_LE(2, buf->tailroom());

  std::string empty;
  buf = IOBuf::maybeCopyBuffer("", 5, 7);
  EXPECT_EQ(nullptr, buf.get());

  buf = IOBuf::maybeCopyBuffer("");
  EXPECT_EQ(nullptr, buf.get());
}

TEST(IOBuf, copyEmptyBuffer) {
  auto buf = IOBuf::copyBuffer(nullptr, 0);
  EXPECT_EQ(buf->length(), 0);
}

namespace {

int customDeleterCount = 0;
int destructorCount = 0;
struct OwnershipTestClass {
  explicit OwnershipTestClass(int v = 0) : val(v) {}
  ~OwnershipTestClass() { ++destructorCount; }
  int val;
};

typedef std::function<void(OwnershipTestClass*)> CustomDeleter;

void customDelete(OwnershipTestClass* p) {
  ++customDeleterCount;
  delete p;
}

void customDeleteArray(OwnershipTestClass* p) {
  ++customDeleterCount;
  delete[] p;
}

} // namespace

TEST(IOBuf, takeOwnershipUniquePtr) {
  destructorCount = 0;
  { std::unique_ptr<OwnershipTestClass> p(new OwnershipTestClass()); }
  EXPECT_EQ(1, destructorCount);

  destructorCount = 0;
  { std::unique_ptr<OwnershipTestClass[]> p(new OwnershipTestClass[2]); }
  EXPECT_EQ(2, destructorCount);

  destructorCount = 0;
  {
    std::unique_ptr<OwnershipTestClass> p(new OwnershipTestClass());
    std::unique_ptr<IOBuf> buf(IOBuf::takeOwnership(std::move(p)));
    EXPECT_EQ(sizeof(OwnershipTestClass), buf->length());
    EXPECT_EQ(0, destructorCount);
  }
  EXPECT_EQ(1, destructorCount);

  destructorCount = 0;
  {
    std::unique_ptr<OwnershipTestClass[]> p(new OwnershipTestClass[2]);
    std::unique_ptr<IOBuf> buf(IOBuf::takeOwnership(std::move(p), 2));
    EXPECT_EQ(2 * sizeof(OwnershipTestClass), buf->length());
    EXPECT_EQ(0, destructorCount);
  }
  EXPECT_EQ(2, destructorCount);

  customDeleterCount = 0;
  destructorCount = 0;
  {
    std::unique_ptr<OwnershipTestClass, CustomDeleter> p(
        new OwnershipTestClass(), customDelete);
    std::unique_ptr<IOBuf> buf(IOBuf::takeOwnership(std::move(p)));
    EXPECT_EQ(sizeof(OwnershipTestClass), buf->length());
    EXPECT_EQ(0, destructorCount);
  }
  EXPECT_EQ(1, destructorCount);
  EXPECT_EQ(1, customDeleterCount);

  customDeleterCount = 0;
  destructorCount = 0;
  {
    std::unique_ptr<OwnershipTestClass[], CustomDeleter> p(
        new OwnershipTestClass[2], CustomDeleter(customDeleteArray));
    std::unique_ptr<IOBuf> buf(IOBuf::takeOwnership(std::move(p), 2));
    EXPECT_EQ(2 * sizeof(OwnershipTestClass), buf->length());
    EXPECT_EQ(0, destructorCount);
  }
  EXPECT_EQ(2, destructorCount);
  EXPECT_EQ(1, customDeleterCount);
}

TEST(IOBuf, Alignment) {
  size_t alignment = alignof(std::max_align_t);

  std::vector<size_t> sizes;
  sizes.reserve(259);
  for (size_t i = 0; i <= 256; i++) {
    sizes.push_back(i);
  }
  sizes.push_back(1024);
  sizes.push_back(1 << 10);

  for (size_t size : sizes) {
    auto buf = IOBuf::create(size);
    uintptr_t p = reinterpret_cast<uintptr_t>(buf->data());
    EXPECT_EQ(0, p & (alignment - 1)) << "size=" << size;
  }
}

TEST(TypedIOBuf, Simple) {
  auto buf = IOBuf::create(0);
  TypedIOBuf<std::size_t> typed(buf.get());
  const std::size_t n = 10000;
  typed.reserve(0, n);
  EXPECT_LE(n, typed.capacity());
  for (std::size_t i = 0; i < n; i++) {
    *typed.writableTail() = i;
    typed.append(1);
  }
  EXPECT_EQ(n, typed.length());
  for (std::size_t i = 0; i < n; i++) {
    EXPECT_EQ(i, typed.data()[i]);
  }
}
enum BufType {
  CREATE,
  TAKE_OWNERSHIP_MALLOC,
  TAKE_OWNERSHIP_CUSTOM,
  USER_OWNED,
};

// chain element size, number of elements in chain, shared
class MoveToFbStringTest
    : public ::testing::TestWithParam<std::tuple<int, int, bool, BufType>> {
 protected:
  void SetUp() override {
    elementSize_ = std::get<0>(GetParam());
    elementCount_ = std::get<1>(GetParam());
    shared_ = std::get<2>(GetParam());
    type_ = std::get<3>(GetParam());

    buf_ = makeBuf();
    for (int i = 0; i < elementCount_ - 1; ++i) {
      buf_->prependChain(makeBuf());
    }
    EXPECT_EQ(elementCount_, buf_->countChainElements());
    EXPECT_EQ(elementCount_ * elementSize_, buf_->computeChainDataLength());
    if (shared_) {
      buf2_ = buf_->clone();
      EXPECT_EQ(elementCount_, buf2_->countChainElements());
      EXPECT_EQ(elementCount_ * elementSize_, buf2_->computeChainDataLength());
    }
  }

  std::unique_ptr<IOBuf> makeBuf() {
    unique_ptr<IOBuf> buf;
    switch (type_) {
      case CREATE:
        buf = IOBuf::create(elementSize_);
        buf->append(elementSize_);
        break;
      case TAKE_OWNERSHIP_MALLOC: {
        void* data = malloc(elementSize_);
        if (!data) {
          throw std::bad_alloc();
        }
        buf = IOBuf::takeOwnership(data, elementSize_);
        break;
      }
      case TAKE_OWNERSHIP_CUSTOM: {
        uint8_t* data = new uint8_t[elementSize_];
        buf = IOBuf::takeOwnership(data, elementSize_, testFreeFn);
        break;
      }
      case USER_OWNED: {
        unique_ptr<uint8_t[]> data(new uint8_t[elementSize_]);
        buf = IOBuf::wrapBuffer(data.get(), elementSize_);
        ownedBuffers_.emplace_back(std::move(data));
        break;
      }
      default:
        throw std::invalid_argument("unexpected buffer type parameter");
    }
    memset(buf->writableData(), 'x', elementSize_);
    return buf;
  }

  void check(std::unique_ptr<IOBuf>& buf) {
    fbstring str = buf->moveToFbString();
    EXPECT_EQ(elementCount_ * elementSize_, str.size());
    EXPECT_EQ(elementCount_ * elementSize_, strspn(str.c_str(), "x"));
    EXPECT_EQ(0, buf->length());
    EXPECT_EQ(1, buf->countChainElements());
    EXPECT_EQ(0, buf->computeChainDataLength());
    EXPECT_FALSE(buf->isChained());
  }

  int elementSize_;
  int elementCount_;
  bool shared_;
  BufType type_;
  std::unique_ptr<IOBuf> buf_;
  std::unique_ptr<IOBuf> buf2_;
  std::vector<std::unique_ptr<uint8_t[]>> ownedBuffers_;
};

TEST_P(MoveToFbStringTest, Simple) {
  check(buf_);
  if (shared_) {
    check(buf2_);
  }
}

INSTANTIATE_TEST_SUITE_P(
    MoveToFbString,
    MoveToFbStringTest,
    ::testing::Combine(
        ::testing::Values(0, 1, 24, 256, 1 << 10, 1 << 20), // element size
        ::testing::Values(1, 2, 10), // element count
        ::testing::Bool(), // shared
        ::testing::Values(
            CREATE, TAKE_OWNERSHIP_MALLOC, TAKE_OWNERSHIP_CUSTOM, USER_OWNED)));

TEST(IOBuf, getIov) {
  uint32_t fillSeed = 0xdeadbeef;
  std::mt19937 gen(fillSeed);

  size_t len = 4096;
  size_t count = 32;
  auto buf = IOBuf::create(len + 1);
  buf->append(rand() % len + 1);
  fillBuf(buf.get(), gen);

  for (size_t i = 0; i < count - 1; i++) {
    auto buf2 = IOBuf::create(len + 1);
    buf2->append(rand() % len + 1);
    fillBuf(buf2.get(), gen);
    buf->prependChain(std::move(buf2));
  }
  EXPECT_EQ(count, buf->countChainElements());

  auto iov = buf->getIov();
  EXPECT_EQ(count, iov.size());

  IOBuf const* p = buf.get();
  for (size_t i = 0; i < count; i++, p = p->next()) {
    EXPECT_EQ(p->data(), iov[i].iov_base);
    EXPECT_EQ(p->length(), iov[i].iov_len);
  }

  // an empty buf should be skipped in the iov.
  buf->next()->clear();
  iov = buf->getIov();
  EXPECT_EQ(count - 1, iov.size());
  EXPECT_EQ(buf->next()->next()->data(), iov[1].iov_base);

  // same for the first one being empty
  buf->clear();
  iov = buf->getIov();
  EXPECT_EQ(count - 2, iov.size());
  EXPECT_EQ(buf->next()->next()->data(), iov[0].iov_base);

  // and the last one
  buf->prev()->clear();
  iov = buf->getIov();
  EXPECT_EQ(count - 3, iov.size());

  // test appending to an existing iovec array
  iov.clear();
  const char localBuf[] = "hello";
  iov.push_back({(void*)localBuf, sizeof(localBuf)});
  iov.push_back({(void*)localBuf, sizeof(localBuf)});
  buf->appendToIov(&iov);
  EXPECT_EQ(count - 1, iov.size());
  EXPECT_EQ(localBuf, iov[0].iov_base);
  EXPECT_EQ(localBuf, iov[1].iov_base);
  // The first two IOBufs were cleared, so the next iov entry
  // should be the third IOBuf in the chain.
  EXPECT_EQ(buf->next()->next()->data(), iov[2].iov_base);
}

TEST(IOBuf, wrapIov) {
  // Test wrapping IOVs
  constexpr folly::StringPiece hello = "hello";
  constexpr folly::StringPiece world = "world!";
  folly::fbvector<struct iovec> iov;
  iov.push_back({nullptr, 0});
  iov.push_back({(void*)hello.data(), hello.size()});
  iov.push_back({(void*)world.data(), world.size()});
  auto wrapped = IOBuf::wrapIov(iov.data(), iov.size());
  EXPECT_EQ(iov.size() - 1, wrapped->countChainElements());
  IOBuf const* w = wrapped.get();
  // skip the first iovec, which is empty/null, as it is ignored by
  // IOBuf::wrapIov
  for (size_t i = 0; i < wrapped->countChainElements(); ++i, w = w->next()) {
    EXPECT_EQ(w->data(), iov[i + 1].iov_base);
    EXPECT_EQ(w->length(), iov[i + 1].iov_len);
  }
}

TEST(IOBuf, takeOwnershipIov) {
  // Test taking IOVs ownership
  folly::fbvector<folly::StringPiece> words{"hello", "world!"};
  folly::fbvector<struct iovec> iov;
  iov.push_back({nullptr, 0});
  for (size_t i = 0; i < words.size(); i++) {
    iov.push_back({(void*)strdup(words[i].data()), words[i].size() + 1});
  }
  auto buf = IOBuf::takeOwnershipIov(iov.data(), iov.size());
  EXPECT_EQ(iov.size() - 1, buf->countChainElements());

  IOBuf const* b = buf.get();
  // skip the first iovec, which is empty/null, as it is ignored by
  // IOBuf::takeIovOwnership
  for (size_t i = 0; i < buf->countChainElements(); ++i, b = b->next()) {
    EXPECT_EQ(words[i], static_cast<const char*>(iov[i + 1].iov_base));
  }
}

TEST(IOBuf, wrapZeroLenIov) {
  folly::fbvector<struct iovec> iov;
  iov.push_back({nullptr, 0});
  iov.push_back({nullptr, 0});
  auto wrapped = IOBuf::wrapIov(iov.data(), iov.size());
  EXPECT_NE(nullptr, wrapped);
  EXPECT_EQ(wrapped->countChainElements(), 1);
  EXPECT_EQ(wrapped->length(), 0);

  wrapped = IOBuf::wrapIov(nullptr, 0);
  EXPECT_NE(nullptr, wrapped);
  EXPECT_EQ(wrapped->countChainElements(), 1);
  EXPECT_EQ(wrapped->length(), 0);
}

TEST(IOBuf, move) {
  // Default allocate an IOBuf on the stack
  IOBuf outerBuf;
  char data[] = "foobar";
  uint32_t length = sizeof(data);
  uint32_t actualCapacity{0};
  const void* ptr{nullptr};

  {
    // Create a small IOBuf on the stack.
    // Note that IOBufs created on the stack always use an external buffer.
    IOBuf b1(IOBuf::CREATE, 10);
    actualCapacity = b1.capacity();
    EXPECT_GE(actualCapacity, 10);
    EXPECT_EQ(0, b1.length());
    EXPECT_FALSE(b1.isShared());
    ptr = b1.data();
    ASSERT_TRUE(ptr != nullptr);
    memcpy(b1.writableTail(), data, length);
    b1.append(length);
    EXPECT_EQ(length, b1.length());

    // Use the move constructor
    IOBuf b2(std::move(b1));
    EXPECT_EQ(ptr, b2.data());
    EXPECT_EQ(length, b2.length());
    EXPECT_EQ(actualCapacity, b2.capacity());
    EXPECT_FALSE(b2.isShared());

    // Use the move assignment operator
    outerBuf = std::move(b2);
    // Close scope, destroying b1 and b2
    // (which are both be invalid now anyway after moving out of them)
  }

  EXPECT_EQ(ptr, outerBuf.data());
  EXPECT_EQ(length, outerBuf.length());
  EXPECT_EQ(actualCapacity, outerBuf.capacity());
  EXPECT_FALSE(outerBuf.isShared());
}

namespace {
std::unique_ptr<IOBuf> fromStr(StringPiece sp) {
  return IOBuf::copyBuffer(ByteRange(sp));
}

std::unique_ptr<IOBuf> seq(std::initializer_list<StringPiece> sps) {
  auto ret = IOBuf::create(0);
  for (auto sp : sps) {
    ret->prependChain(IOBuf::copyBuffer(ByteRange(sp)));
  }
  return ret;
}
} // namespace

TEST(IOBuf, HashAndEqual) {
  folly::IOBufEqualTo eq;
  folly::IOBufHash hash;

  EXPECT_TRUE(eq(nullptr, nullptr));
  EXPECT_EQ(0, hash(nullptr));

  auto empty = IOBuf::create(0);

  EXPECT_TRUE(eq(*empty, *empty));
  EXPECT_TRUE(eq(empty, empty));
  EXPECT_TRUE(eq(empty.get(), empty.get()));

  EXPECT_FALSE(eq(nullptr, empty));
  EXPECT_FALSE(eq(empty, nullptr));
  EXPECT_FALSE(eq(empty.get(), nullptr));

  EXPECT_EQ(hash(*empty), hash(empty));
  EXPECT_EQ(hash(*empty), hash(empty.get()));
  EXPECT_NE(0, hash(empty));
  EXPECT_NE(0, hash(empty.get()));

  auto a = fromStr("hello");

  EXPECT_TRUE(eq(*a, *a));
  EXPECT_TRUE(eq(a, a));
  EXPECT_TRUE(eq(a.get(), a.get()));

  EXPECT_FALSE(eq(nullptr, a));
  EXPECT_FALSE(eq(a, nullptr));
  EXPECT_FALSE(eq(a.get(), nullptr));

  EXPECT_EQ(hash(*a), hash(a));
  EXPECT_EQ(hash(*a), hash(a.get()));
  EXPECT_NE(0, hash(a));
  EXPECT_NE(0, hash(a.get()));

  auto b = fromStr("hello");

  EXPECT_TRUE(eq(*a, *b));
  EXPECT_TRUE(eq(a, b));
  EXPECT_TRUE(eq(a.get(), b.get()));

  EXPECT_EQ(hash(a), hash(b));
  EXPECT_EQ(hash(a.get()), hash(b.get()));

  auto c = fromStr("hellow");

  EXPECT_FALSE(eq(a, c));
  EXPECT_FALSE(eq(a.get(), c.get()));
  EXPECT_NE(hash(a), hash(c));
  EXPECT_NE(hash(a.get()), hash(c.get()));

  auto d = fromStr("world");

  EXPECT_FALSE(eq(a, d));
  EXPECT_FALSE(eq(a.get(), d.get()));
  EXPECT_NE(hash(a), hash(d));
  EXPECT_NE(hash(a.get()), hash(d.get()));

  auto e = fromStr("helloworld");
  auto f = fromStr("hello");
  f->prependChain(fromStr("wo"));
  f->prependChain(fromStr("rld"));

  EXPECT_TRUE(eq(e, f));
  EXPECT_TRUE(eq(e.get(), f.get()));
  EXPECT_EQ(hash(e), hash(f));
  EXPECT_EQ(hash(e.get()), hash(f.get()));
}

TEST(IOBuf, IOBufCompare) {
  folly::IOBufCompare op;
  auto n = std::unique_ptr<IOBuf>{};
  auto e = IOBuf::create(0);
  auto hello1 = seq({"hello"});
  auto hello2 = seq({"hel", "lo"});
  auto hello3 = seq({"he", "ll", "o"});
  auto hellow = seq({"hellow"});
  auto hellox = seq({"hellox"});

  EXPECT_EQ(ordering::eq, op(n, n));
  EXPECT_EQ(ordering::lt, op(n, e));
  EXPECT_EQ(ordering::gt, op(e, n));
  EXPECT_EQ(ordering::lt, op(e, hello1));
  EXPECT_EQ(ordering::gt, op(hello1, e));
  EXPECT_EQ(ordering::eq, op(hello1, hello1));
  EXPECT_EQ(ordering::eq, op(hello1, hello2));
  EXPECT_EQ(ordering::eq, op(hello1, hello3));
  EXPECT_EQ(ordering::lt, op(hello1, hellow));
  EXPECT_EQ(ordering::gt, op(hellow, hello1));
  EXPECT_EQ(ordering::lt, op(hellow, hellox));
  EXPECT_EQ(ordering::gt, op(hellox, hellow));
}

// reserveSlow() had a bug when reallocating the buffer in place. It would
// preserve old headroom if it's not too much (heuristically) but wouldn't
// adjust the requested amount of memory to account for that; the end result
// would be that reserve() would return with less tailroom than requested.
TEST(IOBuf, ReserveWithHeadroom) {
  // This is assuming jemalloc, where we know that 4096 and 8192 bytes are
  // valid (and consecutive) allocation sizes. We're hoping that our
  // 4096-byte buffer can be expanded in place to 8192 (in practice, this
  // usually happens).
  const char data[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit";
  constexpr size_t reservedSize = 24; // sizeof(SharedInfo)
  // chosen carefully so that the buffer is exactly 4096 bytes
  IOBuf buf(IOBuf::CREATE, 4096 - reservedSize);
  buf.advance(10);
  memcpy(buf.writableData(), data, sizeof(data));
  buf.append(sizeof(data));
  EXPECT_EQ(sizeof(data), buf.length());

  // Grow the buffer (hopefully in place); this would incorrectly reserve
  // the 10 bytes of headroom, giving us 10 bytes less than requested.
  size_t tailroom = 8192 - reservedSize - sizeof(data);
  buf.reserve(0, tailroom);
  EXPECT_LE(tailroom, buf.tailroom());
  EXPECT_EQ(sizeof(data), buf.length());
  EXPECT_EQ(0, memcmp(data, buf.data(), sizeof(data)));
}

TEST(IOBuf, CopyConstructorAndAssignmentOperator) {
  auto buf = IOBuf::create(4096);
  append(buf, "hello world");
  auto buf2 = IOBuf::create(4096);
  append(buf2, " goodbye");
  buf->prependChain(std::move(buf2));
  EXPECT_FALSE(buf->isShared());

  {
    auto copy = *buf;
    EXPECT_TRUE(buf->isShared());
    EXPECT_TRUE(copy.isShared());
    EXPECT_EQ((void*)buf->data(), (void*)copy.data());
    EXPECT_NE(buf->next(), copy.next()); // actually different buffers

    auto copy2 = *buf;
    copy2.coalesce();
    EXPECT_TRUE(buf->isShared());
    EXPECT_TRUE(copy.isShared());
    EXPECT_FALSE(copy2.isShared());

    auto p = reinterpret_cast<const char*>(copy2.data());
    EXPECT_EQ("hello world goodbye", std::string(p, copy2.length()));
  }

  EXPECT_FALSE(buf->isShared());

  {
    folly::IOBuf newBuf(folly::IOBuf::CREATE, 4096);
    EXPECT_FALSE(newBuf.isShared());

    auto newBufCopy = newBuf;
    EXPECT_TRUE(newBuf.isShared());
    EXPECT_TRUE(newBufCopy.isShared());

    newBufCopy = *buf;
    EXPECT_TRUE(buf->isShared());
    EXPECT_FALSE(newBuf.isShared());
    EXPECT_TRUE(newBufCopy.isShared());
  }

  EXPECT_FALSE(buf->isShared());
}

TEST(IOBuf, CloneAsValue) {
  auto buf = IOBuf::create(4096);
  append(buf, "hello world");
  {
    auto buf2 = IOBuf::create(4096);
    append(buf2, " goodbye");
    buf->prependChain(std::move(buf2));
    EXPECT_FALSE(buf->isShared());
  }

  {
    auto copy = buf->cloneOneAsValue();
    EXPECT_TRUE(buf->isShared());
    EXPECT_TRUE(copy.isShared());
    EXPECT_EQ((void*)buf->data(), (void*)copy.data());
    EXPECT_TRUE(buf->isChained());
    EXPECT_FALSE(copy.isChained());

    auto copy2 = buf->cloneAsValue();
    EXPECT_TRUE(buf->isShared());
    EXPECT_TRUE(copy.isShared());
    EXPECT_TRUE(copy2.isShared());
    EXPECT_TRUE(buf->isChained());
    EXPECT_TRUE(copy2.isChained());

    copy.unshareOne();
    EXPECT_TRUE(buf->isShared());
    EXPECT_FALSE(copy.isShared());
    EXPECT_NE((void*)buf->data(), (void*)copy.data());
    EXPECT_TRUE(copy2.isShared());

    auto p = reinterpret_cast<const char*>(copy.data());
    EXPECT_EQ("hello world", std::string(p, copy.length()));

    copy2.coalesce();
    EXPECT_FALSE(buf->isShared());
    EXPECT_FALSE(copy.isShared());
    EXPECT_FALSE(copy2.isShared());
    EXPECT_FALSE(copy2.isChained());

    auto p2 = reinterpret_cast<const char*>(copy2.data());
    EXPECT_EQ("hello world goodbye", std::string(p2, copy2.length()));
  }

  EXPECT_FALSE(buf->isShared());
}

namespace {
// Use with string literals only
std::unique_ptr<IOBuf> wrap(const char* str) {
  return IOBuf::wrapBuffer(str, strlen(str));
}

std::unique_ptr<IOBuf> copy(const char* str) {
  // At least 1KiB of tailroom, to ensure an external buffer
  return IOBuf::copyBuffer(str, strlen(str), 0, 1024);
}

char* writableStr(folly::IOBuf& buf) {
  return reinterpret_cast<char*>(buf.writableData());
}

} // namespace

TEST(IOBuf, ExternallyShared) {
  struct Item {
    Item(const char* src, size_t len) : size(len) {
      CHECK_LE(len, sizeof(buffer));
      memcpy(buffer, src, len);
    }
    uint32_t refcount{0};
    uint8_t size;
    char buffer[256];
  };

  auto hello = "hello";
  struct Item it(hello, strlen(hello));

  {
    auto freeFn = [](void* /* unused */, void* userData) {
      auto it2 = static_cast<struct Item*>(userData);
      it2->refcount--;
    };
    it.refcount++;
    auto buf1 = IOBuf::takeOwnership(it.buffer, it.size, freeFn, &it);
    EXPECT_TRUE(buf1->isManagedOne());
    EXPECT_FALSE(buf1->isSharedOne());

    buf1->markExternallyShared();
    EXPECT_TRUE(buf1->isSharedOne());

    {
      auto buf2 = buf1->clone();
      EXPECT_TRUE(buf2->isManagedOne());
      EXPECT_TRUE(buf2->isSharedOne());
      EXPECT_EQ(buf1->data(), buf2->data());
      EXPECT_EQ(it.refcount, 1);
    }
    EXPECT_EQ(it.refcount, 1);
  }
  EXPECT_EQ(it.refcount, 0);
}

TEST(IOBuf, Managed) {
  auto hello = "hello";
  auto buf1UP = wrap(hello);
  auto buf1 = buf1UP.get();
  EXPECT_FALSE(buf1->isManagedOne());
  auto buf2UP = copy("world");
  auto buf2 = buf2UP.get();
  EXPECT_TRUE(buf2->isManagedOne());
  auto buf3UP = wrap(hello);
  auto buf3 = buf3UP.get();
  auto buf4UP = buf2->clone();
  auto buf4 = buf4UP.get();

  // buf1 and buf3 share the same memory (but are unmanaged)
  EXPECT_FALSE(buf1->isManagedOne());
  EXPECT_FALSE(buf3->isManagedOne());
  EXPECT_TRUE(buf1->isSharedOne());
  EXPECT_TRUE(buf3->isSharedOne());
  EXPECT_EQ(buf1->data(), buf3->data());

  // buf2 and buf4 share the same memory (but are managed)
  EXPECT_TRUE(buf2->isManagedOne());
  EXPECT_TRUE(buf4->isManagedOne());
  EXPECT_TRUE(buf2->isSharedOne());
  EXPECT_TRUE(buf4->isSharedOne());
  EXPECT_EQ(buf2->data(), buf4->data());

  buf1->prependChain(std::move(buf2UP));
  buf1->prependChain(std::move(buf3UP));
  buf1->prependChain(std::move(buf4UP));

  EXPECT_EQ("helloworldhelloworld", buf1->to<std::string>());
  EXPECT_FALSE(buf1->isManaged());

  buf1->makeManaged();
  EXPECT_TRUE(buf1->isManaged());

  // buf1 and buf3 are now unshared (because they were unmanaged)
  EXPECT_TRUE(buf1->isManagedOne());
  EXPECT_TRUE(buf3->isManagedOne());
  EXPECT_FALSE(buf1->isSharedOne());
  EXPECT_FALSE(buf3->isSharedOne());
  EXPECT_NE(buf1->data(), buf3->data());

  // buf2 and buf4 are still shared
  EXPECT_TRUE(buf2->isManagedOne());
  EXPECT_TRUE(buf4->isManagedOne());
  EXPECT_TRUE(buf2->isSharedOne());
  EXPECT_TRUE(buf4->isSharedOne());
  EXPECT_EQ(buf2->data(), buf4->data());

  // And verify that the truth is what we expect: modify a byte in buf1 and
  // buf2, see that the change from buf1 is *not* reflected in buf3, but the
  // change from buf2 is reflected in buf4.
  writableStr(*buf1)[0] = 'j';
  writableStr(*buf2)[0] = 'x';
  EXPECT_EQ("jelloxorldhelloxorld", buf1->to<std::string>());
}

TEST(IOBuf, CoalesceEmptyBuffers) {
  auto b1 = IOBuf::takeOwnership(nullptr, 0);
  auto b2 = fromStr("hello");
  auto b3 = IOBuf::takeOwnership(nullptr, 0);

  b2->insertAfterThisOne(std::move(b3));
  b1->insertAfterThisOne(std::move(b2));

  auto br = b1->coalesce();

  EXPECT_TRUE(ByteRange(StringPiece("hello")) == br);
}

TEST(IOBuf, CloneCoalescedChain) {
  auto b = IOBuf::createChain(1000, 100);
  b->advance(10);
  const uint32_t fillSeed = 0x12345678;
  std::mt19937 gen(fillSeed);
  {
    auto c = b.get();
    std::size_t length = c->tailroom();
    do {
      length = std::min(length, c->tailroom());
      c->append(length--);
      fillBuf(c, gen);
      c = c->next();
    } while (c != b.get());
  }
  auto c = b->cloneCoalescedAsValue();
  EXPECT_FALSE(c.isChained()); // Not chained
  EXPECT_FALSE(c.isSharedOne()); // Not shared
  EXPECT_EQ(b->headroom(), c.headroom()); // Preserves headroom
  EXPECT_LE(b->prev()->tailroom(), c.tailroom()); // Preserves minimum tailroom
  EXPECT_EQ(b->computeChainDataLength(), c.length()); // Same length
  gen.seed(fillSeed);
  checkBuf(&c, gen); // Same contents

  auto newHeadroom = b->headroom() + 10;
  auto newTailroom = b->tailroom();
  c = b->cloneCoalescedAsValueWithHeadroomTailroom(newHeadroom, newTailroom);
  EXPECT_FALSE(c.isChained()); // Not chained
  EXPECT_GE(c.headroom(), newHeadroom);
  EXPECT_GE(c.tailroom(), newTailroom);
  gen.seed(fillSeed);
  checkBuf(&c, gen); // Same contents

  newHeadroom = b->headroom();
  newTailroom = b->tailroom() + 10;
  c = b->cloneCoalescedAsValueWithHeadroomTailroom(newHeadroom, newTailroom);
  EXPECT_FALSE(c.isChained()); // Not chained
  EXPECT_GE(c.headroom(), newHeadroom);
  EXPECT_GE(c.tailroom(), newTailroom);
  gen.seed(fillSeed);
  checkBuf(&c, gen); // Same contents
}

TEST(IOBuf, CloneCoalescedSingle) {
  auto b = IOBuf::create(1000);
  b->advance(10);
  b->append(900);
  const uint32_t fillSeed = 0x12345678;
  std::mt19937 gen(fillSeed);
  fillBuf(b.get(), gen);

  auto c = b->cloneCoalesced();
  EXPECT_FALSE(c->isChained()); // Not chained
  EXPECT_TRUE(c->isSharedOne()); // Shared
  EXPECT_EQ(b->buffer(), c->buffer());
  EXPECT_EQ(b->capacity(), c->capacity());
  EXPECT_EQ(b->data(), c->data());
  EXPECT_EQ(b->length(), c->length());

  auto newHeadroom = b->headroom() + 10;
  auto newTailroom = b->tailroom();
  c = b->cloneCoalescedWithHeadroomTailroom(newHeadroom, newTailroom);
  EXPECT_FALSE(c->isChained()); // Not chained
  EXPECT_EQ(
      ByteRange(c->data(), c->length()), ByteRange(b->data(), b->length()));
  EXPECT_GE(c->headroom(), newHeadroom);
  EXPECT_GE(c->tailroom(), newTailroom);

  newHeadroom = b->headroom();
  newTailroom = b->tailroom() + 10;
  c = b->cloneCoalescedWithHeadroomTailroom(newHeadroom, newTailroom);
  EXPECT_FALSE(c->isChained()); // Not chained
  EXPECT_EQ(
      ByteRange(c->data(), c->length()), ByteRange(b->data(), b->length()));
  EXPECT_GE(c->headroom(), newHeadroom);
  EXPECT_GE(c->tailroom(), newTailroom);
}

TEST(IOBuf, fillIov) {
  auto buf = IOBuf::create(4096);
  append(buf, "hello");
  auto buf2 = IOBuf::create(4096);
  append(buf2, "goodbye");
  auto buf3 = IOBuf::create(4096);
  append(buf3, "hello again");

  buf2->insertAfterThisOne(std::move(buf3));
  buf->insertAfterThisOne(std::move(buf2));

  constexpr size_t iovCount = 3;
  struct iovec vec[iovCount];
  auto res = buf->fillIov(vec, iovCount);

  EXPECT_EQ(iovCount, res.numIovecs);
  EXPECT_EQ(23, res.totalLength);
  EXPECT_EQ(
      "hello",
      std::string(
          reinterpret_cast<const char*>(vec[0].iov_base), vec[0].iov_len));
  EXPECT_EQ(
      "goodbye",
      std::string(
          reinterpret_cast<const char*>(vec[1].iov_base), vec[1].iov_len));
  EXPECT_EQ(
      "hello again",
      std::string(
          reinterpret_cast<const char*>(vec[2].iov_base), vec[2].iov_len));
}

TEST(IOBuf, fillIov2) {
  auto buf = IOBuf::create(4096);
  append(buf, "hello");
  auto buf2 = IOBuf::create(4096);
  append(buf2, "goodbye");
  auto buf3 = IOBuf::create(4096);
  append(buf2, "hello again");

  buf2->insertAfterThisOne(std::move(buf3));
  buf->insertAfterThisOne(std::move(buf2));

  constexpr size_t iovCount = 2;
  struct iovec vec[iovCount];
  auto res = buf->fillIov(vec, iovCount);

  EXPECT_EQ(0, res.numIovecs);
  EXPECT_EQ(0, res.totalLength);
}

TEST(IOBuf, FreeFn) {
  class IOBufFreeObserver {
   public:
    using Func = std::function<void()>;
    explicit IOBufFreeObserver(Func&& freeFunc, Func&& releaseFunc)
        : freeFunc_(std::move(freeFunc)),
          releaseFunc_(std::move(releaseFunc)) {}
    void afterFreeExtBuffer() const noexcept { freeFunc_(); }
    void afterReleaseExtBuffer() const noexcept { releaseFunc_(); }

   private:
    Func freeFunc_;
    Func releaseFunc_;
  };

  int freeVal = 0;
  int releaseVal = 0;
  IOBufFreeObserver observer(
      [&freeVal]() { freeVal += 1; }, [&releaseVal]() { releaseVal += 1; });

  // no observers
  { unique_ptr<IOBuf> iobuf(IOBuf::create(64)); }

  // one observer
  {
    unique_ptr<IOBuf> iobuf(IOBuf::create(64));

    EXPECT_TRUE(iobuf->appendSharedInfoObserver(observer));
  }
  EXPECT_EQ(freeVal, 1);
  EXPECT_EQ(releaseVal, 0);

  freeVal = 0;
  releaseVal = 0;
  // reserve
  {
    unique_ptr<IOBuf> iobuf(IOBuf::create(64));
    EXPECT_TRUE(iobuf->appendSharedInfoObserver(observer));
    iobuf->reserve(0, iobuf->capacity() + 1024);
    EXPECT_EQ(freeVal, 1);
    EXPECT_EQ(releaseVal, 0);
  }

  freeVal = 0;
  releaseVal = 0;
  // one observer - call moveToFbString
  {
    unique_ptr<IOBuf> iobuf(IOBuf::create(64 * 1024));

    EXPECT_TRUE(iobuf->appendSharedInfoObserver(observer));
    auto str = iobuf->moveToFbString();
  }
  EXPECT_EQ(freeVal, 0);
  EXPECT_EQ(releaseVal, 1);

  freeVal = 0;
  releaseVal = 0;
  // multiple observers
  {
    unique_ptr<IOBuf> iobuf(IOBuf::create(64));

    for (size_t i = 0; i < 3; i++) {
      EXPECT_TRUE(iobuf->appendSharedInfoObserver(observer));
    }
  }
  EXPECT_EQ(freeVal, 3);
  EXPECT_EQ(releaseVal, 0);
}

// Compute the chained capacity of a single non-chained IOBuf of capacity zero
TEST(IOBuf, computeChainCapacityOfZeroSizeIOBuf) {
  size_t size = 0;
  uint8_t* data = nullptr;

  // Create buffer of capacity 0
  unique_ptr<IOBuf> buf(IOBuf::wrapBuffer(data, size));

  EXPECT_EQ(buf->computeChainCapacity(), 0);
}

// Compute the chained capacity of a single non-chained IOBuf of capacity
// non-zero
TEST(IOBuf, computeChainCapacityOfNonZeroSizeIOBuf) {
  const size_t size = 20;
  uint8_t data[size] = {};

  // Create buffer of capacity 20
  unique_ptr<IOBuf> buf(IOBuf::wrapBuffer(data, size));

  EXPECT_EQ(buf->computeChainCapacity(), 20);
}

// Compute the chained capacity of a chained IOBuf with chains having a variety
// of zero and non-zero capacities.
TEST(IOBuf, computeChainCapacityOfMixedCapacityChainedIOBuf) {
  // Total capacity is 100
  uint8_t data1[20] = {};
  uint8_t data2[0] = {};
  uint8_t data3[60] = {};
  uint8_t data4[15] = {};
  uint8_t data5[0] = {};
  uint8_t data6[5] = {};

  // Create IOBuf at head of chain
  unique_ptr<IOBuf> buf(IOBuf::wrapBuffer(data1, sizeof(data1)));

  // Create IOBuf chains
  auto temp = buf.get();
  temp->insertAfterThisOne(IOBuf::wrapBuffer(data2, sizeof(data2)));
  temp = temp->next();
  temp->insertAfterThisOne(IOBuf::wrapBuffer(data3, sizeof(data3)));
  temp = temp->next();
  temp->insertAfterThisOne(IOBuf::wrapBuffer(data4, sizeof(data4)));
  temp = temp->next();
  temp->insertAfterThisOne(IOBuf::wrapBuffer(data5, sizeof(data5)));
  temp = temp->next();
  temp->insertAfterThisOne(IOBuf::wrapBuffer(data6, sizeof(data6)));

  EXPECT_EQ(buf->computeChainCapacity(), 100);
}

TEST(IOBuf, AppendTo) {
  using folly::range;

  IOBuf buf;
  EXPECT_EQ(buf.to<std::string>(), "");
  EXPECT_EQ(buf.toString(), "");

  auto temp = &buf;
  temp->insertAfterThisOne(IOBuf::copyBuffer("Hello"));
  temp = temp->next();
  temp->insertAfterThisOne(IOBuf::copyBuffer(" and"));
  temp = temp->next();
  temp->insertAfterThisOne(IOBuf::copyBuffer(" goodbye."));

  auto testAppendTo = [&](auto c) {
    const StringPiece kExpected = "Hello and goodbye.";
    EXPECT_EQ(StringPiece(range(buf.to<decltype(c)>())), kExpected);

    const StringPiece kPreviousData = "I was there before. ";
    c.insert(c.end(), kPreviousData.begin(), kPreviousData.end());
    buf.appendTo(c);
    EXPECT_EQ(StringPiece(range(c)), kPreviousData.str() + kExpected.str());
  };

  testAppendTo(std::string{});
  testAppendTo(fbstring{});
  testAppendTo(std::vector<char>{});
  testAppendTo(std::vector<unsigned char>{});
}

TEST(IOBuf, FromStringView) {
  auto literalHelloBuffer = IOBuf::copyBuffer("Hello");
  std::string hello("Hello");
  auto fromStringHelloBuffer = IOBuf::copyBuffer(hello);
  std::string_view hello2("Hello");
  auto fromStringViewHelloBuffer = IOBuf::copyBuffer(hello2);

  std::string fromLiteral;
  literalHelloBuffer->appendTo(fromLiteral);
  std::string fromString;
  fromStringHelloBuffer->appendTo(fromString);
  std::string fromStringView;
  fromStringViewHelloBuffer->appendTo(fromStringView);

  EXPECT_EQ(fromStringView, fromString);
  EXPECT_EQ(fromLiteral, fromString);
}

TEST(IOBuf, bufferTooLarge) {
  EXPECT_THROW(
      IOBuf::copyBuffer(StringPiece("Hello"), (size_t)0xFFFF'FFFF'FFFF'FFFE),
      std::length_error);
}

TEST(IOBuf, copyConstructBufferTooLarge) {
  auto buf = StringPiece("Hello");
  EXPECT_THROW(
      IOBuf(
          IOBuf::COPY_BUFFER,
          buf.data(),
          buf.size(),
          57,
          (size_t)0xFFFF'FFFF'FFFF'FFFE),
      std::bad_alloc);
}

TEST(IOBuf, MaybeSplitTail) {
  auto buf = IOBuf::create(256);
  append(buf, "Hello ");

  size_t tailroom1 = buf->tailroom();
  auto buf1 = buf->maybeSplitTail();
  EXPECT_EQ(buf->tailroom(), 0);
  EXPECT_EQ(buf1->tailroom(), tailroom1);
  EXPECT_EQ(
      reinterpret_cast<const void*>(buf->bufferEnd()),
      reinterpret_cast<const void*>(buf1->buffer()));
  EXPECT_TRUE(buf->isSharedOne());
  EXPECT_FALSE(buf1->isSharedOne());
  append(buf1, "world");

  size_t tailroom2 = buf1->tailroom();
  auto buf2 = buf1->maybeSplitTail();
  EXPECT_EQ(buf1->tailroom(), 0);
  EXPECT_EQ(buf2->tailroom(), tailroom2);
  EXPECT_EQ(
      reinterpret_cast<const void*>(buf1->bufferEnd()),
      reinterpret_cast<const void*>(buf2->buffer()));
  // This maybeSplitTail() doesn't make buf1 shared because buf is the original
  // owner, so buf2 refers to it as well.
  EXPECT_FALSE(buf1->isSharedOne());
  EXPECT_FALSE(buf2->isSharedOne());
  append(buf2, "!");

  buf = {};
  EXPECT_EQ(buf1->to<std::string>(), "world");
  buf1 = {};
  EXPECT_EQ(buf2->to<std::string>(), "!");

  // The original buffer is gone, but we can continue splitting its tail.
  auto buf3 = buf2->maybeSplitTail();
  append(buf3, "!!!1");
}