[kernel] Add unit tests and comments for MBufChain
Add unit tests for MBufChain that make use of testable_user_ptr and
fake_user_ptr.
Add comments to MBufChain.
ZX-1846 #comment initial tests
ZX-1852 #comment prereq
Change-Id: I6626b5e05e56611006da3de0f8d7deae482fb474
diff --git a/kernel/object/include/object/mbuf.h b/kernel/object/include/object/mbuf.h
index ff82381..7e85564 100644
--- a/kernel/object/include/object/mbuf.h
+++ b/kernel/object/include/object/mbuf.h
@@ -8,20 +8,51 @@
#include <stdint.h>
-#include <lib/user_copy/user_ptr.h>
+#include <lib/user_copy/testable_user_ptr.h>
#include <zircon/types.h>
#include <fbl/intrusive_single_list.h>
+// MBufChain is a container for storing a stream of bytes or a sequence of datagrams.
+//
+// It's designed to back sockets and channels. Don't simultaneously store stream data and datagrams
+// in a single instance.
class MBufChain {
public:
MBufChain() = default;
~MBufChain();
- zx_status_t WriteStream(user_in_ptr<const void> src, size_t len, size_t* written);
- zx_status_t WriteDatagram(user_in_ptr<const void> src, size_t len, size_t* written);
- size_t Read(user_out_ptr<void> dst, size_t len, bool datagram);
+ // Writes |len| bytes of stream data from |src| and sets |written| to number of bytes written.
+ //
+ // Returns an error on failure.
+ template <typename UCT>
+ zx_status_t WriteStream(testable_user_in_ptr<const void, UCT> src, size_t len, size_t* written);
+
+ // Writes a datagram of |len| bytes from |src| and sets |written| to number of bytes written.
+ //
+ // This operation is atomic in that either the entire datagram is written successfully or the
+ // chain is unmodified.
+ //
+ // Returns an error on failure.
+ template <typename UCT>
+ zx_status_t WriteDatagram(testable_user_in_ptr<const void, UCT> src, size_t len,
+ size_t* written);
+
+ // Reads upto |len| bytes from chain into |dst|.
+ //
+ // When |datagram| is false, the data in the chain is treated as a stream (no boundaries).
+ //
+ // When |datagram| is true, the data in the chain is treated as a sequence of datagrams and the
+ // call will read at most one datagram. If |len| is too small to read a complete datagram, a
+ // partial datagram is returned and its remaining bytes are discarded.
+ //
+ // Returns number of bytes read.
+ template <typename UCT>
+ size_t Read(testable_user_out_ptr<void, UCT> dst, size_t len, bool datagram);
+
bool is_full() const;
bool is_empty() const;
+
+ // Returns number of bytes stored in the chain.
size_t size() const { return size_; }
private:
@@ -33,6 +64,7 @@
static constexpr size_t kMallocSize = 2048 - 16;
static constexpr size_t kPayloadSize = kMallocSize - kHeaderSize;
+ // Returns number of bytes of free space in this MBuf.
size_t rem() const;
uint32_t off_ = 0u;
diff --git a/kernel/object/mbuf.cpp b/kernel/object/mbuf.cpp
index 134df50..295e896 100644
--- a/kernel/object/mbuf.cpp
+++ b/kernel/object/mbuf.cpp
@@ -6,6 +6,7 @@
#include <object/mbuf.h>
+#include <lib/user_copy/fake_user_ptr.h>
#include <lib/user_copy/user_ptr.h>
#include <fbl/algorithm.h>
@@ -37,7 +38,8 @@
return size_ == 0;
}
-size_t MBufChain::Read(user_out_ptr<void> dst, size_t len, bool datagram) {
+template <typename UCT>
+size_t MBufChain::Read(testable_user_out_ptr<void, UCT> dst, size_t len, bool datagram) {
if (datagram && len > tail_.front().pkt_len_)
len = tail_.front().pkt_len_;
@@ -72,8 +74,9 @@
return pos;
}
-zx_status_t MBufChain::WriteDatagram(user_in_ptr<const void> src,
- size_t len, size_t* written) {
+template <typename UCT>
+zx_status_t MBufChain::WriteDatagram(testable_user_in_ptr<const void, UCT> src, size_t len,
+ size_t* written) {
if (len + size_ > kSizeMax)
return ZX_ERR_SHOULD_WAIT;
@@ -118,8 +121,8 @@
return ZX_OK;
}
-zx_status_t MBufChain::WriteStream(user_in_ptr<const void> src,
- size_t len, size_t* written) {
+template <typename UCT>
+zx_status_t MBufChain::WriteStream(testable_user_in_ptr<const void, UCT> src, size_t len, size_t* written) {
if (head_ == nullptr) {
head_ = AllocMBuf();
if (head_ == nullptr)
@@ -171,3 +174,16 @@
buf->len_ = 0u;
freelist_.push_front(buf);
}
+
+template zx_status_t MBufChain::WriteStream(user_in_ptr<const void> src, size_t len,
+ size_t* written);
+template zx_status_t MBufChain::WriteDatagram(user_in_ptr<const void> src, size_t len,
+ size_t* written);
+template size_t MBufChain::Read(user_out_ptr<void> dst, size_t len, bool datagram);
+
+template zx_status_t MBufChain::WriteStream(internal::testing::fake_user_in_ptr<const void> src,
+ size_t len, size_t* written);
+template zx_status_t MBufChain::WriteDatagram(internal::testing::fake_user_in_ptr<const void> src,
+ size_t len, size_t* written);
+template size_t MBufChain::Read(internal::testing::fake_user_out_ptr<void> dst, size_t len,
+ bool datagram);
diff --git a/kernel/object/mbuf_tests.cpp b/kernel/object/mbuf_tests.cpp
new file mode 100644
index 0000000..43e9fc0
--- /dev/null
+++ b/kernel/object/mbuf_tests.cpp
@@ -0,0 +1,284 @@
+// Copyright 2018 The Fuchsia Authors
+//
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT
+
+#include <object/mbuf.h>
+
+#include <lib/user_copy/fake_user_ptr.h>
+#include <fbl/unique_ptr.h>
+#include <unittest.h>
+
+using internal::testing::make_fake_user_out_ptr;
+using internal::testing::make_fake_user_in_ptr;
+
+namespace {
+
+static bool initial_state() {
+ BEGIN_TEST;
+ MBufChain chain;
+ EXPECT_TRUE(chain.is_empty(), "");
+ EXPECT_FALSE(chain.is_full(), "");
+ EXPECT_EQ(0U, chain.size(), "");
+ END_TEST;
+}
+
+// Tests reading a stream when the chain is empty.
+static bool stream_read_empty() {
+ BEGIN_TEST;
+ char buf[1] = {0};
+ MBufChain chain;
+ auto dst = make_fake_user_out_ptr(static_cast<void*>(buf));
+ EXPECT_EQ(0U, chain.Read(dst, sizeof(buf), false), "");
+ END_TEST;
+}
+
+// Tests reading a stream with a zero-length buffer.
+static bool stream_read_zero() {
+ BEGIN_TEST;
+ char buf[1] = {'A'};
+ auto src = make_fake_user_in_ptr(static_cast<const void*>(buf));
+ MBufChain chain;
+ size_t written = 7;
+ ASSERT_EQ(ZX_OK, chain.WriteStream(src, 1, &written), "");
+ ASSERT_EQ(1U, written, "");
+ auto dst = make_fake_user_out_ptr(static_cast<void*>(buf));
+ EXPECT_EQ(0U, chain.Read(dst, 0, false), "");
+ END_TEST;
+}
+
+// Tests basic WriteStream/Read functionality.
+static bool stream_write_basic() {
+ BEGIN_TEST;
+ constexpr size_t kWriteLen = 1024;
+ constexpr int kNumWrites = 5;
+ char buf[kWriteLen] = {0};
+ auto src = make_fake_user_in_ptr(static_cast<const void*>(buf));
+
+ size_t written = 0;
+ MBufChain chain;
+ // Call write several times with different buffer contents.
+ for (int i = 0; i < kNumWrites; ++i) {
+ memset(buf, 'A' + i, kWriteLen);
+ ASSERT_EQ(ZX_OK, chain.WriteStream(src, kWriteLen, &written), "");
+ ASSERT_EQ(kWriteLen, written, "");
+ EXPECT_FALSE(chain.is_empty(), "");
+ EXPECT_FALSE(chain.is_full(), "");
+ EXPECT_EQ((i + 1) * kWriteLen, chain.size(), "");
+ }
+
+ // Read it all back in one call.
+ fbl::AllocChecker ac;
+ auto read_buf = fbl::unique_ptr<char[]>(new (&ac) char[kWriteLen * kNumWrites]);
+ ASSERT_TRUE(ac.check(), "");
+ auto dst = make_fake_user_out_ptr(static_cast<void*>(read_buf.get()));
+ size_t result = chain.Read(dst, kNumWrites * kWriteLen, false);
+ ASSERT_EQ(kNumWrites * kWriteLen, result, "");
+ EXPECT_TRUE(chain.is_empty(), "");
+ EXPECT_FALSE(chain.is_full(), "");
+ EXPECT_EQ(0U, chain.size(), "");
+
+ // Verify result.
+ auto expected_buf = fbl::unique_ptr<char[]>(new (&ac) char[kWriteLen * kNumWrites]);
+ ASSERT_TRUE(ac.check(), "");
+ for (int i = 0; i < kNumWrites; ++i) {
+ memset(static_cast<void*>(expected_buf.get() + i * kWriteLen), 'A' + i, kWriteLen);
+ }
+ EXPECT_EQ(0, memcmp(static_cast<void*>(expected_buf.get()),
+ static_cast<void*>(read_buf.get()), kWriteLen), "");
+ END_TEST;
+}
+
+// Tests writing a stream with a zero-length buffer.
+static bool stream_write_zero() {
+ BEGIN_TEST;
+ char buf[1] = {0};
+ auto src = make_fake_user_in_ptr(static_cast<const void*>(buf));
+ size_t written = 7;
+ MBufChain chain;
+ // TODO(maniscalco): Is ZX_ERR_SHOULD_WAIT really the right error here in this case?
+ EXPECT_EQ(ZX_ERR_SHOULD_WAIT, chain.WriteStream(src, 0, &written), "");
+ EXPECT_EQ(7U, written, "");
+ EXPECT_TRUE(chain.is_empty(), "");
+ EXPECT_FALSE(chain.is_full(), "");
+ EXPECT_EQ(0U, chain.size(), "");
+ END_TEST;
+}
+
+// Tests writing a stream to the chain until it stops accepting writes.
+static bool stream_write_too_much() {
+ BEGIN_TEST;
+ constexpr size_t kWriteLen = 65536;
+ fbl::AllocChecker ac;
+ auto buf = fbl::unique_ptr<char[]>(new (&ac) char[kWriteLen]);
+ ASSERT_TRUE(ac.check(), "");
+ memset(static_cast<void*>(buf.get()), 'A', kWriteLen);
+ auto src = make_fake_user_in_ptr(static_cast<const void*>(buf.get()));
+ size_t written = 0;
+ MBufChain chain;
+ size_t total_written = 0;
+
+ // Fill the chain until it refuses to take any more.
+ while (!chain.is_full() && chain.WriteStream(src, kWriteLen, &written) == ZX_OK) {
+ total_written += written;
+ }
+ ASSERT_FALSE(chain.is_empty(), "");
+ ASSERT_TRUE(chain.is_full(), "");
+ EXPECT_EQ(total_written, chain.size(), "");
+
+ // Read it all back out and see we get back the same number of bytes we wrote.
+ size_t total_read = 0;
+ size_t bytes_read = 0;
+ auto dst = make_fake_user_out_ptr(static_cast<void*>(buf.get()));
+ while (!chain.is_empty() && (bytes_read = chain.Read(dst, kWriteLen, false)) > 0) {
+ total_read += bytes_read;
+ }
+ EXPECT_TRUE(chain.is_empty(), "");
+ EXPECT_EQ(0U, chain.size(), "");
+ EXPECT_EQ(total_written, total_read, "");
+ END_TEST;
+}
+
+// TODO(ZX-1847): Implemented a test that verifies behavior of calling ReadDatagram when MBufChain
+// is empty.
+
+// Tests reading a datagram with a zero-length buffer.
+static bool datagram_read_zero() {
+ BEGIN_TEST;
+ char buf[1] = {'A'};
+ auto src = make_fake_user_in_ptr(static_cast<const void*>(buf));
+ MBufChain chain;
+ size_t written = 7;
+ ASSERT_EQ(ZX_OK, chain.WriteDatagram(src, 1, &written), "");
+ ASSERT_EQ(1U, written, "");
+ auto dst = make_fake_user_out_ptr(static_cast<void*>(buf));
+ EXPECT_EQ(0U, chain.Read(dst, 0, true), "");
+ EXPECT_FALSE(chain.is_empty(), "");
+ END_TEST;
+}
+
+// Tests reading a datagram into a buffer that's too small.
+static bool datagram_read_buffer_too_small() {
+ BEGIN_TEST;
+ constexpr size_t kWriteLen = 32;
+ char buf[kWriteLen] = {0};
+ size_t written = 0;
+ MBufChain chain;
+ auto src = make_fake_user_in_ptr(static_cast<const void*>(buf));
+
+ // Write the 'A' datagram.
+ memset(buf, 'A', kWriteLen);
+ ASSERT_EQ(ZX_OK, chain.WriteDatagram(src, kWriteLen, &written), "");
+ ASSERT_EQ(kWriteLen, written, "");
+ EXPECT_EQ(kWriteLen, chain.size(), "");
+ ASSERT_FALSE(chain.is_empty(), "");
+
+ // Write the 'B' datagram.
+ memset(buf, 'B', kWriteLen);
+ ASSERT_EQ(ZX_OK, chain.WriteDatagram(src, kWriteLen, &written), "");
+ ASSERT_EQ(kWriteLen, written, "");
+ EXPECT_EQ(2 * kWriteLen, chain.size(), "");
+ ASSERT_FALSE(chain.is_empty(), "");
+
+ // Read back the first datagram, but with a buffer that's too small. See that we get back a
+ // truncated 'A' datagram.
+ auto dst = make_fake_user_out_ptr(static_cast<void*>(buf));
+ memset(buf, 0, kWriteLen);
+ EXPECT_EQ(1U, chain.Read(dst, 1, true), "");
+ EXPECT_FALSE(chain.is_empty(), "");
+ EXPECT_EQ('A', buf[0],"");
+
+ // Read the next one and see that it's 'B' implying the remainder of 'A' was discarded.
+ EXPECT_EQ(kWriteLen, chain.size(), "");
+ memset(buf, 0, kWriteLen);
+ EXPECT_EQ(kWriteLen, chain.Read(dst, kWriteLen, true), "");
+ EXPECT_TRUE(chain.is_empty(), "");
+ EXPECT_EQ(0U, chain.size(), "");
+ char expected_buf[kWriteLen] = {0};
+ memset(expected_buf, 'B', kWriteLen);
+ EXPECT_EQ(0, memcmp(expected_buf, buf, kWriteLen), "");
+ END_TEST;
+}
+
+// Tests basic WriteDatagram/Read functionality.
+static bool datagram_write_basic() {
+ BEGIN_TEST;
+ constexpr int kNumDatagrams = 100;
+ constexpr size_t kMaxLength = kNumDatagrams;
+ size_t written = 0;
+ char buf[kMaxLength] = {0};
+ auto src = make_fake_user_in_ptr(static_cast<const void*>(buf));
+
+ MBufChain chain;
+ // Write a series of datagrams with different sizes.
+ for (unsigned i = 1; i <= kNumDatagrams; ++i) {
+ memset(buf, i, i);
+ ASSERT_EQ(ZX_OK, chain.WriteDatagram(src, i, &written), "");
+ ASSERT_EQ(i, written, "");
+ EXPECT_FALSE(chain.is_empty(), "");
+ EXPECT_FALSE(chain.is_full(), "");
+ }
+
+ // Read them back and verify their contents.
+ auto dst = make_fake_user_out_ptr(static_cast<void*>(buf));
+ for (unsigned i = 1; i <= kNumDatagrams; ++i) {
+ char expected_buf[kMaxLength] = {0};
+ memset(expected_buf, i, i);
+ size_t result = chain.Read(dst, i, true);
+ ASSERT_EQ(i, result, "");
+ EXPECT_EQ(0, memcmp(expected_buf, buf, i), "");
+ }
+ EXPECT_TRUE(chain.is_empty(), "");
+ EXPECT_EQ(0U, chain.size(), "");
+ END_TEST;
+}
+
+// TODO(ZX-1848): Implemented a test that verifies behavior of calling WriteDatagram with a
+// zero-length buffer.
+
+// Tests writing datagrams to the chain until it stops accepting writes.
+static bool datagram_write_too_much() {
+ BEGIN_TEST;
+ constexpr size_t kWriteLen = 65536;
+ fbl::AllocChecker ac;
+ auto buf = fbl::unique_ptr<char[]>(new (&ac) char[kWriteLen]);
+ ASSERT_TRUE(ac.check(), "");
+ memset(static_cast<void*>(buf.get()), 'A', kWriteLen);
+ auto src = make_fake_user_in_ptr(static_cast<const void*>(buf.get()));
+ size_t written = 0;
+ MBufChain chain;
+ int num_datagrams_written = 0;
+ // Fill the chain until it refuses to take any more.
+ while (!chain.is_full() && chain.WriteDatagram(src, kWriteLen, &written) == ZX_OK) {
+ ++num_datagrams_written;
+ ASSERT_EQ(kWriteLen, written, "");
+ }
+ ASSERT_FALSE(chain.is_empty(), "");
+ EXPECT_EQ(kWriteLen * num_datagrams_written, chain.size(), "");
+ // Read it all back out and see that there's none left over.
+ int num_datagrams_read = 0;
+ auto dst = make_fake_user_out_ptr(static_cast<void*>(buf.get()));
+ while (!chain.is_empty() && chain.Read(dst, kWriteLen, true) > 0) {
+ ++num_datagrams_read;
+ }
+ EXPECT_TRUE(chain.is_empty(), "");
+ EXPECT_EQ(0U, chain.size(), "");
+ EXPECT_EQ(num_datagrams_written, num_datagrams_read, "");
+ END_TEST;
+}
+
+} // namespace
+
+UNITTEST_START_TESTCASE(mbuf_tests)
+UNITTEST("initial_state", initial_state)
+UNITTEST("stream_read_empty", stream_read_empty)
+UNITTEST("stream_read_zero", stream_read_zero)
+UNITTEST("stream_write_basic", stream_write_basic)
+UNITTEST("stream_write_zero", stream_write_zero)
+UNITTEST("stream_write_too_much", stream_write_too_much)
+UNITTEST("datagram_read_zero", datagram_read_zero)
+UNITTEST("datagram_read_buffer_too_small", datagram_read_buffer_too_small)
+UNITTEST("datagram_write_basic", datagram_write_basic)
+UNITTEST("datagram_write_too_much", datagram_write_too_much)
+UNITTEST_END_TESTCASE(mbuf_tests, "mbuf", "MBuf test");
diff --git a/kernel/object/rules.mk b/kernel/object/rules.mk
index 8b4d860..44fd361 100644
--- a/kernel/object/rules.mk
+++ b/kernel/object/rules.mk
@@ -50,6 +50,7 @@
# Tests
MODULE_SRCS += \
+ $(LOCAL_DIR)/mbuf_tests.cpp \
$(LOCAL_DIR)/state_tracker_tests.cpp \
MODULE_DEPS := \