pw_stream: Add a ReaderWriter base class

Add a `class ReaderWriter` base class that combines interfaces of
pw::stream::Reader/Writer.

Change-Id: I20a8d506523483bb9c61f3990ffea2b79a1c1a3c
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/48462
Commit-Queue: Yecheng Zhao <zyecheng@google.com>
Reviewed-by: David Rogers <davidrogers@google.com>
Reviewed-by: Ali Zhang <alizhang@google.com>
diff --git a/pw_stream/docs.rst b/pw_stream/docs.rst
index 2fcd644..749cdcd 100644
--- a/pw_stream/docs.rst
+++ b/pw_stream/docs.rst
@@ -52,6 +52,10 @@
 implementation. Note that ``Read()`` itself is **not** virtual, and should not
 be overridden.
 
+pw::stream::ReaderWriter
+-------------------------
+The class simply combines the interfaces of pw::stream::Reader/Writer.
+
 pw::stream::MemoryWriter
 ------------------------
 The ``MemoryWriter`` class implements the ``Writer`` interface by backing the
@@ -69,6 +73,10 @@
 The ``NullWriter`` class implements the ``Writer`` interface by dropping all
 requested data writes, similar to ``/dev/null``.
 
+pw::stream::NullReaderWriter
+-----------------------------
+The class simply combines pw::stream::NullReader/NullWriter.
+
 Why use pw_stream?
 ==================
 
diff --git a/pw_stream/public/pw_stream/null_stream.h b/pw_stream/public/pw_stream/null_stream.h
index b3db9c5..51ce2f5 100644
--- a/pw_stream/public/pw_stream/null_stream.h
+++ b/pw_stream/public/pw_stream/null_stream.h
@@ -36,4 +36,21 @@
   StatusWithSize DoRead(ByteSpan) final { return StatusWithSize::OutOfRange(); }
 };
 
+// Stream reader/writer that combines NullWriter and NullReader.
+class NullReaderWriter final : public ReaderWriter {
+ private:
+  NullWriter null_writer_;
+  NullReader null_reader_;
+
+  Status DoWrite(ConstByteSpan data) final { return null_writer_.Write(data); }
+
+  StatusWithSize DoRead(ByteSpan dest) final {
+    auto res = null_reader_.Read(dest);
+    if (!res.ok()) {
+      return StatusWithSize(res.status(), 0);
+    }
+    return StatusWithSize(res.value().size());
+  }
+};
+
 }  // namespace pw::stream
diff --git a/pw_stream/public/pw_stream/socket_stream.h b/pw_stream/public/pw_stream/socket_stream.h
index 1ab1161..7759d6e 100644
--- a/pw_stream/public/pw_stream/socket_stream.h
+++ b/pw_stream/public/pw_stream/socket_stream.h
@@ -31,7 +31,7 @@
 static constexpr int kExitCode = -1;
 static constexpr int kInvalidFd = -1;
 
-class SocketStream : public Writer, public Reader {
+class SocketStream : public ReaderWriter {
  public:
   explicit SocketStream() {}
   ~SocketStream();
diff --git a/pw_stream/public/pw_stream/stream.h b/pw_stream/public/pw_stream/stream.h
index 7014203..97fe069 100644
--- a/pw_stream/public/pw_stream/stream.h
+++ b/pw_stream/public/pw_stream/stream.h
@@ -144,4 +144,8 @@
   virtual StatusWithSize DoRead(ByteSpan dest) = 0;
 };
 
+// A general-purpose ReaderWriter class that combines interfaces of Reader and
+// Writer.
+class ReaderWriter : public Writer, public Reader {};
+
 }  // namespace pw::stream
diff --git a/pw_stream/stream_test.cc b/pw_stream/stream_test.cc
index fad335a..97f6e1d 100644
--- a/pw_stream/stream_test.cc
+++ b/pw_stream/stream_test.cc
@@ -33,5 +33,12 @@
   EXPECT_EQ(stream.ConservativeReadLimit(), std::numeric_limits<size_t>::max());
 }
 
+TEST(Stream, DefaultConservativeReadWriteLimit) {
+  NullReaderWriter stream;
+  EXPECT_EQ(stream.ConservativeWriteLimit(),
+            std::numeric_limits<size_t>::max());
+  EXPECT_EQ(stream.ConservativeReadLimit(), std::numeric_limits<size_t>::max());
+}
+
 }  // namespace
 }  // namespace pw::stream