Commit 6b2c52f3 authored by Claire (Yue) Zhang's avatar Claire (Yue) Zhang Committed by Facebook Github Bot

Add boundary support into Cursor

Summary:
Added boundary support in folly/io/Cursor.h.

The bounded Cursor can have a boundary that is shorter than the underlying IOBuf. This gives a custom view of the IOBuf.

Reviewed By: spalamarchuk

Differential Revision: D16347838

fbshipit-source-id: 16d91e42e2acee788b31bc2a9b6cc8716b1d22f2
parent 7fe15ffe
...@@ -66,6 +66,17 @@ class CursorBase { ...@@ -66,6 +66,17 @@ class CursorBase {
} }
} }
CursorBase(BufType* buf, size_t len) : crtBuf_(buf), buffer_(buf) {
if (crtBuf_) {
crtPos_ = crtBegin_ = crtBuf_->data();
crtEnd_ = crtBuf_->tail();
if (crtPos_ + len < crtEnd_) {
crtEnd_ = crtPos_ + len;
}
remainingLen_ = len - (crtEnd_ - crtPos_);
}
}
/** /**
* Copy constructor. * Copy constructor.
* *
...@@ -79,7 +90,27 @@ class CursorBase { ...@@ -79,7 +90,27 @@ class CursorBase {
crtBegin_(cursor.crtBegin_), crtBegin_(cursor.crtBegin_),
crtEnd_(cursor.crtEnd_), crtEnd_(cursor.crtEnd_),
crtPos_(cursor.crtPos_), crtPos_(cursor.crtPos_),
absolutePos_(cursor.absolutePos_) {} 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 (crtPos_ + len < crtEnd_) {
crtEnd_ = crtPos_ + len;
}
remainingLen_ = len - (crtEnd_ - crtPos_);
}
/** /**
* Reset cursor to point to a new buffer. * Reset cursor to point to a new buffer.
...@@ -88,6 +119,7 @@ class CursorBase { ...@@ -88,6 +119,7 @@ class CursorBase {
crtBuf_ = buf; crtBuf_ = buf;
buffer_ = buf; buffer_ = buf;
absolutePos_ = 0; absolutePos_ = 0;
remainingLen_ = std::numeric_limits<size_t>::max();
if (crtBuf_) { if (crtBuf_) {
crtPos_ = crtBegin_ = crtBuf_->data(); crtPos_ = crtBegin_ = crtBuf_->data();
crtEnd_ = crtBuf_->tail(); crtEnd_ = crtBuf_->tail();
...@@ -122,14 +154,16 @@ class CursorBase { ...@@ -122,14 +154,16 @@ class CursorBase {
/** /**
* Return the space available until the end of the entire IOBuf chain. * Return the space available until the end of the entire IOBuf chain.
* For bounded Cursors, return the available space until the boundary.
*/ */
size_t totalLength() const { size_t totalLength() const {
if (crtBuf_ == buffer_) { size_t len = 0;
return crtBuf_->computeChainDataLength() - (crtPos_ - crtBegin_); const IOBuf* buf = crtBuf_->next();
while (buf != buffer_ && len < remainingLen_) {
len += buf->length();
buf = buf->next();
} }
CursorBase end(buffer_->prev()); return std::min(len, remainingLen_) + length();
end.crtPos_ = end.crtEnd_;
return end - *this;
} }
/** /**
...@@ -140,6 +174,9 @@ class CursorBase { ...@@ -140,6 +174,9 @@ class CursorBase {
* walks the minimal set of buffers in the chain to determine the result. * walks the minimal set of buffers in the chain to determine the result.
*/ */
bool canAdvance(size_t amount) const { bool canAdvance(size_t amount) const {
if (isBounded() && amount > remainingLen_ + length()) {
return false;
}
const IOBuf* nextBuf = crtBuf_; const IOBuf* nextBuf = crtBuf_;
size_t available = length(); size_t available = length();
do { do {
...@@ -165,11 +202,13 @@ class CursorBase { ...@@ -165,11 +202,13 @@ class CursorBase {
if (crtBuf_ == buffer_->prev()) { if (crtBuf_ == buffer_->prev()) {
return true; return true;
} }
if (isBounded() && remainingLen_ == 0) {
return true;
}
// We are at the end of a buffer, but it isn't the last buffer. // 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 // We might still be at the end if the remaining buffers in the chain are
// empty. // empty.
const IOBuf* buf = crtBuf_->next(); const IOBuf* buf = crtBuf_->next();
;
while (buf != buffer_) { while (buf != buffer_) {
if (buf->length() > 0) { if (buf->length() > 0) {
return false; return false;
...@@ -191,14 +230,24 @@ class CursorBase { ...@@ -191,14 +230,24 @@ class CursorBase {
auto* nextBuf = crtBuf_->next(); auto* nextBuf = crtBuf_->next();
while (nextBuf != buffer_) { while (nextBuf != buffer_) {
if (isBounded() && remainingLen_ == 0) {
crtPos_ = crtEnd_;
return;
}
absolutePos_ += crtEnd_ - crtBegin_; absolutePos_ += crtEnd_ - crtBegin_;
crtBuf_ = nextBuf; crtBuf_ = nextBuf;
nextBuf = crtBuf_->next(); nextBuf = crtBuf_->next();
crtBegin_ = crtBuf_->data(); crtBegin_ = crtBuf_->data();
crtPos_ = crtEnd_ = crtBuf_->tail(); crtEnd_ = crtBuf_->tail();
if (isBounded()) {
static_cast<Derived*>(this)->advanceDone(); if (crtBegin_ + remainingLen_ < crtEnd_) {
crtEnd_ = crtBegin_ + remainingLen_;
}
remainingLen_ -= crtEnd_ - crtBegin_;
}
crtPos_ = crtEnd_;
derived().advanceDone();
} }
} }
...@@ -523,7 +572,11 @@ class CursorBase { ...@@ -523,7 +572,11 @@ class CursorBase {
size_t len = 0; size_t len = 0;
if (otherBuf != crtBuf_) { if (otherBuf != crtBuf_) {
if (other.remainingLen_ == 0) {
len += otherBuf->tail() - other.crtPos_;
} else {
len += other.crtEnd_ - other.crtPos_; len += other.crtEnd_ - other.crtPos_;
}
for (otherBuf = otherBuf->next(); for (otherBuf = otherBuf->next();
otherBuf != crtBuf_ && otherBuf != other.buffer_; otherBuf != crtBuf_ && otherBuf != other.buffer_;
...@@ -566,13 +619,17 @@ class CursorBase { ...@@ -566,13 +619,17 @@ class CursorBase {
return len; return len;
} }
bool isBounded() const {
return remainingLen_ != std::numeric_limits<size_t>::max();
}
protected: protected:
void dcheckIntegrity() const { void dcheckIntegrity() const {
DCHECK(crtBegin_ <= crtPos_ && crtPos_ <= crtEnd_); DCHECK(crtBegin_ <= crtPos_ && crtPos_ <= crtEnd_);
DCHECK(crtBuf_ == nullptr || crtBegin_ == crtBuf_->data()); DCHECK(crtBuf_ == nullptr || crtBegin_ == crtBuf_->data());
DCHECK( DCHECK(
crtBuf_ == nullptr || crtBuf_ == nullptr ||
(std::size_t)(crtEnd_ - crtBegin_) == crtBuf_->length()); (std::size_t)(crtEnd_ - crtBegin_) <= crtBuf_->length());
} }
~CursorBase() {} ~CursorBase() {}
...@@ -583,7 +640,7 @@ class CursorBase { ...@@ -583,7 +640,7 @@ class CursorBase {
bool tryAdvanceBuffer() { bool tryAdvanceBuffer() {
BufType* nextBuf = crtBuf_->next(); BufType* nextBuf = crtBuf_->next();
if (UNLIKELY(nextBuf == buffer_)) { if (UNLIKELY(nextBuf == buffer_) || remainingLen_ == 0) {
crtPos_ = crtEnd_; crtPos_ = crtEnd_;
return false; return false;
} }
...@@ -592,7 +649,13 @@ class CursorBase { ...@@ -592,7 +649,13 @@ class CursorBase {
crtBuf_ = nextBuf; crtBuf_ = nextBuf;
crtPos_ = crtBegin_ = crtBuf_->data(); crtPos_ = crtBegin_ = crtBuf_->data();
crtEnd_ = crtBuf_->tail(); crtEnd_ = crtBuf_->tail();
static_cast<Derived*>(this)->advanceDone(); if (isBounded()) {
if (crtPos_ + remainingLen_ < crtEnd_) {
crtEnd_ = crtPos_ + remainingLen_;
}
remainingLen_ -= crtEnd_ - crtPos_;
}
derived().advanceDone();
return true; return true;
} }
...@@ -601,11 +664,14 @@ class CursorBase { ...@@ -601,11 +664,14 @@ class CursorBase {
crtPos_ = crtBegin_; crtPos_ = crtBegin_;
return false; return false;
} }
if (isBounded()) {
remainingLen_ += crtEnd_ - crtBegin_;
}
crtBuf_ = crtBuf_->prev(); crtBuf_ = crtBuf_->prev();
crtBegin_ = crtBuf_->data(); crtBegin_ = crtBuf_->data();
crtPos_ = crtEnd_ = crtBuf_->tail(); crtPos_ = crtEnd_ = crtBuf_->tail();
absolutePos_ -= crtEnd_ - crtBegin_; absolutePos_ -= crtEnd_ - crtBegin_;
static_cast<Derived*>(this)->advanceDone(); derived().advanceDone();
return true; return true;
} }
...@@ -622,8 +688,20 @@ class CursorBase { ...@@ -622,8 +688,20 @@ class CursorBase {
const uint8_t* crtEnd_{nullptr}; const uint8_t* crtEnd_{nullptr};
const uint8_t* crtPos_{nullptr}; const uint8_t* crtPos_{nullptr};
size_t absolutePos_{0}; 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()};
private: private:
Derived& derived() {
return static_cast<Derived&>(*this);
}
Derived const& derived() const {
return static_cast<const Derived&>(*this);
}
template <class T> template <class T>
FOLLY_NOINLINE T readSlow() { FOLLY_NOINLINE T readSlow() {
T val; T val;
...@@ -723,9 +801,16 @@ class Cursor : public detail::CursorBase<Cursor, const IOBuf> { ...@@ -723,9 +801,16 @@ class Cursor : public detail::CursorBase<Cursor, const IOBuf> {
explicit Cursor(const IOBuf* buf) explicit Cursor(const IOBuf* buf)
: detail::CursorBase<Cursor, const IOBuf>(buf) {} : detail::CursorBase<Cursor, const IOBuf>(buf) {}
explicit Cursor(const IOBuf* buf, size_t len)
: detail::CursorBase<Cursor, const IOBuf>(buf, len) {}
template <class OtherDerived, class OtherBuf> template <class OtherDerived, class OtherBuf>
explicit Cursor(const detail::CursorBase<OtherDerived, OtherBuf>& cursor) explicit Cursor(const detail::CursorBase<OtherDerived, OtherBuf>& cursor)
: detail::CursorBase<Cursor, const IOBuf>(cursor) {} : detail::CursorBase<Cursor, const IOBuf>(cursor) {}
template <class OtherDerived, class OtherBuf>
Cursor(const detail::CursorBase<OtherDerived, OtherBuf>& cursor, size_t len)
: detail::CursorBase<Cursor, const IOBuf>(cursor, len) {}
}; };
namespace detail { namespace detail {
...@@ -823,7 +908,10 @@ class RWCursor : public detail::CursorBase<RWCursor<access>, IOBuf>, ...@@ -823,7 +908,10 @@ class RWCursor : public detail::CursorBase<RWCursor<access>, IOBuf>,
template <class OtherDerived, class OtherBuf> template <class OtherDerived, class OtherBuf>
explicit RWCursor(const detail::CursorBase<OtherDerived, OtherBuf>& cursor) explicit RWCursor(const detail::CursorBase<OtherDerived, OtherBuf>& cursor)
: detail::CursorBase<RWCursor<access>, IOBuf>(cursor), : detail::CursorBase<RWCursor<access>, IOBuf>(cursor),
maybeShared_(true) {} maybeShared_(true) {
CHECK(!cursor.isBounded())
<< "Creating RWCursor from bounded Cursor is not allowed";
}
/** /**
* Gather at least n bytes contiguously into the current buffer, * Gather at least n bytes contiguously into the current buffer,
* by coalescing subsequent buffers from the chain as necessary. * by coalescing subsequent buffers from the chain as necessary.
......
...@@ -1210,3 +1210,288 @@ TEST(IOBuf, positionTracking) { ...@@ -1210,3 +1210,288 @@ TEST(IOBuf, positionTracking) {
EXPECT_EQ(0, cursor.getCurrentPosition()); EXPECT_EQ(0, cursor.getCurrentPosition());
EXPECT_EQ(30, cursor.totalLength()); EXPECT_EQ(30, cursor.totalLength());
} }
TEST(IOBuf, BoundedCursorSanity) {
// initialize bounded Cursor with length 0
std::unique_ptr<IOBuf> chain1(IOBuf::create(20));
chain1->append(10);
Cursor subC(chain1.get(), 0);
EXPECT_EQ(0, subC.totalLength());
EXPECT_EQ(0, subC.length());
EXPECT_EQ(0, subC - chain1.get());
EXPECT_TRUE(subC.isAtEnd());
EXPECT_FALSE(subC.canAdvance(1));
EXPECT_THROW(subC.skip(1), std::out_of_range);
// multi-item chain
chain1->appendChain(chain1->clone());
EXPECT_EQ(2, chain1->countChainElements());
EXPECT_EQ(20, chain1->computeChainDataLength());
Cursor subCurs1(chain1.get(), 0);
EXPECT_EQ(0, subCurs1.totalLength());
EXPECT_EQ(0, subCurs1.length());
EXPECT_TRUE(subCurs1.isAtEnd());
EXPECT_FALSE(subCurs1.canAdvance(1));
EXPECT_THROW(subCurs1.skip(1), std::out_of_range);
// initialize bounded Cursor with length greater than total IOBuf bytes
Cursor subCurs2(chain1.get(), 25);
EXPECT_EQ(20, subCurs2.totalLength());
EXPECT_FALSE(subCurs2.isAtEnd());
EXPECT_TRUE(subCurs2.canAdvance(1));
subCurs2.skip(10);
EXPECT_EQ(10, subCurs2.totalLength());
EXPECT_EQ(10, subCurs2 - chain1.get());
EXPECT_FALSE(subCurs2.isAtEnd());
subCurs2.skip(3);
EXPECT_EQ(7, subCurs2.totalLength());
EXPECT_EQ(13, subCurs2 - chain1.get());
EXPECT_EQ(7, subCurs2.skipAtMost(10));
EXPECT_TRUE(subCurs2.isAtEnd());
// initialize bounded Cursor with another bounded Cursor
Cursor subCurs3(chain1.get(), 16);
EXPECT_EQ(16, subCurs3.totalLength());
EXPECT_FALSE(subCurs3.isAtEnd());
subCurs3.skip(4);
EXPECT_EQ(12, subCurs3.totalLength());
EXPECT_FALSE(subCurs3.isAtEnd());
Cursor subCurs4(subCurs3, 9);
EXPECT_EQ(9, subCurs4.totalLength());
EXPECT_FALSE(subCurs4.isAtEnd());
EXPECT_EQ(0, subCurs3 - subCurs4);
subCurs4.skip(5);
EXPECT_EQ(4, subCurs4.totalLength());
EXPECT_FALSE(subCurs4.isAtEnd());
EXPECT_EQ(5, subCurs4 - subCurs3);
subCurs4.skip(4);
EXPECT_TRUE(subCurs4.isAtEnd());
EXPECT_EQ(0, subCurs4.totalLength());
// Initialize a bounded Cursor with length greater than the bytes left in the
// bounded Cursor in the ctor argument
EXPECT_THROW(Cursor(subCurs3, 20), std::out_of_range);
// Initialize a bounded Cursor with equal length as the bounded Cursor
// in the ctor arguments
Cursor subCurs5(subCurs3, 12);
EXPECT_EQ(12, subCurs5.totalLength());
EXPECT_FALSE(subCurs5.isAtEnd());
// Initialize a Cursor with bounded Cursor
Cursor subCurs6(chain1.get(), 17);
EXPECT_EQ(17, subCurs6.totalLength());
Cursor curs(subCurs6);
EXPECT_TRUE(curs.isBounded());
EXPECT_EQ(17, curs.totalLength());
EXPECT_EQ(0, curs - chain1.get());
EXPECT_FALSE(curs.isAtEnd());
curs.advanceToEnd();
EXPECT_EQ(0, curs.totalLength());
EXPECT_TRUE(curs.isAtEnd());
EXPECT_EQ(17, curs - chain1.get());
subCurs6.skip(7);
EXPECT_EQ(10, subCurs6.totalLength());
Cursor curs1(subCurs6);
EXPECT_TRUE(curs1.isBounded());
EXPECT_EQ(10, curs1.totalLength());
EXPECT_EQ(7, curs1 - chain1.get());
EXPECT_FALSE(curs1.isAtEnd());
// test reset
curs.reset(chain1.get());
EXPECT_FALSE(curs.isBounded());
EXPECT_EQ(20, curs.totalLength());
EXPECT_EQ(0, curs - chain1.get());
EXPECT_FALSE(curs.isAtEnd());
}
TEST(IOBuf, BoundedCursorOperators) {
// Test operators on a single-item chain
{
std::unique_ptr<IOBuf> chain1(IOBuf::create(20));
chain1->append(10);
Cursor subCurs1(chain1.get(), 8);
EXPECT_EQ(8, subCurs1.totalLength());
EXPECT_EQ(0, subCurs1 - chain1.get());
EXPECT_FALSE(subCurs1.isAtEnd());
subCurs1.skip(3);
EXPECT_EQ(5, subCurs1.totalLength());
EXPECT_EQ(3, subCurs1 - chain1.get());
EXPECT_FALSE(subCurs1.isAtEnd());
subCurs1.skip(5);
EXPECT_EQ(0, subCurs1.totalLength());
EXPECT_EQ(8, subCurs1 - chain1.get());
EXPECT_TRUE(subCurs1.isAtEnd());
Cursor curs1(chain1.get());
Cursor subCurs2(curs1, 6);
EXPECT_EQ(6, subCurs2.totalLength());
EXPECT_EQ(0, subCurs2 - chain1.get());
EXPECT_EQ(8, subCurs1 - subCurs2);
EXPECT_THROW(subCurs2 - subCurs1, std::out_of_range);
EXPECT_EQ(subCurs2.retreatAtMost(10), 0);
EXPECT_THROW(subCurs2.retreat(10), std::out_of_range);
subCurs2.advanceToEnd();
EXPECT_EQ(2, subCurs1 - subCurs2);
subCurs1.retreat(1);
EXPECT_EQ(1, subCurs1.totalLength());
EXPECT_EQ(subCurs1.retreatAtMost(10), 7);
EXPECT_EQ(0, subCurs1 - chain1.get());
EXPECT_EQ(8, subCurs1.totalLength());
EXPECT_EQ(6, subCurs2 - subCurs1);
subCurs1.advanceToEnd();
EXPECT_EQ(2, subCurs1 - subCurs2);
}
// Test cross-chain operations
{
std::unique_ptr<IOBuf> chain1(IOBuf::create(20));
chain1->append(10);
std::unique_ptr<IOBuf> chain2 = chain1->clone();
Cursor subCurs1(chain1.get(), 9);
Cursor subCurs2(chain2.get(), 9);
EXPECT_THROW(subCurs1 - subCurs2, std::out_of_range);
EXPECT_THROW(subCurs1 - chain2.get(), std::out_of_range);
}
// Test operations on multi-item chains
{
std::unique_ptr<IOBuf> iobuf1(IOBuf::create(20));
iobuf1->append(10);
std::unique_ptr<IOBuf> iobuf2(IOBuf::create(20));
iobuf2->append(10);
std::unique_ptr<IOBuf> iobuf3(IOBuf::create(20));
iobuf3->append(10);
iobuf1->prependChain(std::move(iobuf2));
iobuf1->prependChain(std::move(iobuf3));
EXPECT_EQ(3, iobuf1->countChainElements());
EXPECT_EQ(30, iobuf1->computeChainDataLength());
Cursor curs1(iobuf1.get());
Cursor subCurs1(curs1, 28);
subCurs1.skip(5);
EXPECT_EQ(23, subCurs1.totalLength());
EXPECT_EQ(5, subCurs1 - iobuf1.get());
EXPECT_FALSE(subCurs1.isAtEnd());
Cursor subCurs2(iobuf1.get(), 15);
subCurs2.skip(3);
EXPECT_EQ(12, subCurs2.totalLength());
EXPECT_EQ(2, subCurs1 - subCurs2);
EXPECT_THROW(subCurs2 - subCurs1, std::out_of_range);
subCurs1.skip(7);
EXPECT_EQ(9, subCurs1 - subCurs2);
EXPECT_EQ(12, subCurs1 - iobuf1.get());
EXPECT_FALSE(subCurs1.isAtEnd());
EXPECT_EQ(subCurs1.length(), 8);
EXPECT_THROW(subCurs2 - subCurs1, std::out_of_range);
subCurs2.skip(7);
EXPECT_EQ(5, subCurs2.totalLength());
EXPECT_EQ(10, subCurs2 - iobuf1.get());
EXPECT_FALSE(subCurs2.isAtEnd());
EXPECT_EQ(2, subCurs1 - subCurs2);
EXPECT_THROW(subCurs2 - subCurs1, std::out_of_range);
subCurs1.advanceToEnd();
EXPECT_EQ(28, subCurs1 - iobuf1.get());
EXPECT_EQ(18, subCurs1 - subCurs2);
EXPECT_TRUE(subCurs1.isAtEnd());
subCurs1.retreat(8);
EXPECT_FALSE(subCurs1.isAtEnd());
EXPECT_EQ(subCurs1.totalLength(), 8);
EXPECT_EQ(subCurs1.length(), 8);
EXPECT_EQ(20, subCurs1 - iobuf1.get());
EXPECT_EQ(10, subCurs1 - subCurs2);
subCurs1.retreat(1);
EXPECT_EQ(subCurs1.totalLength(), 9);
EXPECT_EQ(subCurs1.length(), 1);
EXPECT_EQ(19, subCurs1 - iobuf1.get());
EXPECT_EQ(subCurs1.retreatAtMost(20), 19);
EXPECT_EQ(subCurs1.totalLength(), 28);
EXPECT_EQ(0, subCurs1 - iobuf1.get());
EXPECT_EQ(10, subCurs2 - subCurs1);
EXPECT_THROW(subCurs1.retreat(1), std::out_of_range);
}
// Test canAdvance with a chain of items
{
auto chain = IOBuf::create(10);
chain->append(10);
chain->appendChain(chain->clone());
EXPECT_EQ(2, chain->countChainElements());
EXPECT_EQ(20, chain->computeChainDataLength());
Cursor c(chain.get());
Cursor subC(c, 14);
for (size_t i = 0; i <= 14; ++i) {
EXPECT_TRUE(subC.canAdvance(i));
}
EXPECT_FALSE(subC.canAdvance(15));
subC.skip(10);
EXPECT_TRUE(subC.canAdvance(4));
EXPECT_FALSE(subC.canAdvance(5));
}
}
TEST(IOBuf, BoundedCursorPullAndPeek) {
std::unique_ptr<IOBuf> iobuf1(IOBuf::create(10));
append(iobuf1, "he");
std::unique_ptr<IOBuf> iobuf2(IOBuf::create(10));
append(iobuf2, "llo ");
std::unique_ptr<IOBuf> iobuf3(IOBuf::create(10));
append(iobuf3, "world abc");
iobuf1->prependChain(std::move(iobuf2));
iobuf1->prependChain(std::move(iobuf3));
EXPECT_EQ(3, iobuf1->countChainElements());
EXPECT_EQ(15, iobuf1->computeChainDataLength());
std::array<char, 20> buf;
buf.fill(0);
Cursor subCurs1(iobuf1.get(), 13);
subCurs1.pull(buf.data(), 13);
EXPECT_EQ("hello world a", std::string(buf.data()));
EXPECT_TRUE(subCurs1.isAtEnd());
buf.fill(0);
Cursor subCurs2(iobuf1.get(), 11);
EXPECT_EQ(11, subCurs2.pullAtMost(buf.data(), 20));
EXPECT_EQ("hello world", std::string(buf.data()));
EXPECT_TRUE(subCurs2.isAtEnd());
buf.fill(0);
Cursor subCurs3(iobuf1.get(), 2);
EXPECT_EQ(2, subCurs3.totalLength());
EXPECT_EQ(0, subCurs3 - iobuf1.get());
EXPECT_EQ(2, subCurs3.pullAtMost(buf.data(), 20));
EXPECT_EQ("he", std::string(buf.data()));
EXPECT_TRUE(subCurs3.isAtEnd());
EXPECT_EQ(0, subCurs3.totalLength());
// the following test makes sure that tryAdvanceBuffer inside pullAtMostSlow
// won't go over the entire IOBuf chain, but terminate at the right boundary
EXPECT_EQ(2, subCurs3 - iobuf1.get());
EXPECT_THROW(
{ Cursor(iobuf1.get(), 11).pull(buf.data(), 20); }, std::out_of_range);
{
Cursor subCursor(iobuf1.get(), 13);
auto b = subCursor.peekBytes();
EXPECT_EQ("he", StringPiece(b));
subCursor.skip(b.size());
b = subCursor.peekBytes();
EXPECT_EQ("llo ", StringPiece(b));
subCursor.skip(b.size());
b = subCursor.peekBytes();
EXPECT_EQ("world a", StringPiece(b));
subCursor.skip(b.size());
EXPECT_EQ(3, iobuf1->countChainElements());
EXPECT_EQ(15, iobuf1->computeChainDataLength());
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment