|  | // Copyright 2022 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "quiche/balsa/simple_buffer.h" | 
|  |  | 
|  | #include <string> | 
|  |  | 
|  | #include "absl/strings/string_view.h" | 
|  | #include "quiche/common/platform/api/quiche_expect_bug.h" | 
|  | #include "quiche/common/platform/api/quiche_test.h" | 
|  |  | 
|  | namespace quiche { | 
|  |  | 
|  | namespace test { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | constexpr int kMinimumSimpleBufferSize = 10; | 
|  |  | 
|  | // Buffer full of 40 char strings. | 
|  | const char ibuf[] = { | 
|  | "123456789!@#$%^&*()abcdefghijklmnopqrstu" | 
|  | "123456789!@#$%^&*()abcdefghijklmnopqrstu" | 
|  | "123456789!@#$%^&*()abcdefghijklmnopqrstu" | 
|  | "123456789!@#$%^&*()abcdefghijklmnopqrstu" | 
|  | "123456789!@#$%^&*()abcdefghijklmnopqrstu"}; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class SimpleBufferTest : public QuicheTest { | 
|  | public: | 
|  | static char* storage(SimpleBuffer& buffer) { return buffer.storage_; } | 
|  | static int write_idx(SimpleBuffer& buffer) { return buffer.write_idx_; } | 
|  | static int read_idx(SimpleBuffer& buffer) { return buffer.read_idx_; } | 
|  | static int storage_size(SimpleBuffer& buffer) { return buffer.storage_size_; } | 
|  | }; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | TEST_F(SimpleBufferTest, CreationWithSize) { | 
|  | SimpleBuffer buffer1(5); | 
|  | EXPECT_EQ(kMinimumSimpleBufferSize, storage_size(buffer1)); | 
|  |  | 
|  | SimpleBuffer buffer2(25); | 
|  | EXPECT_EQ(25, storage_size(buffer2)); | 
|  | } | 
|  |  | 
|  | // Make sure that a zero-sized initial buffer does not throw things off. | 
|  | TEST_F(SimpleBufferTest, CreationWithZeroSize) { | 
|  | SimpleBuffer buffer(0); | 
|  | EXPECT_EQ(0, storage_size(buffer)); | 
|  | EXPECT_EQ(4, buffer.Write(ibuf, 4)); | 
|  | EXPECT_EQ(4, write_idx(buffer)); | 
|  | EXPECT_EQ(kMinimumSimpleBufferSize, storage_size(buffer)); | 
|  | EXPECT_EQ(4, buffer.ReadableBytes()); | 
|  | } | 
|  |  | 
|  | TEST_F(SimpleBufferTest, ReadZeroBytes) { | 
|  | SimpleBuffer buffer; | 
|  |  | 
|  | EXPECT_EQ(0, buffer.Read(nullptr, 0)); | 
|  | } | 
|  |  | 
|  | TEST_F(SimpleBufferTest, WriteZeroFromNullptr) { | 
|  | SimpleBuffer buffer; | 
|  |  | 
|  | EXPECT_EQ(0, buffer.Write(nullptr, 0)); | 
|  | } | 
|  |  | 
|  | TEST(SimpleBufferExpectBug, ReserveNegativeSize) { | 
|  | SimpleBuffer buffer; | 
|  |  | 
|  | EXPECT_QUICHE_BUG(buffer.Reserve(-1), "size must not be negative"); | 
|  | } | 
|  |  | 
|  | TEST(SimpleBufferExpectBug, ReadNegativeSize) { | 
|  | SimpleBuffer buffer; | 
|  |  | 
|  | EXPECT_QUICHE_BUG(buffer.Read(nullptr, -1), "size must not be negative"); | 
|  | } | 
|  |  | 
|  | TEST(SimpleBufferExpectBug, WriteNegativeSize) { | 
|  | SimpleBuffer buffer; | 
|  |  | 
|  | EXPECT_QUICHE_BUG(buffer.Write(nullptr, -1), "size must not be negative"); | 
|  | } | 
|  |  | 
|  | TEST_F(SimpleBufferTest, Basics) { | 
|  | SimpleBuffer buffer; | 
|  |  | 
|  | EXPECT_TRUE(buffer.Empty()); | 
|  | EXPECT_EQ("", buffer.GetReadableRegion()); | 
|  | EXPECT_EQ(0, storage_size(buffer)); | 
|  | EXPECT_EQ(0, read_idx(buffer)); | 
|  | EXPECT_EQ(0, write_idx(buffer)); | 
|  |  | 
|  | char* readable_ptr = nullptr; | 
|  | int readable_size = 0; | 
|  | buffer.GetReadablePtr(&readable_ptr, &readable_size); | 
|  | char* writeable_ptr = nullptr; | 
|  | int writable_size = 0; | 
|  | buffer.GetWritablePtr(&writeable_ptr, &writable_size); | 
|  |  | 
|  | EXPECT_EQ(storage(buffer), readable_ptr); | 
|  | EXPECT_EQ(0, readable_size); | 
|  | EXPECT_EQ(storage(buffer), writeable_ptr); | 
|  | EXPECT_EQ(0, writable_size); | 
|  | EXPECT_EQ(0, buffer.ReadableBytes()); | 
|  |  | 
|  | const SimpleBuffer buffer2; | 
|  | EXPECT_EQ(0, buffer2.ReadableBytes()); | 
|  | } | 
|  |  | 
|  | TEST_F(SimpleBufferTest, BasicWR) { | 
|  | SimpleBuffer buffer; | 
|  |  | 
|  | EXPECT_EQ(4, buffer.Write(ibuf, 4)); | 
|  | EXPECT_EQ(0, read_idx(buffer)); | 
|  | EXPECT_EQ(4, write_idx(buffer)); | 
|  | EXPECT_EQ(kMinimumSimpleBufferSize, storage_size(buffer)); | 
|  | EXPECT_EQ(4, buffer.ReadableBytes()); | 
|  | EXPECT_EQ("1234", buffer.GetReadableRegion()); | 
|  | int bytes_written = 4; | 
|  | EXPECT_TRUE(!buffer.Empty()); | 
|  |  | 
|  | char* readable_ptr = nullptr; | 
|  | int readable_size = 0; | 
|  | buffer.GetReadablePtr(&readable_ptr, &readable_size); | 
|  | char* writeable_ptr = nullptr; | 
|  | int writable_size = 0; | 
|  | buffer.GetWritablePtr(&writeable_ptr, &writable_size); | 
|  |  | 
|  | EXPECT_EQ(storage(buffer), readable_ptr); | 
|  | EXPECT_EQ(4, readable_size); | 
|  | EXPECT_EQ(storage(buffer) + 4, writeable_ptr); | 
|  | EXPECT_EQ(6, writable_size); | 
|  |  | 
|  | char obuf[ABSL_ARRAYSIZE(ibuf)]; | 
|  | int bytes_read = 0; | 
|  | EXPECT_EQ(4, buffer.Read(obuf + bytes_read, 40)); | 
|  | EXPECT_EQ(0, read_idx(buffer)); | 
|  | EXPECT_EQ(0, write_idx(buffer)); | 
|  | EXPECT_EQ(kMinimumSimpleBufferSize, storage_size(buffer)); | 
|  | EXPECT_EQ(0, buffer.ReadableBytes()); | 
|  | EXPECT_EQ("", buffer.GetReadableRegion()); | 
|  | bytes_read += 4; | 
|  | EXPECT_TRUE(buffer.Empty()); | 
|  | buffer.GetReadablePtr(&readable_ptr, &readable_size); | 
|  | buffer.GetWritablePtr(&writeable_ptr, &writable_size); | 
|  | EXPECT_EQ(storage(buffer), readable_ptr); | 
|  | EXPECT_EQ(0, readable_size); | 
|  | EXPECT_EQ(storage(buffer), writeable_ptr); | 
|  | EXPECT_EQ(kMinimumSimpleBufferSize, writable_size); | 
|  |  | 
|  | EXPECT_EQ(bytes_written, bytes_read); | 
|  | for (int i = 0; i < bytes_read; ++i) { | 
|  | EXPECT_EQ(obuf[i], ibuf[i]); | 
|  | } | 
|  |  | 
|  | // More R/W tests. | 
|  | EXPECT_EQ(10, buffer.Write(ibuf + bytes_written, 10)); | 
|  | EXPECT_EQ(0, read_idx(buffer)); | 
|  | EXPECT_EQ(10, write_idx(buffer)); | 
|  | EXPECT_EQ(10, storage_size(buffer)); | 
|  | EXPECT_EQ(10, buffer.ReadableBytes()); | 
|  | bytes_written += 10; | 
|  |  | 
|  | EXPECT_TRUE(!buffer.Empty()); | 
|  |  | 
|  | EXPECT_EQ(6, buffer.Read(obuf + bytes_read, 6)); | 
|  | EXPECT_EQ(6, read_idx(buffer)); | 
|  | EXPECT_EQ(10, write_idx(buffer)); | 
|  | EXPECT_EQ(10, storage_size(buffer)); | 
|  | EXPECT_EQ(4, buffer.ReadableBytes()); | 
|  | bytes_read += 6; | 
|  |  | 
|  | EXPECT_TRUE(!buffer.Empty()); | 
|  |  | 
|  | EXPECT_EQ(4, buffer.Read(obuf + bytes_read, 7)); | 
|  | EXPECT_EQ(0, read_idx(buffer)); | 
|  | EXPECT_EQ(0, write_idx(buffer)); | 
|  | EXPECT_EQ(10, storage_size(buffer)); | 
|  | EXPECT_EQ(0, buffer.ReadableBytes()); | 
|  | bytes_read += 4; | 
|  |  | 
|  | EXPECT_TRUE(buffer.Empty()); | 
|  |  | 
|  | EXPECT_EQ(bytes_written, bytes_read); | 
|  | for (int i = 0; i < bytes_read; ++i) { | 
|  | EXPECT_EQ(obuf[i], ibuf[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(SimpleBufferTest, Reserve) { | 
|  | SimpleBuffer buffer; | 
|  | EXPECT_EQ(0, storage_size(buffer)); | 
|  |  | 
|  | buffer.WriteString("foo"); | 
|  | EXPECT_EQ(kMinimumSimpleBufferSize, storage_size(buffer)); | 
|  |  | 
|  | // Reserve by expanding the buffer. | 
|  | buffer.Reserve(kMinimumSimpleBufferSize + 1); | 
|  | EXPECT_EQ(2 * kMinimumSimpleBufferSize, storage_size(buffer)); | 
|  |  | 
|  | buffer.Clear(); | 
|  | buffer.AdvanceWritablePtr(kMinimumSimpleBufferSize); | 
|  | buffer.AdvanceReadablePtr(kMinimumSimpleBufferSize - 2); | 
|  | EXPECT_EQ(kMinimumSimpleBufferSize, write_idx(buffer)); | 
|  | EXPECT_EQ(2 * kMinimumSimpleBufferSize, storage_size(buffer)); | 
|  |  | 
|  | // Reserve by moving data around.  `storage_size` does not change. | 
|  | buffer.Reserve(kMinimumSimpleBufferSize + 1); | 
|  | EXPECT_EQ(2, write_idx(buffer)); | 
|  | EXPECT_EQ(2 * kMinimumSimpleBufferSize, storage_size(buffer)); | 
|  | } | 
|  |  | 
|  | TEST_F(SimpleBufferTest, Extend) { | 
|  | SimpleBuffer buffer; | 
|  |  | 
|  | // Test a write which should not extend the buffer. | 
|  | EXPECT_EQ(7, buffer.Write(ibuf, 7)); | 
|  | EXPECT_EQ(0, read_idx(buffer)); | 
|  | EXPECT_EQ(7, write_idx(buffer)); | 
|  | EXPECT_EQ(kMinimumSimpleBufferSize, storage_size(buffer)); | 
|  | EXPECT_EQ(7, buffer.ReadableBytes()); | 
|  | EXPECT_EQ(0, read_idx(buffer)); | 
|  | EXPECT_EQ(7, write_idx(buffer)); | 
|  | EXPECT_EQ(kMinimumSimpleBufferSize, storage_size(buffer)); | 
|  | EXPECT_EQ(7, buffer.ReadableBytes()); | 
|  | int bytes_written = 7; | 
|  |  | 
|  | // Test a write which should extend the buffer. | 
|  | EXPECT_EQ(4, buffer.Write(ibuf + bytes_written, 4)); | 
|  | EXPECT_EQ(0, read_idx(buffer)); | 
|  | EXPECT_EQ(11, write_idx(buffer)); | 
|  | EXPECT_EQ(20, storage_size(buffer)); | 
|  | EXPECT_EQ(11, buffer.ReadableBytes()); | 
|  | bytes_written += 4; | 
|  |  | 
|  | char obuf[ABSL_ARRAYSIZE(ibuf)]; | 
|  | EXPECT_EQ(11, buffer.Read(obuf, 11)); | 
|  | EXPECT_EQ(0, read_idx(buffer)); | 
|  | EXPECT_EQ(0, write_idx(buffer)); | 
|  | EXPECT_EQ(20, storage_size(buffer)); | 
|  | EXPECT_EQ(0, read_idx(buffer)); | 
|  | EXPECT_EQ(0, write_idx(buffer)); | 
|  | EXPECT_EQ(0, buffer.ReadableBytes()); | 
|  |  | 
|  | const int bytes_read = 11; | 
|  | EXPECT_EQ(bytes_written, bytes_read); | 
|  | for (int i = 0; i < bytes_read; ++i) { | 
|  | EXPECT_EQ(obuf[i], ibuf[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(SimpleBufferTest, Clear) { | 
|  | SimpleBuffer buffer; | 
|  |  | 
|  | buffer.Clear(); | 
|  |  | 
|  | EXPECT_EQ(0, read_idx(buffer)); | 
|  | EXPECT_EQ(0, write_idx(buffer)); | 
|  | EXPECT_EQ(0, storage_size(buffer)); | 
|  | EXPECT_EQ(0, buffer.ReadableBytes()); | 
|  |  | 
|  | buffer.WriteString("foo"); | 
|  | buffer.Clear(); | 
|  |  | 
|  | EXPECT_EQ(0, read_idx(buffer)); | 
|  | EXPECT_EQ(0, write_idx(buffer)); | 
|  | EXPECT_EQ(kMinimumSimpleBufferSize, storage_size(buffer)); | 
|  | EXPECT_EQ(0, buffer.ReadableBytes()); | 
|  | } | 
|  |  | 
|  | TEST_F(SimpleBufferTest, LongWrite) { | 
|  | SimpleBuffer buffer; | 
|  |  | 
|  | std::string s1 = "HTTP/1.1 500 Service Unavailable"; | 
|  | buffer.Write(s1.data(), s1.size()); | 
|  | buffer.Write("\r\n", 2); | 
|  | std::string key = "Connection"; | 
|  | std::string value = "close"; | 
|  | buffer.Write(key.data(), key.size()); | 
|  | buffer.Write(": ", 2); | 
|  | buffer.Write(value.data(), value.size()); | 
|  | buffer.Write("\r\n", 2); | 
|  | buffer.Write("\r\n", 2); | 
|  | std::string message = | 
|  | "<html><head>\n" | 
|  | "<meta http-equiv=\"content-type\"" | 
|  | " content=\"text/html;charset=us-ascii\">\n" | 
|  | "<style><!--\n" | 
|  | "body {font-family: arial,sans-serif}\n" | 
|  | "div.nav {margin-top: 1ex}\n" | 
|  | "div.nav A {font-size: 10pt; font-family: arial,sans-serif}\n" | 
|  | "span.nav {font-size: 10pt; font-family: arial,sans-serif;" | 
|  | " font-weight: bold}\n" | 
|  | "div.nav A,span.big {font-size: 12pt; color: #0000cc}\n" | 
|  | "div.nav A {font-size: 10pt; color: black}\n" | 
|  | "A.l:link {color: #6f6f6f}\n" | 
|  | "A.u:link {color: green}\n" | 
|  | "//--></style>\n" | 
|  | "</head>\n" | 
|  | "<body text=#000000 bgcolor=#ffffff>\n" | 
|  | "<table border=0 cellpadding=2 cellspacing=0 width=100%>" | 
|  | "<tr><td rowspan=3 width=1% nowrap>\n" | 
|  | "<b>" | 
|  | "<font face=times color=#0039b6 size=10>G</font>" | 
|  | "<font face=times color=#c41200 size=10>o</font>" | 
|  | "<font face=times color=#f3c518 size=10>o</font>" | 
|  | "<font face=times color=#0039b6 size=10>g</font>" | 
|  | "<font face=times color=#30a72f size=10>l</font>" | 
|  | "<font face=times color=#c41200 size=10>e</font>" | 
|  | "  </b>\n" | 
|  | "<td> </td></tr>\n" | 
|  | "<tr><td bgcolor=#3366cc><font face=arial,sans-serif color=#ffffff>" | 
|  | " <b>Error</b></td></tr>\n" | 
|  | "<tr><td> </td></tr></table>\n" | 
|  | "<blockquote>\n" | 
|  | "<H1> Internal Server Error</H1>\n" | 
|  | " This server was unable to complete the request\n" | 
|  | "<p></blockquote>\n" | 
|  | "<table width=100% cellpadding=0 cellspacing=0>" | 
|  | "<tr><td bgcolor=#3366cc><img alt=\"\" width=1 height=4></td></tr>" | 
|  | "</table>" | 
|  | "</body></html>\n"; | 
|  | buffer.Write(message.data(), message.size()); | 
|  | const std::string correct_result = | 
|  | "HTTP/1.1 500 Service Unavailable\r\n" | 
|  | "Connection: close\r\n" | 
|  | "\r\n" | 
|  | "<html><head>\n" | 
|  | "<meta http-equiv=\"content-type\"" | 
|  | " content=\"text/html;charset=us-ascii\">\n" | 
|  | "<style><!--\n" | 
|  | "body {font-family: arial,sans-serif}\n" | 
|  | "div.nav {margin-top: 1ex}\n" | 
|  | "div.nav A {font-size: 10pt; font-family: arial,sans-serif}\n" | 
|  | "span.nav {font-size: 10pt; font-family: arial,sans-serif;" | 
|  | " font-weight: bold}\n" | 
|  | "div.nav A,span.big {font-size: 12pt; color: #0000cc}\n" | 
|  | "div.nav A {font-size: 10pt; color: black}\n" | 
|  | "A.l:link {color: #6f6f6f}\n" | 
|  | "A.u:link {color: green}\n" | 
|  | "//--></style>\n" | 
|  | "</head>\n" | 
|  | "<body text=#000000 bgcolor=#ffffff>\n" | 
|  | "<table border=0 cellpadding=2 cellspacing=0 width=100%>" | 
|  | "<tr><td rowspan=3 width=1% nowrap>\n" | 
|  | "<b>" | 
|  | "<font face=times color=#0039b6 size=10>G</font>" | 
|  | "<font face=times color=#c41200 size=10>o</font>" | 
|  | "<font face=times color=#f3c518 size=10>o</font>" | 
|  | "<font face=times color=#0039b6 size=10>g</font>" | 
|  | "<font face=times color=#30a72f size=10>l</font>" | 
|  | "<font face=times color=#c41200 size=10>e</font>" | 
|  | "  </b>\n" | 
|  | "<td> </td></tr>\n" | 
|  | "<tr><td bgcolor=#3366cc><font face=arial,sans-serif color=#ffffff>" | 
|  | " <b>Error</b></td></tr>\n" | 
|  | "<tr><td> </td></tr></table>\n" | 
|  | "<blockquote>\n" | 
|  | "<H1> Internal Server Error</H1>\n" | 
|  | " This server was unable to complete the request\n" | 
|  | "<p></blockquote>\n" | 
|  | "<table width=100% cellpadding=0 cellspacing=0>" | 
|  | "<tr><td bgcolor=#3366cc><img alt=\"\" width=1 height=4></td></tr>" | 
|  | "</table>" | 
|  | "</body></html>\n"; | 
|  | EXPECT_EQ(correct_result, buffer.GetReadableRegion()); | 
|  | } | 
|  |  | 
|  | TEST_F(SimpleBufferTest, ReleaseAsSlice) { | 
|  | SimpleBuffer buffer; | 
|  |  | 
|  | buffer.WriteString("abc"); | 
|  | SimpleBuffer::ReleasedBuffer released = buffer.Release(); | 
|  | EXPECT_EQ("abc", absl::string_view(released.buffer.get(), released.size)); | 
|  |  | 
|  | char* readable_ptr = nullptr; | 
|  | int readable_size = 0; | 
|  | buffer.GetReadablePtr(&readable_ptr, &readable_size); | 
|  | EXPECT_EQ(nullptr, readable_ptr); | 
|  | EXPECT_EQ(0, readable_size); | 
|  |  | 
|  | buffer.WriteString("def"); | 
|  | released = buffer.Release(); | 
|  | buffer.GetReadablePtr(&readable_ptr, &readable_size); | 
|  | EXPECT_EQ(nullptr, readable_ptr); | 
|  | EXPECT_EQ(0, readable_size); | 
|  | EXPECT_EQ("def", absl::string_view(released.buffer.get(), released.size)); | 
|  | } | 
|  |  | 
|  | TEST_F(SimpleBufferTest, EmptyBufferReleaseAsSlice) { | 
|  | SimpleBuffer buffer; | 
|  | char* readable_ptr = nullptr; | 
|  | int readable_size = 0; | 
|  |  | 
|  | SimpleBuffer::ReleasedBuffer released = buffer.Release(); | 
|  | buffer.GetReadablePtr(&readable_ptr, &readable_size); | 
|  | EXPECT_EQ(nullptr, readable_ptr); | 
|  | EXPECT_EQ(0, readable_size); | 
|  | EXPECT_TRUE(released.buffer == nullptr); | 
|  | EXPECT_EQ(released.size, 0u); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | }  // namespace test | 
|  |  | 
|  | }  // namespace quiche |