Provide an abstract API for a stream that one can write bytes into.

For QUIC, we have a QuicPacketWriter as an API for outputting all datagrams used by the QUIC connection; since encapsulated WebTransport (aka WebTransport over HTTP/2) has a single bytestream as an output, we need a similar type for that.

This CL takes the existing WebTransport stream write API, cleans it up, and moves it into its own class.

Notable changes to the write API:
- Make it vectorized
- Let it return a status object instead of simple true/false
- Merge writing and sending fin into a single method, the same way as existing QUIC APIs work

Parts of the old API are still kept around for compatibility with existing calls.

PiperOrigin-RevId: 501849717
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 28ba715..0db8800 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -56,6 +56,7 @@
     "common/quiche_mem_slice_storage.h",
     "common/quiche_protocol_flags_list.h",
     "common/quiche_random.h",
+    "common/quiche_stream.h",
     "common/quiche_text_utils.h",
     "common/simple_buffer_allocator.h",
     "common/structured_headers.h",
diff --git a/build/source_list.gni b/build/source_list.gni
index 5779607..62962e7 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -56,6 +56,7 @@
     "src/quiche/common/quiche_mem_slice_storage.h",
     "src/quiche/common/quiche_protocol_flags_list.h",
     "src/quiche/common/quiche_random.h",
+    "src/quiche/common/quiche_stream.h",
     "src/quiche/common/quiche_text_utils.h",
     "src/quiche/common/simple_buffer_allocator.h",
     "src/quiche/common/structured_headers.h",
diff --git a/build/source_list.json b/build/source_list.json
index ad20600..e7cde56 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -55,6 +55,7 @@
     "quiche/common/quiche_mem_slice_storage.h",
     "quiche/common/quiche_protocol_flags_list.h",
     "quiche/common/quiche_random.h",
+    "quiche/common/quiche_stream.h",
     "quiche/common/quiche_text_utils.h",
     "quiche/common/simple_buffer_allocator.h",
     "quiche/common/structured_headers.h",
diff --git a/quiche/common/quiche_stream.h b/quiche/common/quiche_stream.h
new file mode 100644
index 0000000..c3dc621
--- /dev/null
+++ b/quiche/common/quiche_stream.h
@@ -0,0 +1,94 @@
+// Copyright 2023 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.
+
+// General-purpose abstractions for a write stream.
+
+#ifndef QUICHE_COMMON_QUICHE_STREAM_H_
+#define QUICHE_COMMON_QUICHE_STREAM_H_
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace quiche {
+
+// A general-purpose visitor API that gets notifications for WriteStream-related
+// events.
+class QUICHE_EXPORT WriteStreamVisitor {
+ public:
+  virtual ~WriteStreamVisitor() {}
+
+  // Called whenever the stream is not write-blocked and can accept new data.
+  virtual void OnCanWrite() = 0;
+};
+
+// Options for writing data into a WriteStream.
+class QUICHE_EXPORT StreamWriteOptions {
+ public:
+  StreamWriteOptions() = default;
+
+  // If send_fin() is sent to true, the write operation also sends a FIN on the
+  // stream.
+  bool send_fin() const { return send_fin_; }
+  void set_send_fin(bool send_fin) { send_fin_ = send_fin; }
+
+ private:
+  bool send_fin_ = false;
+};
+
+inline constexpr StreamWriteOptions kDefaultStreamWriteOptions =
+    StreamWriteOptions();
+
+// WriteStream is an object that can accept a stream of bytes.
+//
+// The writes into a WriteStream are all-or-nothing.  A WriteStream object has
+// to either accept all data written into it by returning absl::OkStatus, or ask
+// the caller to try again once via OnCanWrite() by returning
+// absl::UnavailableError.
+class QUICHE_EXPORT WriteStream {
+ public:
+  virtual ~WriteStream() {}
+
+  // Writes |data| into the stream.
+  virtual absl::Status Writev(absl::Span<const absl::string_view> data,
+                              const StreamWriteOptions& options) = 0;
+
+  // Indicates whether it is possible to write into stream right now.
+  virtual bool CanWrite() const = 0;
+
+  // Legacy convenience method for writing a single string_view.  New users
+  // should use quiche::WriteIntoStream instead, since this method does not
+  // return useful failure information.
+  [[nodiscard]] bool SendFin() {
+    StreamWriteOptions options;
+    options.set_send_fin(true);
+    return Writev(absl::Span<const absl::string_view>(), options).ok();
+  }
+
+  // Legacy convenience method for writing a single string_view.  New users
+  // should use quiche::WriteIntoStream instead, since this method does not
+  // return useful failure information.
+  [[nodiscard]] bool Write(absl::string_view data) {
+    return Writev(absl::MakeSpan(&data, 1), kDefaultStreamWriteOptions).ok();
+  }
+};
+
+// Convenience methods to write a single chunk of data into the stream.
+inline absl::Status WriteIntoStream(
+    WriteStream& stream, absl::string_view data,
+    const StreamWriteOptions& options = kDefaultStreamWriteOptions) {
+  return stream.Writev(absl::MakeSpan(&data, 1), options);
+}
+
+// Convenience methods to send a FIN on the stream.
+inline absl::Status SendFinOnStream(WriteStream& stream) {
+  StreamWriteOptions options;
+  options.set_send_fin(true);
+  return stream.Writev(absl::Span<const absl::string_view>(), options);
+}
+
+}  // namespace quiche
+
+#endif  // QUICHE_COMMON_QUICHE_STREAM_H_
diff --git a/quiche/common/test_tools/quiche_test_utils.h b/quiche/common/test_tools/quiche_test_utils.h
index 8fe1f7a..c233051 100644
--- a/quiche/common/test_tools/quiche_test_utils.h
+++ b/quiche/common/test_tools/quiche_test_utils.h
@@ -9,6 +9,7 @@
 
 #include "absl/strings/string_view.h"
 #include "quiche/common/platform/api/quiche_iovec.h"
+#include "quiche/common/platform/api/quiche_test.h"
 
 namespace quiche {
 namespace test {
@@ -26,6 +27,19 @@
 // This function checks if IDNAs are supported.
 bool GoogleUrlSupportsIdnaForTest();
 
+// Abseil does not provide absl::Status-related macros, so we have to provide
+// those instead.
+MATCHER(IsOk, "Checks if an instance of absl::Status is ok.") {
+  if (arg.ok()) {
+    return true;
+  }
+  *result_listener << "Expected status OK, got " << arg;
+  return false;
+}
+
+#define QUICHE_EXPECT_OK(arg) EXPECT_THAT((arg), ::quiche::test::IsOk())
+#define QUICHE_ASSERT_OK(arg) ASSERT_THAT((arg), ::quiche::test::IsOk())
+
 }  // namespace test
 }  // namespace quiche
 
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index 2524521..4625ebb 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -15,6 +15,7 @@
 #include "absl/strings/string_view.h"
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
+#include "absl/types/span.h"
 #include "quiche/quic/core/crypto/null_encrypter.h"
 #include "quiche/quic/core/crypto/quic_client_session_cache.h"
 #include "quiche/quic/core/frames/quic_blocked_frame.h"
@@ -72,6 +73,8 @@
 #include "quiche/quic/tools/quic_simple_client_stream.h"
 #include "quiche/quic/tools/quic_simple_server_stream.h"
 #include "quiche/common/platform/api/quiche_test.h"
+#include "quiche/common/quiche_stream.h"
+#include "quiche/common/test_tools/quiche_test_utils.h"
 #include "quiche/spdy/core/http2_header_block.h"
 
 using spdy::Http2HeaderBlock;
@@ -831,7 +834,8 @@
         visitor = visitor_owned.get();
         stream->SetVisitor(std::move(visitor_owned));
       }
-      EXPECT_CALL(*visitor, OnCanRead()).WillOnce(Assign(&can_read, true));
+      EXPECT_CALL(*visitor, OnCanRead())
+          .WillRepeatedly(Assign(&can_read, true));
       client_->WaitUntil(5000 /*ms*/, [&can_read]() { return can_read; });
       if (!can_read) {
         ADD_FAILURE() << "Waiting for readable data on stream " << id
@@ -6600,7 +6604,7 @@
       .WillOnce(Assign(&data_acknowledged, true));
   outgoing_stream->SetVisitor(std::move(stream_visitor));
 
-  EXPECT_TRUE(outgoing_stream->Write("test"));
+  QUICHE_EXPECT_OK(quiche::WriteIntoStream(*outgoing_stream, "test"));
   EXPECT_TRUE(outgoing_stream->SendFin());
 
   bool stream_received = false;
@@ -6639,7 +6643,7 @@
   WebTransportStream* outgoing_stream =
       session->OpenOutgoingUnidirectionalStream();
   ASSERT_TRUE(outgoing_stream != nullptr);
-  EXPECT_TRUE(outgoing_stream->Write("test"));
+  QUICHE_EXPECT_OK(quiche::WriteIntoStream(*outgoing_stream, "test"));
   EXPECT_TRUE(outgoing_stream->SendFin());
 
   bool stream_received = false;
@@ -6679,7 +6683,7 @@
       .WillOnce(Assign(&data_acknowledged, true));
   stream->SetVisitor(std::move(stream_visitor_owned));
 
-  EXPECT_TRUE(stream->Write("test"));
+  QUICHE_EXPECT_OK(quiche::WriteIntoStream(*stream, "test"));
   EXPECT_TRUE(stream->SendFin());
 
   std::string received_data =
@@ -6706,7 +6710,7 @@
 
   WebTransportStream* stream = session->OpenOutgoingBidirectionalStream();
   ASSERT_TRUE(stream != nullptr);
-  EXPECT_TRUE(stream->Write("test"));
+  QUICHE_EXPECT_OK(quiche::WriteIntoStream(*stream, "test"));
   EXPECT_TRUE(stream->SendFin());
 
   std::string received_data = ReadDataFromWebTransportStreamUntilFin(stream);
@@ -6735,11 +6739,16 @@
 
   WebTransportStream* stream = session->AcceptIncomingBidirectionalStream();
   ASSERT_TRUE(stream != nullptr);
-  EXPECT_TRUE(stream->Write("test"));
-  EXPECT_TRUE(stream->SendFin());
+  // Test the full Writev() API.
+  const std::string kLongString = std::string(16 * 1024, 'a');
+  std::vector<absl::string_view> write_vector = {"foo", "bar", "test",
+                                                 kLongString};
+  quiche::StreamWriteOptions options;
+  options.set_send_fin(true);
+  QUICHE_EXPECT_OK(stream->Writev(absl::MakeConstSpan(write_vector), options));
 
   std::string received_data = ReadDataFromWebTransportStreamUntilFin(stream);
-  EXPECT_EQ(received_data, "test");
+  EXPECT_EQ(received_data, absl::StrCat("foobartest", kLongString));
 }
 
 TEST_P(EndToEndTest, WebTransportDatagrams) {
@@ -6786,7 +6795,7 @@
   WebTransportStream* stream = session->OpenOutgoingBidirectionalStream();
   ASSERT_TRUE(stream != nullptr);
   QuicStreamId stream_id = stream->GetStreamId();
-  EXPECT_TRUE(stream->Write("test"));
+  QUICHE_EXPECT_OK(quiche::WriteIntoStream(*stream, "test"));
   // Keep stream open.
 
   bool close_received = false;
@@ -6818,7 +6827,7 @@
   WebTransportStream* stream = session->OpenOutgoingBidirectionalStream();
   ASSERT_TRUE(stream != nullptr);
   QuicStreamId stream_id = stream->GetStreamId();
-  EXPECT_TRUE(stream->Write("test"));
+  QUICHE_EXPECT_OK(quiche::WriteIntoStream(*stream, "test"));
   // Keep stream open.
 
   bool close_received = false;
@@ -6850,7 +6859,7 @@
   WebTransportStream* stream = session->OpenOutgoingUnidirectionalStream();
   ASSERT_TRUE(stream != nullptr);
   QuicStreamId stream_id = stream->GetStreamId();
-  EXPECT_TRUE(stream->Write("42 test error"));
+  QUICHE_EXPECT_OK(quiche::WriteIntoStream(*stream, "42 test error"));
   EXPECT_TRUE(stream->SendFin());
 
   // Have some other streams open pending, to ensure they are closed properly.
@@ -6890,7 +6899,7 @@
   WebTransportStream* stream = session->OpenOutgoingBidirectionalStream();
   QuicStreamId id1 = stream->GetStreamId();
   ASSERT_TRUE(stream != nullptr);
-  EXPECT_TRUE(stream->Write("test"));
+  QUICHE_EXPECT_OK(quiche::WriteIntoStream(*stream, "test"));
   stream->ResetWithUserCode(42);
 
   // This read fails if the stream is closed in both directions, since that
@@ -6901,7 +6910,7 @@
   stream = session->OpenOutgoingBidirectionalStream();
   QuicStreamId id2 = stream->GetStreamId();
   ASSERT_TRUE(stream != nullptr);
-  EXPECT_TRUE(stream->Write("test"));
+  QUICHE_EXPECT_OK(quiche::WriteIntoStream(*stream, "test"));
   stream->SendStopSending(24);
 
   std::array<std::string, 2> expected_log = {
@@ -6941,7 +6950,7 @@
 
   WebTransportStream* stream = session->OpenOutgoingBidirectionalStream();
   ASSERT_TRUE(stream != nullptr);
-  EXPECT_TRUE(stream->Write("test"));
+  QUICHE_EXPECT_OK(quiche::WriteIntoStream(*stream, "test"));
   EXPECT_TRUE(stream->SendFin());
 
   EXPECT_TRUE(client_->WaitUntil(-1, [this, connect_stream_id]() {
diff --git a/quiche/quic/core/http/web_transport_stream_adapter.cc b/quiche/quic/core/http/web_transport_stream_adapter.cc
index 24d7bf5..6ed6975 100644
--- a/quiche/quic/core/http/web_transport_stream_adapter.cc
+++ b/quiche/quic/core/http/web_transport_stream_adapter.cc
@@ -4,10 +4,13 @@
 
 #include "quiche/quic/core/http/web_transport_stream_adapter.h"
 
+#include "absl/status/status.h"
 #include "quiche/quic/core/http/web_transport_http3.h"
 #include "quiche/quic/core/quic_error_codes.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/common/platform/api/quiche_mem_slice.h"
+#include "quiche/common/quiche_buffer_allocator.h"
+#include "quiche/common/quiche_mem_slice_storage.h"
 #include "quiche/web_transport/web_transport.h"
 
 namespace quic {
@@ -41,51 +44,67 @@
   return result;
 }
 
-bool WebTransportStreamAdapter::Write(absl::string_view data) {
-  if (!CanWrite()) {
-    return false;
+absl::Status WebTransportStreamAdapter::Writev(
+    absl::Span<const absl::string_view> data,
+    const quiche::StreamWriteOptions& options) {
+  if (data.empty() && !options.send_fin()) {
+    return absl::InvalidArgumentError(
+        "Writev() called without any data or a FIN");
+  }
+  const absl::Status initial_check_status = CheckBeforeStreamWrite();
+  if (!initial_check_status.ok()) {
+    return initial_check_status;
   }
 
-  quiche::QuicheMemSlice memslice(quiche::QuicheBuffer::Copy(
-      session_->connection()->helper()->GetStreamSendBufferAllocator(), data));
+  std::vector<iovec> iovecs;
+  size_t total_size = 0;
+  iovecs.resize(data.size());
+  for (size_t i = 0; i < data.size(); i++) {
+    // QuicheMemSliceStorage only reads iovec, thus this is safe.
+    iovecs[i].iov_base = const_cast<char*>(data[i].data());
+    iovecs[i].iov_len = data[i].size();
+    total_size += data[i].size();
+  }
+  quiche::QuicheMemSliceStorage storage(
+      iovecs.data(), iovecs.size(),
+      session_->connection()->helper()->GetStreamSendBufferAllocator(),
+      GetQuicFlag(quic_send_buffer_max_data_slice_size));
   QuicConsumedData consumed =
-      stream_->WriteMemSlices(absl::MakeSpan(&memslice, 1), /*fin=*/false);
+      stream_->WriteMemSlices(storage.ToSpan(), /*fin=*/options.send_fin());
 
-  if (consumed.bytes_consumed == data.size()) {
-    return true;
+  if (consumed.bytes_consumed == total_size) {
+    return absl::OkStatus();
   }
   if (consumed.bytes_consumed == 0) {
-    return false;
+    return absl::UnavailableError("Stream write-blocked");
   }
   // WebTransportStream::Write() is an all-or-nothing write API.  To achieve
   // that property, it relies on WriteMemSlices() being an all-or-nothing API.
   // If WriteMemSlices() fails to provide that guarantee, we have no way to
   // communicate a partial write to the caller, and thus it's safer to just
   // close the connection.
+  constexpr absl::string_view kErrorMessage =
+      "WriteMemSlices() unexpectedly partially consumed the input data";
   QUIC_BUG(WebTransportStreamAdapter partial write)
-      << "WriteMemSlices() unexpectedly partially consumed the input "
-         "data, provided: "
-      << data.size() << ", written: " << consumed.bytes_consumed;
-  stream_->OnUnrecoverableError(
-      QUIC_INTERNAL_ERROR,
-      "WriteMemSlices() unexpectedly partially consumed the input data");
-  return false;
+      << kErrorMessage << ", provided: " << total_size
+      << ", written: " << consumed.bytes_consumed;
+  stream_->OnUnrecoverableError(QUIC_INTERNAL_ERROR,
+                                std::string(kErrorMessage));
+  return absl::InternalError(kErrorMessage);
 }
 
-bool WebTransportStreamAdapter::SendFin() {
-  if (!CanWrite()) {
-    return false;
+absl::Status WebTransportStreamAdapter::CheckBeforeStreamWrite() const {
+  if (stream_->write_side_closed() || stream_->fin_buffered()) {
+    return absl::FailedPreconditionError("Stream write side is closed");
   }
-
-  quiche::QuicheMemSlice empty;
-  QuicConsumedData consumed =
-      stream_->WriteMemSlices(absl::MakeSpan(&empty, 1), /*fin=*/true);
-  QUICHE_DCHECK_EQ(consumed.bytes_consumed, 0u);
-  return consumed.fin_consumed;
+  if (!stream_->CanWriteNewData()) {
+    return absl::UnavailableError("Stream write-blocked");
+  }
+  return absl::OkStatus();
 }
 
 bool WebTransportStreamAdapter::CanWrite() const {
-  return stream_->CanWriteNewData() && !stream_->write_side_closed();
+  return CheckBeforeStreamWrite().ok();
 }
 
 size_t WebTransportStreamAdapter::ReadableBytes() const {
diff --git a/quiche/quic/core/http/web_transport_stream_adapter.h b/quiche/quic/core/http/web_transport_stream_adapter.h
index 621120b..6a6f9fc 100644
--- a/quiche/quic/core/http/web_transport_stream_adapter.h
+++ b/quiche/quic/core/http/web_transport_stream_adapter.h
@@ -25,8 +25,8 @@
   // WebTransportStream implementation.
   ABSL_MUST_USE_RESULT ReadResult Read(absl::Span<char> output) override;
   ABSL_MUST_USE_RESULT ReadResult Read(std::string* output) override;
-  ABSL_MUST_USE_RESULT bool Write(absl::string_view data) override;
-  ABSL_MUST_USE_RESULT bool SendFin() override;
+  absl::Status Writev(absl::Span<const absl::string_view> data,
+                      const quiche::StreamWriteOptions& options) override;
   bool CanWrite() const override;
   size_t ReadableBytes() const override;
   void SetVisitor(std::unique_ptr<WebTransportStreamVisitor> visitor) override {
@@ -53,6 +53,8 @@
   void OnCanWriteNewData();
 
  private:
+  absl::Status CheckBeforeStreamWrite() const;
+
   QuicSession* session_;            // Unowned.
   QuicStream* stream_;              // Unowned.
   QuicStreamSequencer* sequencer_;  // Unowned.
diff --git a/quiche/quic/tools/web_transport_test_visitors.h b/quiche/quic/tools/web_transport_test_visitors.h
index c311ff4..0770af6 100644
--- a/quiche/quic/tools/web_transport_test_visitors.h
+++ b/quiche/quic/tools/web_transport_test_visitors.h
@@ -7,11 +7,13 @@
 
 #include <string>
 
+#include "absl/status/status.h"
 #include "quiche/quic/core/web_transport_interface.h"
 #include "quiche/quic/platform/api/quic_logging.h"
 #include "quiche/common/platform/api/quiche_logging.h"
 #include "quiche/common/platform/api/quiche_mem_slice.h"
 #include "quiche/common/quiche_circular_deque.h"
+#include "quiche/common/quiche_stream.h"
 #include "quiche/common/simple_buffer_allocator.h"
 #include "quiche/spdy/core/http2_header_block.h"
 
@@ -63,11 +65,10 @@
     }
 
     if (!buffer_.empty()) {
-      bool success = stream_->Write(buffer_);
+      absl::Status status = quiche::WriteIntoStream(*stream_, buffer_);
       QUIC_DVLOG(1) << "Attempted writing on WebTransport bidirectional stream "
-                    << stream_->GetStreamId()
-                    << ", success: " << (success ? "yes" : "no");
-      if (!success) {
+                    << stream_->GetStreamId() << ", success: " << status;
+      if (!status.ok()) {
         return;
       }
 
@@ -75,8 +76,8 @@
     }
 
     if (send_fin_ && !fin_sent_) {
-      bool success = stream_->SendFin();
-      if (success) {
+      absl::Status status = quiche::SendFinOnStream(*stream_);
+      if (status.ok()) {
         fin_sent_ = true;
       }
     }
@@ -153,14 +154,17 @@
     if (data_.empty()) {
       return;
     }
-    if (!stream_->Write(data_)) {
+    absl::Status write_status = quiche::WriteIntoStream(*stream_, data_);
+    if (!write_status.ok()) {
+      QUICHE_DLOG_IF(WARNING, !absl::IsUnavailable(write_status))
+          << "Failed to write into stream: " << write_status;
       return;
     }
     data_ = "";
-    bool fin_sent = stream_->SendFin();
+    absl::Status fin_status = quiche::SendFinOnStream(*stream_);
     QUICHE_DVLOG(1)
         << "WebTransportUnidirectionalEchoWriteVisitor finished sending data.";
-    QUICHE_DCHECK(fin_sent);
+    QUICHE_DCHECK(fin_status.ok());
   }
 
   void OnResetStreamReceived(WebTransportStreamError /*error*/) override {}
diff --git a/quiche/web_transport/test_tools/mock_web_transport.h b/quiche/web_transport/test_tools/mock_web_transport.h
index abd6d01..7df7e68 100644
--- a/quiche/web_transport/test_tools/mock_web_transport.h
+++ b/quiche/web_transport/test_tools/mock_web_transport.h
@@ -24,8 +24,10 @@
 class QUICHE_NO_EXPORT MockStream : public Stream {
   MOCK_METHOD(ReadResult, Read, (absl::Span<char> buffer), (override));
   MOCK_METHOD(ReadResult, Read, (std::string * output), (override));
-  MOCK_METHOD(bool, Write, (absl::string_view data), (override));
-  MOCK_METHOD(bool, SendFin, (), (override));
+  MOCK_METHOD(absl::Status, Writev,
+              (absl::Span<const absl::string_view> data,
+               const quiche::StreamWriteOptions& options),
+              (override));
   MOCK_METHOD(bool, CanWrite, (), (const, override));
   MOCK_METHOD(size_t, ReadableBytes, (), (const, override));
   MOCK_METHOD(StreamId, GetStreamId, (), (const, override));
diff --git a/quiche/web_transport/web_transport.h b/quiche/web_transport/web_transport.h
index ce66db5..b5ec5fc 100644
--- a/quiche/web_transport/web_transport.h
+++ b/quiche/web_transport/web_transport.h
@@ -18,6 +18,7 @@
 #include "absl/time/time.h"
 #include "absl/types/span.h"
 #include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/quiche_stream.h"
 #include "quiche/spdy/core/http2_header_block.h"
 
 namespace webtransport {
@@ -61,14 +62,12 @@
 // events related to a WebTransport stream.  The visitor object is owned by the
 // stream itself, meaning that if the stream is ever fully closed, the visitor
 // will be garbage-collected.
-class QUICHE_EXPORT StreamVisitor {
+class QUICHE_EXPORT StreamVisitor : public quiche::WriteStreamVisitor {
  public:
   virtual ~StreamVisitor() {}
 
   // Called whenever the stream has readable data available.
   virtual void OnCanRead() = 0;
-  // Called whenever the stream is not write-blocked and can accept new data.
-  virtual void OnCanWrite() = 0;
 
   // Called when RESET_STREAM is received for the stream.
   virtual void OnResetStreamReceived(StreamErrorCode error) = 0;
@@ -82,7 +81,7 @@
 
 // A stream (either bidirectional or unidirectional) that is contained within a
 // WebTransport session.
-class QUICHE_EXPORT Stream {
+class QUICHE_EXPORT Stream : public quiche::WriteStream {
  public:
   struct QUICHE_EXPORT ReadResult {
     // Number of bytes actually read.
@@ -99,16 +98,7 @@
   [[nodiscard]] virtual ReadResult Read(absl::Span<char> buffer) = 0;
   // Reads all available data and appends it to the end of |output|.
   [[nodiscard]] virtual ReadResult Read(std::string* output) = 0;
-  // Writes |data| into the stream.  WebTransport writes are all-or-nothing: the
-  // stream will either accept all of the data (potentially buffering some of
-  // it), or reject it.  In the latter case, the method will return false, and
-  // the sender has to wait until OnCanWrite() is called.
-  [[nodiscard]] virtual bool Write(absl::string_view data) = 0;
-  // Sends the FIN on the stream.  Returns true on success.
-  [[nodiscard]] virtual bool SendFin() = 0;
 
-  // Indicates whether it is possible to write into stream right now.
-  virtual bool CanWrite() const = 0;
   // Indicates the number of bytes that can be read from the stream.
   virtual size_t ReadableBytes() const = 0;