blob: 7d75141151ecd6908f713d159e926340d501ed32 [file] [log] [blame]
// 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 <lib/unittest/unittest.h>
#include <lib/unittest/user_memory.h>
#include <fbl/array.h>
#include <ktl/unique_ptr.h>
#include "object/mbuf.h"
#include <ktl/enforce.h>
namespace {
using testing::UserMemory;
enum class MessageType { kStream, kDatagram };
enum class ReadType { kRead, kPeek };
// Writes a null-terminated string into |chain|.
//
// Helps eliminate some of the boilerplate code dealing with copying in and
// out of user memory to make the test logic more obvious.
//
// The null terminator on |str| is not copied into |chain|, it's just used
// so that we can easily determine the length without requiring the caller
// to pass it in separately.
//
// Returns false if a user memory operation fails or |chain| failed to write
// the whole |str|.
bool WriteHelper(MBufChain* chain, const char* str, MessageType message_type) {
BEGIN_TEST;
const size_t length = strlen(str);
ktl::unique_ptr<UserMemory> memory = UserMemory::Create(length);
ASSERT_NE(nullptr, memory.get());
ASSERT_EQ(ZX_OK, memory->user_out<char>().copy_array_to_user(str, length));
auto user_in = memory->user_in<char>();
size_t written = 0;
if (message_type == MessageType::kDatagram) {
ASSERT_EQ(ZX_OK, chain->WriteDatagram(user_in, length, &written));
} else {
ASSERT_EQ(ZX_OK, chain->WriteStream(user_in, length, &written));
}
ASSERT_EQ(length, written);
END_TEST;
}
// Reads or peeks data from |chain|.
//
// Returns nullptr on memory failure.
fbl::Array<char> ReadHelper(MBufChain* chain, size_t length, MessageType message_type,
ReadType read_type) {
// It's an error to create UserMemory of size 0, so bump this to 1 even if we
// don't intend to use it.
ktl::unique_ptr<UserMemory> memory = UserMemory::Create(length ? length : 1);
if (!memory) {
unittest_printf("Failed to allocate UserMemory\n");
return nullptr;
}
auto user_out = memory->user_out<char>();
bool datagram = (message_type == MessageType::kDatagram);
size_t actual;
zx_status_t status = (read_type == ReadType::kRead)
? chain->Read(user_out, length, datagram, &actual)
: chain->Peek(user_out, length, datagram, &actual);
if (status != ZX_OK) {
return nullptr;
}
fbl::AllocChecker ac;
fbl::Array<char> buffer(new (&ac) char[actual], actual);
if (!ac.check()) {
unittest_printf("Failed to allocate char buffer\n");
return nullptr;
}
if (memory->user_in<char>().copy_array_from_user(buffer.data(), actual) != ZX_OK) {
unittest_printf("Failed to copy user memory bytes\n");
return nullptr;
}
return buffer;
}
// Checks that the contents of |buffer| match the null-terminated |str|.
//
// fbl::Array<char> isn't supported by EXPECT_EQ() due to printf() usage so
// this allows us to write EXPECT_TRUE(Equal(...)) instead.
//
// Returns false if either the size or contents differ.
bool Equal(const fbl::Array<char>& buffer, const char* str) {
const size_t length = strlen(str);
return buffer.size() == length && memcmp(buffer.data(), str, length) == 0;
}
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;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(1);
auto mem_out = mem->user_out<char>();
MBufChain chain;
size_t actual;
EXPECT_EQ(ZX_OK, chain.Read(mem_out, 1, false, &actual));
EXPECT_EQ(0U, actual);
END_TEST;
}
// Tests reading a stream with a zero-length buffer.
static bool stream_read_zero() {
BEGIN_TEST;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(1);
auto mem_in = mem->user_in<char>();
auto mem_out = mem->user_out<char>();
MBufChain chain;
size_t written = 7;
ASSERT_EQ(ZX_OK, chain.WriteStream(mem_in, 1, &written));
ASSERT_EQ(1U, written);
size_t actual;
EXPECT_EQ(ZX_OK, chain.Read(mem_out, 0, false, &actual));
EXPECT_EQ(0U, actual);
END_TEST;
}
// Tests basic WriteStream/Read functionality.
static bool stream_write_basic() {
BEGIN_TEST;
constexpr size_t kWriteLen = 1024;
constexpr int kNumWrites = 5;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(kWriteLen);
auto mem_in = mem->user_in<char>();
auto mem_out = mem->user_out<char>();
size_t written = 0;
MBufChain chain;
// Call write several times with different buffer contents.
for (int i = 0; i < kNumWrites; ++i) {
char buf[kWriteLen] = {0};
memset(buf, 'A' + i, kWriteLen);
ASSERT_EQ(ZX_OK, mem_out.copy_array_to_user(buf, kWriteLen));
ASSERT_EQ(ZX_OK, chain.WriteStream(mem_in, 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.
constexpr size_t kTotalLen = kWriteLen * kNumWrites;
ASSERT_EQ(kTotalLen, chain.size());
ktl::unique_ptr<UserMemory> read_buf = UserMemory::Create(kTotalLen);
auto read_buf_in = read_buf->user_in<char>();
auto read_buf_out = read_buf->user_out<char>();
size_t actual;
zx_status_t status = chain.Read(read_buf_out, kTotalLen, false, &actual);
ASSERT_EQ(ZX_OK, status);
ASSERT_EQ(kTotalLen, actual);
EXPECT_TRUE(chain.is_empty());
EXPECT_FALSE(chain.is_full());
EXPECT_EQ(0U, chain.size());
// Verify result.
fbl::AllocChecker ac;
auto expected_buf = ktl::unique_ptr<char[]>(new (&ac) char[kTotalLen]);
ASSERT_TRUE(ac.check());
for (int i = 0; i < kNumWrites; ++i) {
memset(static_cast<void*>(expected_buf.get() + i * kWriteLen), 'A' + i, kWriteLen);
}
auto actual_buf = ktl::unique_ptr<char[]>(new (&ac) char[kTotalLen]);
ASSERT_TRUE(ac.check());
ASSERT_EQ(ZX_OK, read_buf_in.copy_array_from_user(actual_buf.get(), kTotalLen));
EXPECT_EQ(0, memcmp(static_cast<void*>(expected_buf.get()), static_cast<void*>(actual_buf.get()),
kTotalLen));
END_TEST;
}
// Tests writing a stream with a zero-length buffer.
static bool stream_write_zero() {
BEGIN_TEST;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(1);
auto mem_in = mem->user_in<char>();
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(mem_in, 0, &written));
EXPECT_EQ(0U, 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;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(kWriteLen);
auto mem_in = mem->user_in<char>();
auto mem_out = mem->user_out<char>();
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(mem_in, 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;
zx_status_t status = ZX_OK;
while (!chain.is_empty() &&
((status = chain.Read(mem_out, kWriteLen, false, &bytes_read)) == ZX_OK) &&
bytes_read > 0) {
total_read += bytes_read;
}
ASSERT_EQ(ZX_OK, status);
EXPECT_TRUE(chain.is_empty());
EXPECT_EQ(0U, chain.size());
EXPECT_EQ(total_written, total_read);
END_TEST;
}
static bool stream_peek() {
BEGIN_TEST;
MBufChain chain;
ASSERT_TRUE(WriteHelper(&chain, "abc", MessageType::kStream));
ASSERT_TRUE(WriteHelper(&chain, "123", MessageType::kStream));
EXPECT_TRUE(Equal(ReadHelper(&chain, 1, MessageType::kStream, ReadType::kPeek), "a"));
EXPECT_TRUE(Equal(ReadHelper(&chain, 3, MessageType::kStream, ReadType::kPeek), "abc"));
EXPECT_TRUE(Equal(ReadHelper(&chain, 4, MessageType::kStream, ReadType::kPeek), "abc1"));
EXPECT_TRUE(Equal(ReadHelper(&chain, 6, MessageType::kStream, ReadType::kPeek), "abc123"));
// Make sure peeking didn't affect an actual read.
EXPECT_EQ(6u, chain.size());
EXPECT_TRUE(Equal(ReadHelper(&chain, 6, MessageType::kStream, ReadType::kRead), "abc123"));
END_TEST;
}
static bool stream_peek_empty() {
BEGIN_TEST;
MBufChain chain;
EXPECT_TRUE(Equal(ReadHelper(&chain, 1, MessageType::kStream, ReadType::kPeek), ""));
END_TEST;
}
static bool stream_peek_zero() {
BEGIN_TEST;
MBufChain chain;
ASSERT_TRUE(WriteHelper(&chain, "a", MessageType::kStream));
EXPECT_TRUE(Equal(ReadHelper(&chain, 0, MessageType::kStream, ReadType::kPeek), ""));
END_TEST;
}
// Ask for more data than exists, make sure it only returns the real data.
static bool stream_peek_underflow() {
BEGIN_TEST;
MBufChain chain;
ASSERT_TRUE(WriteHelper(&chain, "abc", MessageType::kStream));
EXPECT_TRUE(Equal(ReadHelper(&chain, 10, MessageType::kStream, ReadType::kPeek), "abc"));
ASSERT_TRUE(WriteHelper(&chain, "123", MessageType::kStream));
EXPECT_TRUE(Equal(ReadHelper(&chain, 10, MessageType::kStream, ReadType::kPeek), "abc123"));
END_TEST;
}
// Tests reading a datagram when chain is empty.
static bool datagram_read_empty() {
BEGIN_TEST;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(1);
auto mem_out = mem->user_out<char>();
MBufChain chain;
size_t actual;
ASSERT_EQ(ZX_OK, chain.Read(mem_out, 1, true, &actual));
EXPECT_EQ(0U, actual);
EXPECT_TRUE(chain.is_empty());
END_TEST;
}
// Tests reading a datagram with a zero-length buffer.
static bool datagram_read_zero() {
BEGIN_TEST;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(1);
auto mem_in = mem->user_in<char>();
auto mem_out = mem->user_out<char>();
MBufChain chain;
size_t written = 7;
ASSERT_EQ(ZX_OK, chain.WriteDatagram(mem_in, 1, &written));
ASSERT_EQ(1U, written);
size_t actual;
ASSERT_EQ(ZX_OK, chain.Read(mem_out, 0, true, &actual));
EXPECT_EQ(0U, actual);
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;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(kWriteLen);
auto mem_in = mem->user_in<char>();
auto mem_out = mem->user_out<char>();
size_t written = 0;
MBufChain chain;
// Write the 'A' datagram.
char buf[kWriteLen] = {0};
memset(buf, 'A', sizeof(buf));
ASSERT_EQ(ZX_OK, mem_out.copy_array_to_user(buf, sizeof(buf)));
ASSERT_EQ(ZX_OK, chain.WriteDatagram(mem_in, kWriteLen, &written));
ASSERT_EQ(kWriteLen, written);
EXPECT_EQ(kWriteLen, chain.size());
ASSERT_FALSE(chain.is_empty());
// Write the 'B' datagram.
memset(buf, 'B', sizeof(buf));
ASSERT_EQ(ZX_OK, mem_out.copy_array_to_user(buf, sizeof(buf)));
ASSERT_EQ(ZX_OK, chain.WriteDatagram(mem_in, 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.
memset(buf, 0, sizeof(buf));
ASSERT_EQ(ZX_OK, mem_out.copy_array_to_user(buf, sizeof(buf)));
size_t actual;
ASSERT_EQ(ZX_OK, chain.Read(mem_out, 1, true, &actual));
EXPECT_EQ(1U, actual);
EXPECT_FALSE(chain.is_empty());
ASSERT_EQ(ZX_OK, mem_in.copy_array_from_user(buf, sizeof(buf)));
EXPECT_EQ('A', buf[0]);
EXPECT_EQ(0, buf[1]);
// 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);
ASSERT_EQ(ZX_OK, mem_out.copy_array_to_user(buf, sizeof(buf)));
ASSERT_EQ(ZX_OK, chain.Read(mem_out, kWriteLen, true, &actual));
EXPECT_EQ(kWriteLen, actual);
EXPECT_TRUE(chain.is_empty());
EXPECT_EQ(0U, chain.size());
ASSERT_EQ(ZX_OK, mem_in.copy_array_from_user(buf, sizeof(buf)));
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;
size_t total_written = 0;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(kMaxLength);
auto mem_in = mem->user_in<char>();
auto mem_out = mem->user_out<char>();
MBufChain chain;
// Write a series of datagrams with different sizes.
for (unsigned i = 1; i <= kNumDatagrams; ++i) {
char buf[kMaxLength] = {0};
memset(buf, i, i);
ASSERT_EQ(ZX_OK, mem_out.copy_array_to_user(buf, sizeof(buf)));
ASSERT_EQ(ZX_OK, chain.WriteDatagram(mem_in, i, &written));
ASSERT_EQ(i, written);
total_written += written;
EXPECT_FALSE(chain.is_empty());
EXPECT_FALSE(chain.is_full());
}
// Verify size() returns correctly
EXPECT_EQ(1U, chain.size(true));
EXPECT_EQ(total_written, chain.size());
// Read them back and verify their contents.
for (unsigned i = 1; i <= kNumDatagrams; ++i) {
EXPECT_EQ(i, chain.size(true));
char expected_buf[kMaxLength] = {0};
memset(expected_buf, i, i);
size_t actual;
zx_status_t status = chain.Read(mem_out, i, true, &actual);
ASSERT_EQ(ZX_OK, status);
ASSERT_EQ(i, actual);
char actual_buf[kMaxLength] = {0};
ASSERT_EQ(ZX_OK, mem_in.copy_array_from_user(actual_buf, sizeof(actual_buf)));
EXPECT_EQ(0, memcmp(expected_buf, actual_buf, i));
}
EXPECT_TRUE(chain.is_empty());
EXPECT_EQ(0U, chain.size());
END_TEST;
}
// Tests writing a zero-length datagram to the chain.
static bool datagram_write_zero() {
BEGIN_TEST;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(1);
auto mem_in = mem->user_in<char>();
size_t written = 7;
MBufChain chain;
EXPECT_EQ(ZX_ERR_INVALID_ARGS, chain.WriteDatagram(mem_in, 0, &written));
EXPECT_EQ(0U, written);
EXPECT_TRUE(chain.is_empty());
EXPECT_FALSE(chain.is_full());
EXPECT_EQ(0U, chain.size(true));
EXPECT_EQ(0U, chain.size());
END_TEST;
}
// Tests writing datagrams to the chain until it stops accepting writes.
static bool datagram_write_too_much() {
BEGIN_TEST;
constexpr size_t kWriteLen = 65536;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(kWriteLen);
auto mem_in = mem->user_in<char>();
auto mem_out = mem->user_out<char>();
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(mem_in, 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;
zx_status_t status = ZX_OK;
size_t actual;
while (!chain.is_empty() && ((status = chain.Read(mem_out, kWriteLen, true, &actual)) == ZX_OK) &&
actual > 0) {
++num_datagrams_read;
}
ASSERT_EQ(ZX_OK, status);
EXPECT_TRUE(chain.is_empty());
EXPECT_EQ(0U, chain.size());
EXPECT_EQ(num_datagrams_written, num_datagrams_read);
END_TEST;
}
static bool datagram_reuse_mbuf() {
BEGIN_TEST;
const size_t kLargeWrite = MBufChain::mbuf_payload_size() + 10;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(kLargeWrite);
auto mem_in = mem->user_in<char>();
auto mem_out = mem->user_out<char>();
// Write two datagrams.
size_t written = 0;
MBufChain chain;
mem->put<char>('a', 0);
EXPECT_OK(chain.WriteDatagram(mem_in, 1, &written));
mem->put<char>('b', 0);
EXPECT_OK(chain.WriteDatagram(mem_in, 1, &written));
// Now read them both out.
size_t actual;
EXPECT_OK(chain.Read(mem_out, 1, true, &actual));
EXPECT_EQ(mem->get<char>(0), 'a');
EXPECT_OK(chain.Read(mem_out, 1, true, &actual));
EXPECT_EQ(mem->get<char>(0), 'b');
// Now write a large datagram that spans two buffers.
mem->put<char>('c', 0);
EXPECT_OK(chain.WriteDatagram(mem_in, kLargeWrite, &written));
// Write in a second small datagram.
mem->put<char>('d', 0);
EXPECT_OK(chain.WriteDatagram(mem_in, 1, &written));
// Do a short read to consume the first datagram.
EXPECT_OK(chain.Read(mem_out, 1, true, &actual));
EXPECT_EQ(mem->get<char>(0), 'c');
// Reading again should give us the second datagram we wrote, as the remaining of the first should
// have been discarded.
EXPECT_OK(chain.Read(mem_out, 1, true, &actual));
EXPECT_EQ(mem->get<char>(0), 'd');
// At this point the socket should be empty.
EXPECT_TRUE(chain.is_empty());
END_TEST;
}
// Tests writing a datagram packet larger than the mbuf's capacity.
static bool datagram_write_huge_packet() {
BEGIN_TEST;
MBufChain chain;
const size_t kHugePacketSize = chain.max_size() + 1;
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(kHugePacketSize);
auto mem_in = mem->user_in<char>();
size_t written;
zx_status_t status = chain.WriteDatagram(mem_in, kHugePacketSize, &written);
ASSERT_EQ(status, ZX_ERR_OUT_OF_RANGE);
END_TEST;
}
static bool datagram_peek() {
BEGIN_TEST;
MBufChain chain;
ASSERT_TRUE(WriteHelper(&chain, "abc", MessageType::kDatagram));
EXPECT_TRUE(Equal(ReadHelper(&chain, 1, MessageType::kDatagram, ReadType::kPeek), "a"));
EXPECT_TRUE(Equal(ReadHelper(&chain, 3, MessageType::kDatagram, ReadType::kPeek), "abc"));
// Make sure peeking didn't affect an actual read.
EXPECT_EQ(3u, chain.size());
EXPECT_TRUE(Equal(ReadHelper(&chain, 3, MessageType::kDatagram, ReadType::kRead), "abc"));
END_TEST;
}
static bool datagram_peek_empty() {
BEGIN_TEST;
MBufChain chain;
EXPECT_TRUE(Equal(ReadHelper(&chain, 1, MessageType::kDatagram, ReadType::kPeek), ""));
END_TEST;
}
static bool datagram_peek_zero() {
BEGIN_TEST;
MBufChain chain;
ASSERT_TRUE(WriteHelper(&chain, "a", MessageType::kDatagram));
EXPECT_TRUE(Equal(ReadHelper(&chain, 0, MessageType::kDatagram, ReadType::kPeek), ""));
END_TEST;
}
static bool datagram_peek_underflow() {
BEGIN_TEST;
MBufChain chain;
ASSERT_TRUE(WriteHelper(&chain, "abc", MessageType::kDatagram));
ASSERT_TRUE(WriteHelper(&chain, "123", MessageType::kDatagram));
// Datagram peeks should not return more than a single message.
EXPECT_TRUE(Equal(ReadHelper(&chain, 10, MessageType::kDatagram, ReadType::kPeek), "abc"));
EXPECT_TRUE(Equal(ReadHelper(&chain, 3, MessageType::kDatagram, ReadType::kRead), "abc"));
EXPECT_TRUE(Equal(ReadHelper(&chain, 10, MessageType::kDatagram, ReadType::kPeek), "123"));
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("stream_peek", stream_peek)
UNITTEST("stream_peek_empty", stream_peek_empty)
UNITTEST("stream_peek_zero", stream_peek_zero)
UNITTEST("stream_peek_underflow", stream_peek_underflow)
UNITTEST("datagram_read_empty", datagram_read_empty)
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_zero", datagram_write_zero)
UNITTEST("datagram_write_too_much", datagram_write_too_much)
UNITTEST("datagram_reuse_mbuf", datagram_reuse_mbuf)
UNITTEST("datagram_write_huge_packet", datagram_write_huge_packet)
UNITTEST("datagram_peek", datagram_peek)
UNITTEST("datagram_peek_empty", datagram_peek_empty)
UNITTEST("datagram_peek_zero", datagram_peek_zero)
UNITTEST("datagram_peek_underflow", datagram_peek_underflow)
UNITTEST_END_TESTCASE(mbuf_tests, "mbuf", "MBuf test")