Add various tests for memory BIOs.
Our test coverage for BIOs isn't great. Fill in missing memory BIO
tests, in preparation for reworking it a bit to be size_t-clean.
Change-Id: I77aeab93d6d9275c65e998d517463f4cc10efcf3
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/58545
Commit-Queue: Bob Beck <bbe@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/crypto/bio/bio_test.cc b/crypto/bio/bio_test.cc
index 342325c..28b9c6d 100644
--- a/crypto/bio/bio_test.cc
+++ b/crypto/bio/bio_test.cc
@@ -219,6 +219,217 @@
}
}
+TEST(BIOTest, MemReadOnly) {
+ // A memory BIO created from |BIO_new_mem_buf| is a read-only buffer.
+ static const char kData[] = "abcdefghijklmno";
+ bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(kData, strlen(kData)));
+ ASSERT_TRUE(bio);
+
+ // Writing to read-only buffers should fail.
+ EXPECT_EQ(BIO_write(bio.get(), kData, strlen(kData)), -1);
+
+ const uint8_t *contents;
+ size_t len;
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+ EXPECT_EQ(Bytes(contents, len), Bytes(kData));
+ EXPECT_EQ(BIO_eof(bio.get()), 0);
+
+ // Read less than the whole buffer.
+ char buf[6];
+ int ret = BIO_read(bio.get(), buf, sizeof(buf));
+ ASSERT_GT(ret, 0);
+ EXPECT_EQ(Bytes(buf, ret), Bytes("abcdef"));
+
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+ EXPECT_EQ(Bytes(contents, len), Bytes("ghijklmno"));
+ EXPECT_EQ(BIO_eof(bio.get()), 0);
+
+ ret = BIO_read(bio.get(), buf, sizeof(buf));
+ ASSERT_GT(ret, 0);
+ EXPECT_EQ(Bytes(buf, ret), Bytes("ghijkl"));
+
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+ EXPECT_EQ(Bytes(contents, len), Bytes("mno"));
+ EXPECT_EQ(BIO_eof(bio.get()), 0);
+
+ // Read the remainder of the buffer.
+ ret = BIO_read(bio.get(), buf, sizeof(buf));
+ ASSERT_GT(ret, 0);
+ EXPECT_EQ(Bytes(buf, ret), Bytes("mno"));
+
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+ EXPECT_EQ(Bytes(contents, len), Bytes(""));
+ EXPECT_EQ(BIO_eof(bio.get()), 1);
+
+ // By default, reading from a consumed read-only buffer returns EOF.
+ EXPECT_EQ(BIO_read(bio.get(), buf, sizeof(buf)), 0);
+ EXPECT_FALSE(BIO_should_read(bio.get()));
+
+ // A memory BIO can be configured to return an error instead of EOF. This is
+ // error is returned as retryable. (This is not especially useful here. It
+ // makes more sense for a writable BIO.)
+ EXPECT_EQ(BIO_set_mem_eof_return(bio.get(), -1), 1);
+ EXPECT_EQ(BIO_read(bio.get(), buf, sizeof(buf)), -1);
+ EXPECT_TRUE(BIO_should_read(bio.get()));
+
+ // Read exactly the right number of bytes, to test the boundary condition is
+ // correct.
+ bio.reset(BIO_new_mem_buf("abc", 3));
+ ASSERT_TRUE(bio);
+ ret = BIO_read(bio.get(), buf, 3);
+ ASSERT_GT(ret, 0);
+ EXPECT_EQ(Bytes(buf, ret), Bytes("abc"));
+ EXPECT_EQ(BIO_eof(bio.get()), 1);
+}
+
+TEST(BIOTest, MemWritable) {
+ // A memory BIO created from |BIO_new| is writable.
+ bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
+ ASSERT_TRUE(bio);
+
+ // It is initially empty.
+ const uint8_t *contents;
+ size_t len;
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+ EXPECT_EQ(Bytes(contents, len), Bytes(""));
+ EXPECT_EQ(BIO_eof(bio.get()), 1);
+
+ // Reading from it should default to returning a retryable error.
+ char buf[32];
+ EXPECT_EQ(BIO_read(bio.get(), buf, sizeof(buf)), -1);
+ EXPECT_TRUE(BIO_should_read(bio.get()));
+
+ // This can be configured to return an EOF.
+ EXPECT_EQ(BIO_set_mem_eof_return(bio.get(), 0), 1);
+ EXPECT_EQ(BIO_read(bio.get(), buf, sizeof(buf)), 0);
+ EXPECT_FALSE(BIO_should_read(bio.get()));
+
+ // Restore the default. A writable memory |BIO| is typically used in this mode
+ // so additional data can be written when exhausted.
+ EXPECT_EQ(BIO_set_mem_eof_return(bio.get(), -1), 1);
+
+ // Writes append to the buffer.
+ ASSERT_EQ(BIO_write(bio.get(), "abcdef", 6), 6);
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+ EXPECT_EQ(Bytes(contents, len), Bytes("abcdef"));
+ EXPECT_EQ(BIO_eof(bio.get()), 0);
+
+ // Writes can include embedded NULs.
+ ASSERT_EQ(BIO_write(bio.get(), "\0ghijk", 6), 6);
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+ EXPECT_EQ(Bytes(contents, len), Bytes("abcdef\0ghijk", 12));
+ EXPECT_EQ(BIO_eof(bio.get()), 0);
+
+ // Do a partial read.
+ int ret = BIO_read(bio.get(), buf, 4);
+ ASSERT_GT(ret, 0);
+ EXPECT_EQ(Bytes(buf, ret), Bytes("abcd"));
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+ EXPECT_EQ(Bytes(contents, len), Bytes("ef\0ghijk", 8));
+ EXPECT_EQ(BIO_eof(bio.get()), 0);
+
+ // Reads and writes may alternate.
+ ASSERT_EQ(BIO_write(bio.get(), "lmnopq", 6), 6);
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+ EXPECT_EQ(Bytes(contents, len), Bytes("ef\0ghijklmnopq", 14));
+ EXPECT_EQ(BIO_eof(bio.get()), 0);
+
+ // Reads may consume embedded NULs.
+ ret = BIO_read(bio.get(), buf, 4);
+ ASSERT_GT(ret, 0);
+ EXPECT_EQ(Bytes(buf, ret), Bytes("ef\0g", 4));
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+ EXPECT_EQ(Bytes(contents, len), Bytes("hijklmnopq"));
+ EXPECT_EQ(BIO_eof(bio.get()), 0);
+
+ // The read buffer exceeds the |BIO|, so we consume everything.
+ ret = BIO_read(bio.get(), buf, sizeof(buf));
+ ASSERT_GT(ret, 0);
+ EXPECT_EQ(Bytes(buf, ret), Bytes("hijklmnopq"));
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+ EXPECT_EQ(Bytes(contents, len), Bytes(""));
+ EXPECT_EQ(BIO_eof(bio.get()), 1);
+
+ // The |BIO| is now empty.
+ EXPECT_EQ(BIO_read(bio.get(), buf, sizeof(buf)), -1);
+ EXPECT_TRUE(BIO_should_read(bio.get()));
+
+ // Repeat the above, reading exactly the right number of bytes, to test the
+ // boundary condition is correct.
+ ASSERT_EQ(BIO_write(bio.get(), "abc", 3), 3);
+ ret = BIO_read(bio.get(), buf, 3);
+ EXPECT_EQ(Bytes(buf, ret), Bytes("abc"));
+ EXPECT_EQ(BIO_read(bio.get(), buf, sizeof(buf)), -1);
+ EXPECT_TRUE(BIO_should_read(bio.get()));
+ EXPECT_EQ(BIO_eof(bio.get()), 1);
+}
+
+TEST(BIOTest, MemGets) {
+ const struct {
+ std::string bio;
+ int gets_len;
+ std::string gets_result;
+ } kGetsTests[] = {
+ // BIO_gets should stop at the first newline. If the buffer is too small,
+ // stop there instead. Note the buffer size
+ // includes a trailing NUL.
+ {"123456789\n123456789", 5, "1234"},
+ {"123456789\n123456789", 9, "12345678"},
+ {"123456789\n123456789", 10, "123456789"},
+ {"123456789\n123456789", 11, "123456789\n"},
+ {"123456789\n123456789", 12, "123456789\n"},
+ {"123456789\n123456789", 256, "123456789\n"},
+
+ // If we run out of buffer, read the whole buffer.
+ {"12345", 5, "1234"},
+ {"12345", 6, "12345"},
+ {"12345", 10, "12345"},
+
+ // NUL bytes do not terminate gets.
+ // TODO(davidben): File BIOs don't get this right. It's unclear if it's
+ // even possible to use fgets correctly here.
+ {std::string("abc\0def\nghi", 11), 256, std::string("abc\0def\n", 8)},
+
+ // An output size of one means we cannot read any bytes. Only the trailing
+ // NUL is included.
+ {"12345", 1, ""},
+
+ // Empty line.
+ {"\nabcdef", 256, "\n"},
+ // Empty BIO.
+ {"", 256, ""},
+ };
+ for (const auto& t : kGetsTests) {
+ SCOPED_TRACE(t.bio);
+ SCOPED_TRACE(t.gets_len);
+
+ bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(t.bio.data(), t.bio.size()));
+ ASSERT_TRUE(bio);
+
+ std::vector<char> buf(t.gets_len, 'a');
+ int ret = BIO_gets(bio.get(), buf.data(), t.gets_len);
+ ASSERT_GE(ret, 0);
+ // |BIO_gets| should write a NUL terminator, not counted in the return
+ // value.
+ EXPECT_EQ(Bytes(buf.data(), ret + 1),
+ Bytes(t.gets_result.data(), t.gets_result.size() + 1));
+
+ // The remaining data should still be in the BIO.
+ const uint8_t *contents;
+ size_t len;
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+ EXPECT_EQ(Bytes(contents, len), Bytes(t.bio.substr(ret)));
+ }
+
+ // Negative and zero lengths should not output anything, even a trailing NUL.
+ bssl::UniquePtr<BIO> bio(BIO_new_mem_buf("12345", -1));
+ ASSERT_TRUE(bio);
+ char c = 'a';
+ EXPECT_EQ(0, BIO_gets(bio.get(), &c, -1));
+ EXPECT_EQ(0, BIO_gets(bio.get(), &c, 0));
+ EXPECT_EQ(c, 'a');
+}
+
// Run through the tests twice, swapping |bio1| and |bio2|, for symmetry.
class BIOPairTest : public testing::TestWithParam<bool> {};