fd/ops: Add readAllExact()

This is a minor shortcut for `readAllFixed` which requires the entire
input to be read exactly.

Change-Id: Ia1208099ef71d2045050eae1483f8ee0b13c7033
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include-fd/stdplus/fd/ops.hpp b/include-fd/stdplus/fd/ops.hpp
index 53380d3..20395ab 100644
--- a/include-fd/stdplus/fd/ops.hpp
+++ b/include-fd/stdplus/fd/ops.hpp
@@ -36,6 +36,8 @@
                                        std::span<const std::byte> data,
                                        SendFlags flags);
 
+void verifyExact(size_t expected, size_t actual);
+
 template <typename Fun, typename Container, typename... Args>
 auto alignedOp(Fun&& fun, Fd& fd, Container&& c, Args&&... args)
 {
@@ -126,6 +128,13 @@
 }
 
 template <typename T>
+inline auto readAllExact(Fd& fd, T&& t)
+{
+    auto s = raw::asSpan<std::byte>(t);
+    detail::verifyExact(s.size(), detail::readAllFixed(fd, 1, s).size());
+}
+
+template <typename T>
 inline void recvExact(Fd& fd, T&& t, RecvFlags flags = {})
 {
     detail::recvExact(fd, raw::asSpan<std::byte>(t), flags);
diff --git a/src/fd/ops.cpp b/src/fd/ops.cpp
index d05e9b2..ca549f7 100644
--- a/src/fd/ops.cpp
+++ b/src/fd/ops.cpp
@@ -226,6 +226,14 @@
     }
 }
 
+void verifyExact(size_t expected, size_t actual)
+{
+    if (expected != actual)
+    {
+        throw exception::WouldBlock("verifyExact");
+    }
+}
+
 } // namespace detail
 } // namespace fd
 } // namespace stdplus
diff --git a/test/fd/ops.cpp b/test/fd/ops.cpp
index 708a0ff..3fc511c 100644
--- a/test/fd/ops.cpp
+++ b/test/fd/ops.cpp
@@ -352,4 +352,61 @@
     EXPECT_THROW(readAllFixed(fd, buf), std::system_error);
 }
 
+TEST(ReadAllExact, FailEmpty)
+{
+    testing::StrictMock<FdMock> fd;
+    {
+        testing::InSequence seq;
+        EXPECT_CALL(fd, read(_))
+            .WillRepeatedly(testing::Throw(exception::Eof("test")));
+    }
+    std::array<char, 16> buf;
+    EXPECT_THROW(readAllExact(fd, buf), std::system_error);
+}
+
+TEST(ReadAllExact, FailPartial)
+{
+    testing::StrictMock<FdMock> fd;
+    {
+        testing::InSequence seq;
+        EXPECT_CALL(fd, read(SizeIs(Ge(4)))).WillOnce(readSv("alph"));
+        EXPECT_CALL(fd, read(SizeIs(Ge(2)))).WillOnce(readSv("a "));
+        EXPECT_CALL(fd, read(SizeIs(Ge(3)))).WillOnce(readSv("one"));
+        EXPECT_CALL(fd, read(_))
+            .WillRepeatedly(testing::Throw(exception::Eof("test")));
+    }
+    std::array<char, 16> buf;
+    EXPECT_THROW(readAllExact(fd, buf), std::system_error);
+}
+
+TEST(ReadAllExact, Success)
+{
+    testing::StrictMock<FdMock> fd;
+    {
+        testing::InSequence seq;
+        EXPECT_CALL(fd, read(SizeIs(Ge(4)))).WillOnce(readSv("alph"));
+        EXPECT_CALL(fd, read(SizeIs(Ge(2)))).WillOnce(readSv("a "));
+        EXPECT_CALL(fd, read(SizeIs(Ge(10)))).WillOnce(readSv("one tenner"));
+        EXPECT_CALL(fd, read(_))
+            .WillRepeatedly(testing::Throw(exception::Eof("test")));
+    }
+    std::array<char, 16> buf;
+    readAllExact(fd, buf);
+    EXPECT_EQ(std::string_view(buf.begin(), buf.end()), "alpha one tenner");
+}
+
+TEST(ReadAllExact, TooMuch)
+{
+    testing::StrictMock<FdMock> fd;
+    {
+        testing::InSequence seq;
+        EXPECT_CALL(fd, read(SizeIs(Ge(4)))).WillOnce(readSv("alph"));
+        EXPECT_CALL(fd, read(SizeIs(Ge(2)))).WillOnce(readSv("a "));
+        EXPECT_CALL(fd, read(SizeIs(Ge(10)))).WillOnce(readSv("one tenner"));
+        EXPECT_CALL(fd, read(SizeIs(Ge(1)))).WillOnce(readSv("r"));
+    }
+    std::array<char, 16> buf;
+    EXPECT_THROW(readAllExact(fd, buf), std::system_error);
+}
+
 } // namespace stdplus::fd