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> {};