/* * Copyright 2013-present Facebook, Inc. * * 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. */ /* * The code in this file if adapated from the IOBuf of folly: * https://github.com/facebook/folly/blob/master/folly/io/IOBuf.h */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 TRANSPORT_GNU_DISABLE_WARNING("-Wshadow") #endif namespace utils { class MemBuf { public: enum CreateOp { CREATE }; enum WrapBufferOp { WRAP_BUFFER }; enum TakeOwnershipOp { TAKE_OWNERSHIP }; enum CopyBufferOp { COPY_BUFFER }; typedef void (*FreeFunction)(void* buf, void* userData); static std::unique_ptr create(std::size_t capacity); MemBuf(CreateOp, std::size_t capacity); /** * Create a new MemBuf, using a single memory allocation to allocate space * for both the MemBuf object and the data storage space. * * This saves one memory allocation. However, it can be wasteful if you * later need to grow the buffer using reserve(). If the buffer needs to be * reallocated, the space originally allocated will not be freed() until the * MemBuf object itself is also freed. (It can also be slightly wasteful in * some cases where you clone this MemBuf and then free the original MemBuf.) */ static std::unique_ptr createCombined(std::size_t capacity); /** * Create a new IOBuf, using separate memory allocations for the IOBuf object * for the IOBuf and the data storage space. * * This requires two memory allocations, but saves space in the long run * if you know that you will need to reallocate the data buffer later. */ static std::unique_ptr createSeparate(std::size_t capacity); /** * Allocate a new MemBuf chain with the requested total capacity, allocating * no more than maxBufCapacity to each buffer. */ static std::unique_ptr createChain(size_t totalCapacity, std::size_t maxBufCapacity); static std::unique_ptr takeOwnership(void* buf, std::size_t capacity, FreeFunction freeFn = nullptr, void* userData = nullptr, bool freeOnError = true) { return takeOwnership(buf, capacity, capacity, freeFn, userData, freeOnError); } MemBuf(TakeOwnershipOp op, void* buf, std::size_t capacity, FreeFunction freeFn = nullptr, void* userData = nullptr, bool freeOnError = true) : MemBuf(op, buf, capacity, capacity, freeFn, userData, freeOnError) {} static std::unique_ptr takeOwnership(void* buf, std::size_t capacity, std::size_t length, FreeFunction freeFn = nullptr, void* userData = nullptr, bool freeOnError = true); MemBuf(TakeOwnershipOp, void* buf, std::size_t capacity, std::size_t length, FreeFunction freeFn = nullptr, void* userData = nullptr, bool freeOnError = true); static std::unique_ptr wrapBuffer(const void* buf, std::size_t capacity); static MemBuf wrapBufferAsValue(const void* buf, std::size_t capacity) noexcept; MemBuf(WrapBufferOp op, const void* buf, std::size_t capacity) noexcept; /** * Convenience function to create a new MemBuf object that copies data from a * user-supplied buffer, optionally allocating a given amount of * headroom and tailroom. */ static std::unique_ptr copyBuffer(const void* buf, std::size_t size, std::size_t headroom = 0, std::size_t minTailroom = 0); MemBuf(CopyBufferOp op, const void* buf, std::size_t size, std::size_t headroom = 0, std::size_t minTailroom = 0); /** * Convenience function to free a chain of MemBufs held by a unique_ptr. */ static void destroy(std::unique_ptr&& data) { auto destroyer = std::move(data); } ~MemBuf(); bool empty() const; const uint8_t* data() const { return data_; } uint8_t* writableData() { return data_; } const uint8_t* tail() const { return data_ + length_; } uint8_t* writableTail() { return data_ + length_; } std::size_t length() const { return length_; } std::size_t headroom() const { return std::size_t(data_ - buffer()); } std::size_t tailroom() const { return std::size_t(bufferEnd() - tail()); } const uint8_t* buffer() const { return buf_; } uint8_t* writableBuffer() { return buf_; } const uint8_t* bufferEnd() const { return buf_ + capacity_; } std::size_t capacity() const { return capacity_; } MemBuf* next() { return next_; } const MemBuf* next() const { return next_; } MemBuf* prev() { return prev_; } const MemBuf* prev() const { return prev_; } /** * Shift the data forwards in the buffer. * * This shifts the data pointer forwards in the buffer to increase the * headroom. This is commonly used to increase the headroom in a newly * allocated buffer. * * The caller is responsible for ensuring that there is sufficient * tailroom in the buffer before calling advance(). * * If there is a non-zero data length, advance() will use memmove() to shift * the data forwards in the buffer. In this case, the caller is responsible * for making sure the buffer is unshared, so it will not affect other MemBufs * that may be sharing the same underlying buffer. */ void advance(std::size_t amount) { // In debug builds, assert if there is a problem. assert(amount <= tailroom()); if (length_ > 0) { memmove(data_ + amount, data_, length_); } data_ += amount; } /** * Shift the data backwards in the buffer. * * The caller is responsible for ensuring that there is sufficient headroom * in the buffer before calling retreat(). * * If there is a non-zero data length, retreat() will use memmove() to shift * the data backwards in the buffer. In this case, the caller is responsible * for making sure the buffer is unshared, so it will not affect other MemBufs * that may be sharing the same underlying buffer. */ void retreat(std::size_t amount) { // In debug builds, assert if there is a problem. assert(amount <= headroom()); if (length_ > 0) { memmove(data_ - amount, data_, length_); } data_ -= amount; } void prepend(std::size_t amount) { data_ -= amount; length_ += amount; } void append(std::size_t amount) { length_ += amount; } void trimStart(std::size_t amount) { data_ += amount; length_ -= amount; } void trimEnd(std::size_t amount) { length_ -= amount; } // Never call clear on cloned membuf sharing different // portions of the same underlying buffer. // Use the trim functions instead. void clear() { data_ = writableBuffer(); length_ = 0; } void reserve(std::size_t minHeadroom, std::size_t minTailroom) { // Maybe we don't need to do anything. if (headroom() >= minHeadroom && tailroom() >= minTailroom) { return; } // If the buffer is empty but we have enough total room (head + tail), // move the data_ pointer around. if (length() == 0 && headroom() + tailroom() >= minHeadroom + minTailroom) { data_ = writableBuffer() + minHeadroom; return; } // Bah, we have to do actual work. reserveSlow(minHeadroom, minTailroom); } bool isChained() const { assert((next_ == this) == (prev_ == this)); return next_ != this; } size_t countChainElements() const; std::size_t computeChainDataLength() const; void prependChain(std::unique_ptr&& iobuf); void appendChain(std::unique_ptr&& iobuf) { // Just use prependChain() on the next element in our chain next_->prependChain(std::move(iobuf)); } std::unique_ptr unlink() { next_->prev_ = prev_; prev_->next_ = next_; prev_ = this; next_ = this; return std::unique_ptr(this); } /** * Remove this MemBuf from its current chain and return a unique_ptr to * the MemBuf that formerly followed it in the chain. */ std::unique_ptr pop() { MemBuf* next = next_; next_->prev_ = prev_; prev_->next_ = next_; prev_ = this; next_ = this; return std::unique_ptr((next == this) ? nullptr : next); } /** * Remove a subchain from this chain. * * Remove the subchain starting at head and ending at tail from this chain. * * Returns a unique_ptr pointing to head. (In other words, ownership of the * head of the subchain is transferred to the caller.) If the caller ignores * the return value and lets the unique_ptr be destroyed, the subchain will * be immediately destroyed. * * The subchain referenced by the specified head and tail must be part of the * same chain as the current MemBuf, but must not contain the current MemBuf. * However, the specified head and tail may be equal to each other (i.e., * they may be a subchain of length 1). */ std::unique_ptr separateChain(MemBuf* head, MemBuf* tail) { assert(head != this); assert(tail != this); head->prev_->next_ = tail->next_; tail->next_->prev_ = head->prev_; head->prev_ = tail; tail->next_ = head; return std::unique_ptr(head); } /** * Return true if at least one of the MemBufs in this chain are shared, * or false if all of the MemBufs point to unique buffers. * * Use isSharedOne() to only check this MemBuf rather than the entire chain. */ bool isShared() const { const MemBuf* current = this; while (true) { if (current->isSharedOne()) { return true; } current = current->next_; if (current == this) { return false; } } } /** * Return true if all MemBufs in this chain are managed by the usual * refcounting mechanism (and so the lifetime of the underlying memory * can be extended by clone()). */ bool isManaged() const { const MemBuf* current = this; while (true) { if (!current->isManagedOne()) { return false; } current = current->next_; if (current == this) { return true; } } } /** * Return true if this MemBuf is managed by the usual refcounting mechanism * (and so the lifetime of the underlying memory can be extended by * cloneOne()). */ bool isManagedOne() const { return sharedInfo(); } /** * Return true if other MemBufs are also pointing to the buffer used by this * MemBuf, and false otherwise. * * If this MemBuf points at a buffer owned by another (non-MemBuf) part of the * code (i.e., if the MemBuf was created using wrapBuffer(), or was cloned * from such an MemBuf), it is always considered shared. * * This only checks the current MemBuf, and not other MemBufs in the chain. */ bool isSharedOne() const { // If this is a user-owned buffer, it is always considered shared if ((TRANSPORT_EXPECT_FALSE(!sharedInfo()))) { return true; } if ((TRANSPORT_EXPECT_FALSE(sharedInfo()->externallyShared))) { return true; } if ((TRANSPORT_EXPECT_TRUE(!(flags() & flag_maybe_shared)))) { return false; } // flag_maybe_shared is set, so we need to check the reference count. // (Checking the reference count requires an atomic operation, which is why // we prefer to only check flag_maybe_shared if possible.) bool shared = sharedInfo()->refcount.load(std::memory_order_acquire) > 1; if (!shared) { // we're the last one left clearFlags(flag_maybe_shared); } return shared; } /** * Ensure that this MemBuf has a unique buffer that is not shared by other * MemBufs. * * unshare() operates on an entire chain of MemBuf objects. If the chain is * shared, it may also coalesce the chain when making it unique. If the * chain is coalesced, subsequent MemBuf objects in the current chain will be * automatically deleted. * * Note that buffers owned by other (non-MemBuf) users are automatically * considered shared. * * Throws std::bad_alloc on error. On error the MemBuf chain will be * unmodified. * * Currently unshare may also throw std::overflow_error if it tries to * coalesce. (TODO: In the future it would be nice if unshare() were smart * enough not to coalesce the entire buffer if the data is too large. * However, in practice this seems unlikely to become an issue.) */ void unshare() { if (isChained()) { unshareChained(); } else { unshareOne(); } } /** * Ensure that this MemBuf has a unique buffer that is not shared by other * MemBufs. * * unshareOne() operates on a single MemBuf object. This MemBuf will have a * unique buffer after unshareOne() returns, but other MemBufs in the chain * may still be shared after unshareOne() returns. * * Throws std::bad_alloc on error. On error the MemBuf will be unmodified. */ void unshareOne() { if (isSharedOne()) { unshareOneSlow(); } } /** * Mark the underlying buffers in this chain as shared with external memory * management mechanism. This will make isShared() always returns true. * * This function is not thread-safe, and only safe to call immediately after * creating an MemBuf, before it has been shared with other threads. */ void markExternallyShared(); /** * Mark the underlying buffer that this MemBuf refers to as shared with * external memory management mechanism. This will make isSharedOne() always * returns true. * * This function is not thread-safe, and only safe to call immediately after * creating an MemBuf, before it has been shared with other threads. */ void markExternallySharedOne() { SharedInfo* info = sharedInfo(); if (info) { info->externallyShared = true; } } /** * Ensure that the memory that MemBufs in this chain refer to will continue to * be allocated for as long as the MemBufs of the chain (or any clone()s * created from this point onwards) is alive. * * This only has an effect for user-owned buffers (created with the * WRAP_BUFFER constructor or wrapBuffer factory function), in which case * those buffers are unshared. */ void makeManaged() { if (isChained()) { makeManagedChained(); } else { makeManagedOne(); } } /** * Ensure that the memory that this MemBuf refers to will continue to be * allocated for as long as this MemBuf (or any clone()s created from this * point onwards) is alive. * * This only has an effect for user-owned buffers (created with the * WRAP_BUFFER constructor or wrapBuffer factory function), in which case * those buffers are unshared. */ void makeManagedOne() { if (!isManagedOne()) { // We can call the internal function directly; unmanaged implies shared. unshareOneSlow(); } } // /** // * Coalesce this MemBuf chain into a single buffer. // * // * This method moves all of the data in this MemBuf chain into a single // * contiguous buffer, if it is not already in one buffer. After coalesce() // * returns, this MemBuf will be a chain of length one. Other MemBufs in // the // * chain will be automatically deleted. // * // * After coalescing, the MemBuf will have at least as much headroom as the // * first MemBuf in the chain, and at least as much tailroom as the last // MemBuf // * in the chain. // * // * Throws std::bad_alloc on error. On error the MemBuf chain will be // * unmodified. // * // * Returns ByteRange that points to the data MemBuf stores. // */ // ByteRange coalesce() { // const std::size_t newHeadroom = headroom(); // const std::size_t newTailroom = prev()->tailroom(); // return coalesceWithHeadroomTailroom(newHeadroom, newTailroom); // } // /** // * This is similar to the coalesce() method, except this allows to set a // * headroom and tailroom after coalescing. // * // * Returns ByteRange that points to the data MemBuf stores. // */ // ByteRange coalesceWithHeadroomTailroom( // std::size_t newHeadroom, // std::size_t newTailroom) { // if (isChained()) { // coalesceAndReallocate( // newHeadroom, computeChainDataLength(), this, newTailroom); // } // return ByteRange(data_, length_); // } /** * Ensure that this chain has at least maxLength bytes available as a * contiguous memory range. * * This method coalesces whole buffers in the chain into this buffer as * necessary until this buffer's length() is at least maxLength. * * After coalescing, the MemBuf will have at least as much headroom as the * first MemBuf in the chain, and at least as much tailroom as the last MemBuf * that was coalesced. * * Throws std::bad_alloc or std::overflow_error on error. On error the MemBuf * chain will be unmodified. Throws std::overflow_error if maxLength is * longer than the total chain length. * * Upon return, either enough of the chain was coalesced into a contiguous * region, or the entire chain was coalesced. That is, * length() >= maxLength || !isChained() is true. */ void gather(std::size_t maxLength) { if (!isChained() || length_ >= maxLength) { return; } coalesceSlow(maxLength); } /** * Return a new MemBuf chain sharing the same data as this chain. * * The new MemBuf chain will normally point to the same underlying data * buffers as the original chain. (The one exception to this is if some of * the MemBufs in this chain contain small internal data buffers which cannot * be shared.) */ std::unique_ptr clone() const; /** * Similar to clone(). But returns MemBuf by value rather than heap-allocating * it. */ MemBuf cloneAsValue() const; /** * Return a new MemBuf with the same data as this MemBuf. * * The new MemBuf returned will not be part of a chain (even if this MemBuf is * part of a larger chain). */ std::unique_ptr cloneOne() const; /** * Similar to cloneOne(). But returns MemBuf by value rather than * heap-allocating it. */ MemBuf cloneOneAsValue() const; /** * Return a new unchained MemBuf that may share the same data as this chain. * * If the MemBuf chain is not chained then the new MemBuf will point to the * same underlying data buffer as the original chain. Otherwise, it will clone * and coalesce the MemBuf chain. * * The new MemBuf will have at least as much headroom as the first MemBuf in * the chain, and at least as much tailroom as the last MemBuf in the chain. * * Throws std::bad_alloc on error. */ std::unique_ptr cloneCoalesced() const; /** * This is similar to the cloneCoalesced() method, except this allows to set a * headroom and tailroom for the new MemBuf. */ std::unique_ptr cloneCoalescedWithHeadroomTailroom( std::size_t newHeadroom, std::size_t newTailroom) const; /** * Similar to cloneCoalesced(). But returns MemBuf by value rather than * heap-allocating it. */ MemBuf cloneCoalescedAsValue() const; /** * This is similar to the cloneCoalescedAsValue() method, except this allows * to set a headroom and tailroom for the new MemBuf. */ MemBuf cloneCoalescedAsValueWithHeadroomTailroom( std::size_t newHeadroom, std::size_t newTailroom) const; /** * Similar to Clone(). But use other as the head node. Other nodes in the * chain (if any) will be allocted on heap. */ void cloneInto(MemBuf& other) const { other = cloneAsValue(); } /** * Similar to CloneOne(). But to fill an existing MemBuf instead of a new * MemBuf. */ void cloneOneInto(MemBuf& other) const { other = cloneOneAsValue(); } /** * Return an iovector suitable for e.g. writev() * * auto iov = buf->getIov(); * auto xfer = writev(fd, iov.data(), iov.size()); * * Naturally, the returned iovector is invalid if you modify the buffer * chain. */ std::vector getIov() const; /** * Update an existing iovec array with the MemBuf data. * * New iovecs will be appended to the existing vector; anything already * present in the vector will be left unchanged. * * Naturally, the returned iovec data will be invalid if you modify the * buffer chain. */ void appendToIov(std::vector* iov) const; /** * Fill an iovec array with the MemBuf data. * * Returns the number of iovec filled. If there are more buffer than * iovec, returns 0. This version is suitable to use with stack iovec * arrays. * * Naturally, the filled iovec data will be invalid if you modify the * buffer chain. */ size_t fillIov(struct iovec* iov, size_t len) const; /** * A helper that wraps a number of iovecs into an MemBuf chain. If count == * 0, then a zero length buf is returned. This function never returns * nullptr. */ static std::unique_ptr wrapIov(const iovec* vec, size_t count); /** * A helper that takes ownerships a number of iovecs into an MemBuf chain. If * count == 0, then a zero length buf is returned. This function never * returns nullptr. */ static std::unique_ptr takeOwnershipIov(const iovec* vec, size_t count, FreeFunction freeFn = nullptr, void* userData = nullptr, bool freeOnError = true); /* * Overridden operator new and delete. * These perform specialized memory management to help support * createCombined(), which allocates MemBuf objects together with the buffer * data. */ void* operator new(size_t size); void* operator new(size_t size, void* ptr); void operator delete(void* ptr); void operator delete(void* ptr, void* placement); // /** // * Iteration support: a chain of MemBufs may be iterated through using // * STL-style iterators over const ByteRanges. Iterators are only // invalidated // * if the MemBuf that they currently point to is removed. // */ // Iterator cbegin() const; // Iterator cend() const; // Iterator begin() const; // Iterator end() const; /** * Allocate a new null buffer. * * This can be used to allocate an empty MemBuf on the stack. It will have no * space allocated for it. This is generally useful only to later use move * assignment to fill out the MemBuf. */ MemBuf() noexcept; /** * Move constructor and assignment operator. * * In general, you should only ever move the head of an MemBuf chain. * Internal nodes in an MemBuf chain are owned by the head of the chain, and * should not be moved from. (Technically, nothing prevents you from moving * a non-head node, but the moved-to node will replace the moved-from node in * the chain. This has implications for ownership, since non-head nodes are * owned by the chain head. You are then responsible for relinquishing * ownership of the moved-to node, and manually deleting the moved-from * node.) * * With the move assignment operator, the destination of the move should be * the head of an MemBuf chain or a solitary MemBuf not part of a chain. If * the move destination is part of a chain, all other MemBufs in the chain * will be deleted. */ MemBuf(MemBuf&& other) noexcept; MemBuf& operator=(MemBuf&& other) noexcept; MemBuf(const MemBuf& other); MemBuf& operator=(const MemBuf& other); private: enum FlagsEnum : uintptr_t { // Adding any more flags would not work on 32-bit architectures, // as these flags are stashed in the least significant 2 bits of a // max-align-aligned pointer. flag_free_shared_info = 0x1, flag_maybe_shared = 0x2, flag_mask = flag_free_shared_info | flag_maybe_shared }; struct SharedInfo { SharedInfo(); SharedInfo(FreeFunction fn, void* arg); // A pointer to a function to call to free the buffer when the refcount // hits 0. If this is null, free() will be used instead. FreeFunction freeFn; void* userData; std::atomic refcount; bool externallyShared{false}; }; // Helper structs for use by operator new and delete struct HeapPrefix; struct HeapStorage; struct HeapFullStorage; /** * Create a new MemBuf pointing to an external buffer. * * The caller is responsible for holding a reference count for this new * MemBuf. The MemBuf constructor does not automatically increment the * reference count. */ struct InternalConstructor {}; // avoid conflicts MemBuf(InternalConstructor, uintptr_t flagsAndSharedInfo, uint8_t* buf, std::size_t capacity, uint8_t* data, std::size_t length) noexcept; void unshareOneSlow(); void unshareChained(); void makeManagedChained(); void coalesceSlow(); void coalesceSlow(size_t maxLength); // newLength must be the entire length of the buffers between this and // end (no truncation) void coalesceAndReallocate(size_t newHeadroom, size_t newLength, MemBuf* end, size_t newTailroom); void coalesceAndReallocate(size_t newLength, MemBuf* end) { coalesceAndReallocate(headroom(), newLength, end, end->prev_->tailroom()); } void decrementRefcount(); void reserveSlow(std::size_t minHeadroom, std::size_t minTailroom); void freeExtBuffer(); static size_t goodExtBufferSize(std::size_t minCapacity); static void initExtBuffer(uint8_t* buf, size_t mallocSize, SharedInfo** infoReturn, std::size_t* capacityReturn); static void allocExtBuffer(std::size_t minCapacity, uint8_t** bufReturn, SharedInfo** infoReturn, std::size_t* capacityReturn); static void releaseStorage(HeapStorage* storage, uint16_t freeFlags); static void freeInternalBuf(void* buf, void* userData); /* * Member variables */ /* * Links to the next and the previous MemBuf in this chain. * * The chain is circularly linked (the last element in the chain points back * at the head), and next_ and prev_ can never be null. If this MemBuf is the * only element in the chain, next_ and prev_ will both point to this. */ MemBuf* next_{this}; MemBuf* prev_{this}; /* * A pointer to the start of the data referenced by this MemBuf, and the * length of the data. * * This may refer to any subsection of the actual buffer capacity. */ uint8_t* data_{nullptr}; uint8_t* buf_{nullptr}; std::size_t length_{0}; std::size_t capacity_{0}; // Pack flags in least significant 2 bits, sharedInfo in the rest mutable uintptr_t flags_and_shared_info_{0}; static inline uintptr_t packFlagsAndSharedInfo(uintptr_t flags, SharedInfo* info) { uintptr_t uinfo = reinterpret_cast(info); return flags | uinfo; } inline SharedInfo* sharedInfo() const { return reinterpret_cast(flags_and_shared_info_ & ~flag_mask); } inline void setSharedInfo(SharedInfo* info) { uintptr_t uinfo = reinterpret_cast(info); flags_and_shared_info_ = (flags_and_shared_info_ & flag_mask) | uinfo; } inline uintptr_t flags() const { return flags_and_shared_info_ & flag_mask; } // flags_ are changed from const methods inline void setFlags(uintptr_t flags) const { flags_and_shared_info_ |= flags; } inline void clearFlags(uintptr_t flags) const { flags_and_shared_info_ &= ~flags; } inline void setFlagsAndSharedInfo(uintptr_t flags, SharedInfo* info) { flags_and_shared_info_ = packFlagsAndSharedInfo(flags, info); } struct DeleterBase { virtual ~DeleterBase() {} virtual void dispose(void* p) = 0; }; template struct UniquePtrDeleter : public DeleterBase { typedef typename UniquePtr::pointer Pointer; typedef typename UniquePtr::deleter_type Deleter; explicit UniquePtrDeleter(Deleter deleter) : deleter_(std::move(deleter)) {} void dispose(void* p) override { try { deleter_(static_cast(p)); delete this; } catch (...) { abort(); } } private: Deleter deleter_; }; static void freeUniquePtrBuffer(void* ptr, void* userData) { static_cast(userData)->dispose(ptr); } }; // template // typename std::enable_if< // detail::IsUniquePtrToSL::value, // std::unique_ptr>::type // MemBuf::takeOwnership(UniquePtr&& buf, size_t count) { // size_t size = count * sizeof(typename UniquePtr::element_type); // auto deleter = new UniquePtrDeleter(buf.get_deleter()); // return takeOwnership( // buf.release(), size, &MemBuf::freeUniquePtrBuffer, deleter); // } inline std::unique_ptr MemBuf::copyBuffer(const void* data, std::size_t size, std::size_t headroom, std::size_t minTailroom) { std::size_t capacity = headroom + size + minTailroom; std::unique_ptr buf = MemBuf::create(capacity); buf->advance(headroom); if (size != 0) { memcpy(buf->writableData(), data, size); } buf->append(size); return buf; } } // namespace utils