[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 := \