Support zero-copy writes in WebTransport.
This adds support for passing a span of MemSlices into a WebTransport stream instead of string_views; the semantics are the same as the QUIC stream writes.
PiperOrigin-RevId: 777921033
diff --git a/quiche/common/quiche_stream.h b/quiche/common/quiche_stream.h
index 5a8a1f7..c8718bd 100644
--- a/quiche/common/quiche_stream.h
+++ b/quiche/common/quiche_stream.h
@@ -15,6 +15,7 @@
#include "absl/types/span.h"
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/quiche_callbacks.h"
+#include "quiche/common/quiche_mem_slice.h"
namespace quiche {
@@ -161,42 +162,51 @@
public:
virtual ~WriteStream() {}
- // Writes |data| into the stream.
- virtual absl::Status Writev(absl::Span<const absl::string_view> data,
+ // Writes `data` into the stream. If the write succeeds, the ownership is
+ // transferred to the stream; if it does not, the behavior is undefined -- the
+ // users of this API should check `CanWrite()` before calling `Writev()`.
+ virtual absl::Status Writev(absl::Span<QuicheMemSlice> 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
+ // should use quiche::SendFinOnStream 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();
+ return Writev(absl::Span<QuicheMemSlice>(), 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();
+ QuicheMemSlice slice = QuicheMemSlice::Copy(data);
+ return Writev(absl::MakeSpan(&slice, 1), kDefaultStreamWriteOptions).ok();
}
};
// Convenience methods to write a single chunk of data into the stream.
inline absl::Status WriteIntoStream(
+ WriteStream& stream, QuicheMemSlice slice,
+ const StreamWriteOptions& options = kDefaultStreamWriteOptions) {
+ return stream.Writev(absl::MakeSpan(&slice, 1), options);
+}
+inline absl::Status WriteIntoStream(
WriteStream& stream, absl::string_view data,
const StreamWriteOptions& options = kDefaultStreamWriteOptions) {
- return stream.Writev(absl::MakeSpan(&data, 1), options);
+ QuicheMemSlice slice = QuicheMemSlice::Copy(data);
+ return stream.Writev(absl::MakeSpan(&slice, 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);
+ return stream.Writev(absl::Span<QuicheMemSlice>(), options);
}
} // namespace quiche
diff --git a/quiche/common/test_tools/mock_streams.h b/quiche/common/test_tools/mock_streams.h
index 577a324..7290490 100644
--- a/quiche/common/test_tools/mock_streams.h
+++ b/quiche/common/test_tools/mock_streams.h
@@ -14,6 +14,7 @@
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "quiche/common/platform/api/quiche_test.h"
+#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_stream.h"
namespace quiche::test {
@@ -24,22 +25,22 @@
MockWriteStream() {
ON_CALL(*this, CanWrite()).WillByDefault(testing::Return(true));
ON_CALL(*this, Writev(testing::_, testing::_))
- .WillByDefault([&](absl::Span<const absl::string_view> data,
+ .WillByDefault([&](absl::Span<quiche::QuicheMemSlice> data,
const StreamWriteOptions& options) {
return AppendToData(data, options);
});
}
MOCK_METHOD(absl::Status, Writev,
- (absl::Span<const absl::string_view> data,
+ (absl::Span<quiche::QuicheMemSlice> data,
const StreamWriteOptions& options),
(override));
MOCK_METHOD(bool, CanWrite, (), (const, override));
- absl::Status AppendToData(absl::Span<const absl::string_view> data,
+ absl::Status AppendToData(absl::Span<quiche::QuicheMemSlice> data,
const StreamWriteOptions& options) {
- for (absl::string_view fragment : data) {
- data_.append(fragment.data(), fragment.size());
+ for (const quiche::QuicheMemSlice& fragment : data) {
+ data_.append(fragment.data(), fragment.length());
}
ProcessOptions(options);
return absl::OkStatus();
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index 87420ca..ac7c036 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -116,6 +116,7 @@
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/platform/api/quiche_reference_counted.h"
#include "quiche/common/platform/api/quiche_test.h"
+#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_stream.h"
#include "quiche/common/simple_buffer_allocator.h"
#include "quiche/common/test_tools/quiche_test_utils.h"
@@ -7651,11 +7652,13 @@
ASSERT_TRUE(stream != nullptr);
// 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};
+ std::array write_vector = {quiche::QuicheMemSlice::Copy("foo"),
+ quiche::QuicheMemSlice::Copy("bar"),
+ quiche::QuicheMemSlice::Copy("test"),
+ quiche::QuicheMemSlice::Copy(kLongString)};
quiche::StreamWriteOptions options;
options.set_send_fin(true);
- QUICHE_EXPECT_OK(stream->Writev(absl::MakeConstSpan(write_vector), options));
+ QUICHE_EXPECT_OK(stream->Writev(absl::MakeSpan(write_vector), options));
std::string received_data = ReadDataFromWebTransportStreamUntilFin(stream);
EXPECT_EQ(received_data, absl::StrCat("foobartest", kLongString));
diff --git a/quiche/quic/core/http/web_transport_stream_adapter.cc b/quiche/quic/core/http/web_transport_stream_adapter.cc
index 4c26f37..fe68dfa 100644
--- a/quiche/quic/core/http/web_transport_stream_adapter.cc
+++ b/quiche/quic/core/http/web_transport_stream_adapter.cc
@@ -8,10 +8,8 @@
#include <limits>
#include <optional>
#include <string>
-#include <utility>
#include "absl/status/status.h"
-#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "quiche/quic/core/http/web_transport_http3.h"
@@ -21,15 +19,13 @@
#include "quiche/quic/core/quic_stream_priority.h"
#include "quiche/quic/core/quic_stream_sequencer.h"
#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_utils.h"
#include "quiche/quic/core/web_transport_interface.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
-#include "quiche/quic/platform/api/quic_flags.h"
#include "quiche/quic/platform/api/quic_logging.h"
#include "quiche/common/platform/api/quiche_logging.h"
-#include "quiche/common/quiche_buffer_allocator.h"
#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_stream.h"
-#include "quiche/common/vectorized_io_utils.h"
#include "quiche/web_transport/web_transport.h"
namespace quic {
@@ -69,7 +65,7 @@
}
absl::Status WebTransportStreamAdapter::Writev(
- absl::Span<const absl::string_view> data,
+ absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
if (data.empty() && !options.send_fin()) {
return absl::InvalidArgumentError(
@@ -82,21 +78,10 @@
return initial_check_status;
}
- size_t total_size = quiche::TotalStringViewSpanSize(data);
- quiche::QuicheMemSlice slice;
- if (total_size > 0) {
- quiche::QuicheBuffer buffer(
- session_->connection()->helper()->GetStreamSendBufferAllocator(),
- total_size);
- size_t bytes_copied = quiche::GatherStringViewSpan(data, buffer.AsSpan());
- QUICHE_DCHECK_EQ(total_size, bytes_copied);
- slice = quiche::QuicheMemSlice(std::move(buffer));
- }
+ size_t total_size = MemSliceSpanTotalSize(data);
QuicConsumedData consumed = stream_->WriteMemSlices(
- slice.empty() ? absl::Span<quiche::QuicheMemSlice>()
- : absl::MakeSpan(&slice, 1),
- /*fin=*/options.send_fin(),
- /*buffer_uncondtionally=*/options.buffer_unconditionally());
+ data, /*fin=*/options.send_fin(),
+ /*buffer_unconditionally=*/options.buffer_unconditionally());
if (consumed.bytes_consumed == total_size) {
return absl::OkStatus();
diff --git a/quiche/quic/core/http/web_transport_stream_adapter.h b/quiche/quic/core/http/web_transport_stream_adapter.h
index 94f3ba4..7107dd9 100644
--- a/quiche/quic/core/http/web_transport_stream_adapter.h
+++ b/quiche/quic/core/http/web_transport_stream_adapter.h
@@ -23,6 +23,7 @@
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/core/web_transport_interface.h"
#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_stream.h"
#include "quiche/web_transport/web_transport.h"
@@ -39,7 +40,7 @@
// WebTransportStream implementation.
ABSL_MUST_USE_RESULT ReadResult Read(absl::Span<char> output) override;
ABSL_MUST_USE_RESULT ReadResult Read(std::string* output) override;
- absl::Status Writev(absl::Span<const absl::string_view> data,
+ absl::Status Writev(absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) override;
bool CanWrite() const override;
void AbruptlyTerminate(absl::Status error) override;
diff --git a/quiche/quic/moqt/moqt_probe_manager.cc b/quiche/quic/moqt/moqt_probe_manager.cc
index c3745b3..c3d02ff 100644
--- a/quiche/quic/moqt/moqt_probe_manager.cc
+++ b/quiche/quic/moqt/moqt_probe_manager.cc
@@ -18,6 +18,7 @@
#include "quiche/quic/moqt/moqt_priority.h"
#include "quiche/common/platform/api/quiche_bug_tracker.h"
#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_stream.h"
#include "quiche/common/wire_serialization.h"
#include "quiche/web_transport/web_transport.h"
@@ -80,7 +81,8 @@
while (stream_->CanWrite() && data_remaining_ > 0) {
quic::QuicByteCount chunk_size = std::min(kWriteChunkSize, data_remaining_);
- absl::string_view chunk(kZeroes, chunk_size);
+ quiche::QuicheMemSlice chunk(
+ kZeroes, chunk_size, +[](absl::string_view) {});
quiche::StreamWriteOptions options;
options.set_send_fin(chunk_size == data_remaining_);
absl::Status status = stream_->Writev(absl::MakeSpan(&chunk, 1), options);
diff --git a/quiche/quic/moqt/moqt_probe_manager_test.cc b/quiche/quic/moqt/moqt_probe_manager_test.cc
index a60cfa2..9e41f9b 100644
--- a/quiche/quic/moqt/moqt_probe_manager_test.cc
+++ b/quiche/quic/moqt/moqt_probe_manager_test.cc
@@ -19,6 +19,7 @@
#include "quiche/quic/test_tools/quic_test_utils.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/platform/api/quiche_test.h"
+#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_stream.h"
#include "quiche/web_transport/test_tools/mock_web_transport.h"
#include "quiche/web_transport/web_transport.h"
@@ -47,11 +48,11 @@
MockStream(webtransport::StreamId id) : id_(id) {}
webtransport::StreamId GetStreamId() const override { return id_; }
- absl::Status Writev(absl::Span<const absl::string_view> data,
+ absl::Status Writev(absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) override {
QUICHE_CHECK(!fin_) << "FIN written twice.";
- for (absl::string_view chunk : data) {
- data_.append(chunk);
+ for (const quiche::QuicheMemSlice& slice : data) {
+ data_.append(slice.AsStringView());
}
fin_ = options.send_fin();
return absl::OkStatus();
diff --git a/quiche/quic/moqt/moqt_session.cc b/quiche/quic/moqt/moqt_session.cc
index 8f9f585..ef1450b 100644
--- a/quiche/quic/moqt/moqt_session.cc
+++ b/quiche/quic/moqt/moqt_session.cc
@@ -598,7 +598,8 @@
continue;
}
if (fetch->session_->WriteObjectToStream(
- stream_, fetch->fetch_id_, object,
+ stream_, fetch->fetch_id_, object.metadata,
+ std::move(object.payload),
MoqtDataStreamType::kStreamHeaderFetch, !stream_header_written_,
/*fin=*/false)) {
stream_header_written_ = true;
@@ -1566,7 +1567,7 @@
// down the connection if we've buffered too many control messages; otherwise,
// there is potential for memory exhaustion attacks.
options.set_buffer_unconditionally(true);
- std::array<absl::string_view, 1> write_vector = {message.AsStringView()};
+ std::array write_vector = {quiche::QuicheMemSlice(std::move(message))};
absl::Status success = stream_->Writev(absl::MakeSpan(write_vector), options);
if (!success.ok()) {
session_->Error(MoqtError::kInternalError,
@@ -2237,7 +2238,8 @@
}
if (!session_->WriteObjectToStream(
- stream_, subscription.track_alias(), *object,
+ stream_, subscription.track_alias(), object->metadata,
+ std::move(object->payload),
MoqtDataStreamType::kStreamHeaderSubgroup, !stream_header_written_,
object->fin_after_this)) {
// WriteObjectToStream() closes the connection on error, meaning that
@@ -2277,28 +2279,30 @@
}
bool MoqtSession::WriteObjectToStream(webtransport::Stream* stream, uint64_t id,
- const PublishedObject& object,
+ const PublishedObjectMetadata& metadata,
+ quiche::QuicheMemSlice payload,
MoqtDataStreamType type,
bool is_first_on_stream, bool fin) {
QUICHE_DCHECK(stream->CanWrite());
MoqtObject header;
header.track_alias = id;
- header.group_id = object.metadata.location.group;
- header.subgroup_id = object.metadata.subgroup;
- header.object_id = object.metadata.location.object;
- header.publisher_priority = object.metadata.publisher_priority;
- header.object_status = object.metadata.status;
- header.payload_length = object.payload.length();
+ header.group_id = metadata.location.group;
+ header.subgroup_id = metadata.subgroup;
+ header.object_id = metadata.location.object;
+ header.publisher_priority = metadata.publisher_priority;
+ header.object_status = metadata.status;
+ header.payload_length = payload.length();
quiche::QuicheBuffer serialized_header =
framer_.SerializeObjectHeader(header, type, is_first_on_stream);
// TODO(vasilvv): add a version of WebTransport write API that accepts
// memslices so that we can avoid a copy here.
- std::array<absl::string_view, 2> write_vector = {
- serialized_header.AsStringView(), object.payload.AsStringView()};
+ std::array write_vector = {
+ quiche::QuicheMemSlice(std::move(serialized_header)), std::move(payload)};
quiche::StreamWriteOptions options;
options.set_send_fin(fin);
- absl::Status write_status = stream->Writev(write_vector, options);
+ absl::Status write_status =
+ stream->Writev(absl::MakeSpan(write_vector), options);
if (!write_status.ok()) {
QUICHE_BUG(MoqtSession_WriteObjectToStream_write_failed)
<< "Writing into MoQT stream failed despite CanWrite() being true "
@@ -2309,7 +2313,7 @@
}
QUIC_DVLOG(1) << "Stream " << stream->GetStreamId() << " successfully wrote "
- << object.metadata.location << ", fin = " << fin;
+ << metadata.location << ", fin = " << fin;
return true;
}
diff --git a/quiche/quic/moqt/moqt_session.h b/quiche/quic/moqt/moqt_session.h
index 63cbab8..356a4eb 100644
--- a/quiche/quic/moqt/moqt_session.h
+++ b/quiche/quic/moqt/moqt_session.h
@@ -36,6 +36,7 @@
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/quiche_buffer_allocator.h"
#include "quiche/common/quiche_callbacks.h"
+#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_weak_ptr.h"
#include "quiche/web_transport/web_transport.h"
@@ -670,7 +671,8 @@
// and metadata in |object|. Not for use with datagrams. Returns |true| if
// the write was successful.
bool WriteObjectToStream(webtransport::Stream* stream, uint64_t id,
- const PublishedObject& object,
+ const PublishedObjectMetadata& metadata,
+ quiche::QuicheMemSlice payload,
MoqtDataStreamType type, bool is_first_on_stream,
bool fin);
diff --git a/quiche/quic/moqt/moqt_session_test.cc b/quiche/quic/moqt/moqt_session_test.cc
index 8131aa2..80b3520 100644
--- a/quiche/quic/moqt/moqt_session_test.cc
+++ b/quiche/quic/moqt/moqt_session_test.cc
@@ -1272,9 +1272,10 @@
bool correct_message = false;
const std::string kExpectedMessage = {0x04, 0x02, 0x05, 0x00, 0x7f};
EXPECT_CALL(mock_stream_, Writev(_, _))
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
- correct_message = absl::StartsWith(data[0], kExpectedMessage);
+ correct_message =
+ absl::StartsWith(data[0].AsStringView(), kExpectedMessage);
fin |= options.send_fin();
return absl::OkStatus();
});
@@ -1324,9 +1325,10 @@
bool correct_message = false;
const std::string kExpectedMessage = {0x04, 0x02, 0x05, 0x00, 0x7f};
EXPECT_CALL(mock_stream_, Writev(_, _))
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
- correct_message = absl::StartsWith(data[0], kExpectedMessage);
+ correct_message =
+ absl::StartsWith(data[0].AsStringView(), kExpectedMessage);
fin = options.send_fin();
return absl::OkStatus();
});
@@ -1374,9 +1376,10 @@
bool correct_message = false;
const std::string kExpectedMessage = {0x04, 0x02, 0x05, 0x00, 0x7f};
EXPECT_CALL(mock_stream_, Writev(_, _))
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
- correct_message = absl::StartsWith(data[0], kExpectedMessage);
+ correct_message =
+ absl::StartsWith(data[0].AsStringView(), kExpectedMessage);
fin |= options.send_fin();
return absl::OkStatus();
});
@@ -1427,9 +1430,10 @@
bool correct_message = false;
const std::string kExpectedMessage = {0x04, 0x02, 0x05, 0x00, 0x7f};
EXPECT_CALL(mock_stream_, Writev)
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
- correct_message = absl::StartsWith(data[0], kExpectedMessage);
+ correct_message =
+ absl::StartsWith(data[0].AsStringView(), kExpectedMessage);
fin = options.send_fin();
return absl::OkStatus();
});
@@ -1447,7 +1451,7 @@
EXPECT_FALSE(fin);
fin = false;
EXPECT_CALL(mock_stream_, Writev)
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
EXPECT_TRUE(data.empty());
fin = options.send_fin();
@@ -1486,9 +1490,10 @@
bool correct_message = false;
const std::string kExpectedMessage = {0x04, 0x02, 0x05, 0x7f, 0x00, 0x00};
EXPECT_CALL(mock_stream_, Writev(_, _))
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
- correct_message = absl::StartsWith(data[0], kExpectedMessage);
+ correct_message =
+ absl::StartsWith(data[0].AsStringView(), kExpectedMessage);
fin = options.send_fin();
return absl::OkStatus();
});
@@ -1527,9 +1532,10 @@
return std::optional<PublishedObject>();
});
EXPECT_CALL(mock_stream_, Writev(_, _))
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
- correct_message = absl::StartsWith(data[0], kExpectedMessage2);
+ correct_message =
+ absl::StartsWith(data[0].AsStringView(), kExpectedMessage2);
fin = options.send_fin();
return absl::OkStatus();
});
@@ -1568,9 +1574,10 @@
bool correct_message = false;
const std::string kExpectedMessage = {0x04, 0x02, 0x05, 0x7f, 0x00, 0x00};
EXPECT_CALL(mock_stream_, Writev(_, _))
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
- correct_message = absl::StartsWith(data[0], kExpectedMessage);
+ correct_message =
+ absl::StartsWith(data[0].AsStringView(), kExpectedMessage);
fin = options.send_fin();
return absl::OkStatus();
});
@@ -2012,24 +2019,24 @@
EXPECT_CALL(mock_stream1, CanWrite()).WillRepeatedly(Return(true));
EXPECT_CALL(mock_stream2, CanWrite()).WillRepeatedly(Return(true));
EXPECT_CALL(mock_stream0, Writev(_, _))
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
// The Group ID is the 3rd byte of the stream.
- EXPECT_EQ(static_cast<const uint8_t>(data[0][2]), 0);
+ EXPECT_EQ(static_cast<const uint8_t>(data[0].AsStringView()[2]), 0);
return absl::OkStatus();
});
EXPECT_CALL(mock_stream1, Writev(_, _))
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
// The Group ID is the 3rd byte of the stream.
- EXPECT_EQ(static_cast<const uint8_t>(data[0][2]), 1);
+ EXPECT_EQ(static_cast<const uint8_t>(data[0].AsStringView()[2]), 1);
return absl::OkStatus();
});
EXPECT_CALL(mock_stream2, Writev(_, _))
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
// The Group ID is the 3rd byte of the stream.
- EXPECT_EQ(static_cast<const uint8_t>(data[0][2]), 2);
+ EXPECT_EQ(static_cast<const uint8_t>(data[0].AsStringView()[2]), 2);
return absl::OkStatus();
});
session_.OnCanCreateNewOutgoingUnidirectionalStream();
@@ -2114,12 +2121,12 @@
EXPECT_CALL(*track1, GetCachedObject(0, 0, 1)).WillOnce(Return(std::nullopt));
EXPECT_CALL(mock_stream0, CanWrite()).WillRepeatedly(Return(true));
EXPECT_CALL(mock_stream0, Writev)
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
// Check track alias is 14.
- EXPECT_EQ(static_cast<const uint8_t>(data[0][1]), 14);
+ EXPECT_EQ(static_cast<const uint8_t>(data[0].AsStringView()[1]), 14);
// Check Group ID is 0
- EXPECT_EQ(static_cast<const uint8_t>(data[0][2]), 0);
+ EXPECT_EQ(static_cast<const uint8_t>(data[0].AsStringView()[2]), 0);
return absl::OkStatus();
});
session_.OnCanCreateNewOutgoingUnidirectionalStream();
@@ -2150,12 +2157,12 @@
EXPECT_CALL(*track2, GetCachedObject(0, 0, 1)).WillOnce(Return(std::nullopt));
EXPECT_CALL(mock_stream1, CanWrite()).WillRepeatedly(Return(true));
EXPECT_CALL(mock_stream1, Writev(_, _))
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
// Check track alias is 15.
- EXPECT_EQ(static_cast<const uint8_t>(data[0][1]), 15);
+ EXPECT_EQ(static_cast<const uint8_t>(data[0].AsStringView()[1]), 15);
// Check Group ID is 0
- EXPECT_EQ(static_cast<const uint8_t>(data[0][2]), 0);
+ EXPECT_EQ(static_cast<const uint8_t>(data[0].AsStringView()[2]), 0);
return absl::OkStatus();
});
session_.OnCanCreateNewOutgoingUnidirectionalStream();
@@ -2207,9 +2214,9 @@
Invoke([=](PublishedObject& /*output*/) { return second_result; }));
if (second_result == MoqtFetchTask::GetNextObjectResult::kEof) {
EXPECT_CALL(data_stream, Writev)
- .WillOnce(Invoke([](absl::Span<const absl::string_view> data,
+ .WillOnce(Invoke([](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
- quic::QuicDataReader reader(data[0]);
+ quic::QuicDataReader reader(data[0].AsStringView());
uint64_t type;
EXPECT_TRUE(reader.ReadVarInt62(&type));
EXPECT_EQ(type, static_cast<uint64_t>(
@@ -2217,7 +2224,7 @@
EXPECT_FALSE(options.send_fin()); // fin_after_this is ignored.
return absl::OkStatus();
}))
- .WillOnce(Invoke([](absl::Span<const absl::string_view> data,
+ .WillOnce(Invoke([](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
EXPECT_TRUE(data.empty());
EXPECT_TRUE(options.send_fin());
@@ -2226,9 +2233,9 @@
return;
}
EXPECT_CALL(data_stream, Writev)
- .WillOnce(Invoke([](absl::Span<const absl::string_view> data,
+ .WillOnce(Invoke([](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
- quic::QuicDataReader reader(data[0]);
+ quic::QuicDataReader reader(data[0].AsStringView());
uint64_t type;
EXPECT_TRUE(reader.ReadVarInt62(&type));
EXPECT_EQ(type, static_cast<uint64_t>(
@@ -3460,10 +3467,10 @@
EXPECT_CALL(mock_session_, GetStreamById(4)).WillOnce(Return(&mock_stream_));
bool correct_message = false;
EXPECT_CALL(mock_stream_, Writev(_, _))
- .WillOnce([&](absl::Span<const absl::string_view> data,
+ .WillOnce([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
correct_message = true;
- EXPECT_EQ(*ExtractMessageType(data[0]),
+ EXPECT_EQ(*ExtractMessageType(data[0].AsStringView()),
MoqtMessageType::kSubscribeDone);
return absl::OkStatus();
});
diff --git a/quiche/quic/moqt/test_tools/moqt_framer_utils.h b/quiche/quic/moqt/test_tools/moqt_framer_utils.h
index 8e704f0..b008f2e 100644
--- a/quiche/quic/moqt/test_tools/moqt_framer_utils.h
+++ b/quiche/quic/moqt/test_tools/moqt_framer_utils.h
@@ -18,6 +18,7 @@
#include "quiche/quic/moqt/moqt_messages.h"
#include "quiche/common/platform/api/quiche_test.h"
#include "quiche/common/quiche_data_reader.h"
+#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_stream.h"
namespace moqt::test {
@@ -45,13 +46,23 @@
MATCHER_P(SerializedControlMessage, message,
"Matches against a specific expected MoQT message") {
- std::string merged_message = absl::StrJoin(arg, "");
+ std::vector<absl::string_view> data_written;
+ data_written.reserve(arg.size());
+ for (const quiche::QuicheMemSlice& slice : arg) {
+ data_written.push_back(slice.AsStringView());
+ }
+ std::string merged_message = absl::StrJoin(data_written, "");
return merged_message == SerializeGenericMessage(message);
}
MATCHER_P(ControlMessageOfType, expected_type,
"Matches against an MoQT message of a specific type") {
- std::string merged_message = absl::StrJoin(arg, "");
+ std::vector<absl::string_view> data_written;
+ data_written.reserve(arg.size());
+ for (const quiche::QuicheMemSlice& slice : arg) {
+ data_written.push_back(slice.AsStringView());
+ }
+ std::string merged_message = absl::StrJoin(data_written, "");
quiche::QuicheDataReader reader(merged_message);
uint64_t type_raw;
if (!reader.ReadVarInt62(&type_raw)) {
diff --git a/quiche/web_transport/encapsulated/encapsulated_web_transport.cc b/quiche/web_transport/encapsulated/encapsulated_web_transport.cc
index 7371945..5a00803 100644
--- a/quiche/web_transport/encapsulated/encapsulated_web_transport.cc
+++ b/quiche/web_transport/encapsulated/encapsulated_web_transport.cc
@@ -34,6 +34,7 @@
#include "quiche/common/quiche_buffer_allocator.h"
#include "quiche/common/quiche_callbacks.h"
#include "quiche/common/quiche_circular_deque.h"
+#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_status_utils.h"
#include "quiche/common/quiche_stream.h"
#include "quiche/common/vectorized_io_utils.h"
@@ -257,9 +258,10 @@
// allows us to avoid a copy.
quiche::QuicheBuffer buffer =
quiche::SerializeDatagramCapsuleHeader(datagram.size(), allocator_);
- std::array spans = {buffer.AsStringView(), datagram};
+ std::array spans = {quiche::QuicheMemSlice(std::move(buffer)),
+ quiche::QuicheMemSlice::Copy(datagram)};
absl::Status write_status =
- writer_->Writev(absl::MakeConstSpan(spans), quiche::StreamWriteOptions());
+ writer_->Writev(absl::MakeSpan(spans), quiche::StreamWriteOptions());
if (!write_status.ok()) {
OnWriteError(write_status);
return DatagramStatus{
@@ -611,8 +613,15 @@
}
absl::Status EncapsulatedSession::InnerStream::Writev(
- const absl::Span<const absl::string_view> data,
+ const absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
+ // TODO: support zero copy.
+ std::vector<absl::string_view> views;
+ views.reserve(data.size());
+ for (const quiche::QuicheMemSlice& slice : data) {
+ views.push_back(slice.AsStringView());
+ }
+
if (write_side_closed_) {
return absl::FailedPreconditionError(
"Trying to write into an already-closed stream");
@@ -636,7 +645,7 @@
!pending_write_.empty();
if (write_blocked) {
fin_buffered_ = options.send_fin();
- for (absl::string_view chunk : data) {
+ for (absl::string_view chunk : views) {
absl::StrAppend(&pending_write_, chunk);
}
absl::Status status = session_->scheduler_.Schedule(id_);
@@ -648,12 +657,12 @@
return absl::OkStatus();
}
- size_t bytes_written = WriteInner(data, options.send_fin());
+ size_t bytes_written = WriteInner(views, options.send_fin());
// TODO: handle partial writes when flow control requires those.
QUICHE_DCHECK(bytes_written == 0 ||
- bytes_written == quiche::TotalStringViewSpanSize(data));
+ bytes_written == quiche::TotalStringViewSpanSize(views));
if (bytes_written == 0) {
- for (absl::string_view chunk : data) {
+ for (absl::string_view chunk : views) {
absl::StrAppend(&pending_write_, chunk);
}
}
@@ -700,12 +709,15 @@
quiche::QuicheBuffer header =
quiche::SerializeWebTransportStreamCapsuleHeader(id_, fin, total_size,
session_->allocator_);
- std::vector<absl::string_view> views_to_write;
+ std::vector<quiche::QuicheMemSlice> views_to_write;
views_to_write.reserve(data.size() + 1);
- views_to_write.push_back(header.AsStringView());
- absl::c_copy(data, std::back_inserter(views_to_write));
+ views_to_write.push_back(quiche::QuicheMemSlice(std::move(header)));
+ for (absl::string_view view : data) {
+ // TODO: support zero copy.
+ views_to_write.push_back(quiche::QuicheMemSlice::Copy(view));
+ }
absl::Status write_status = session_->writer_->Writev(
- views_to_write, quiche::kDefaultStreamWriteOptions);
+ absl::MakeSpan(views_to_write), quiche::kDefaultStreamWriteOptions);
if (!write_status.ok()) {
session_->OnWriteError(write_status);
return 0;
diff --git a/quiche/web_transport/encapsulated/encapsulated_web_transport.h b/quiche/web_transport/encapsulated/encapsulated_web_transport.h
index e4e7f7a..44f489a 100644
--- a/quiche/web_transport/encapsulated/encapsulated_web_transport.h
+++ b/quiche/web_transport/encapsulated/encapsulated_web_transport.h
@@ -25,6 +25,7 @@
#include "quiche/common/quiche_buffer_allocator.h"
#include "quiche/common/quiche_callbacks.h"
#include "quiche/common/quiche_circular_deque.h"
+#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_stream.h"
#include "quiche/common/simple_buffer_allocator.h"
#include "quiche/web_transport/web_transport.h"
@@ -145,7 +146,7 @@
bool SkipBytes(size_t bytes) override;
// WriteStream implementation.
- absl::Status Writev(absl::Span<const absl::string_view> data,
+ absl::Status Writev(absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) override;
bool CanWrite() const override;
diff --git a/quiche/web_transport/encapsulated/encapsulated_web_transport_test.cc b/quiche/web_transport/encapsulated/encapsulated_web_transport_test.cc
index 8c0c79f..6f27bc6 100644
--- a/quiche/web_transport/encapsulated/encapsulated_web_transport_test.cc
+++ b/quiche/web_transport/encapsulated/encapsulated_web_transport_test.cc
@@ -17,6 +17,7 @@
#include "quiche/common/http/http_header_block.h"
#include "quiche/common/platform/api/quiche_test.h"
#include "quiche/common/quiche_buffer_allocator.h"
+#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_stream.h"
#include "quiche/common/simple_buffer_allocator.h"
#include "quiche/common/test_tools/mock_streams.h"
@@ -46,10 +47,10 @@
ADD_FAILURE() << "Fatal session error: " << error;
});
ON_CALL(writer_, Writev(_, _))
- .WillByDefault([&](absl::Span<const absl::string_view> data,
+ .WillByDefault([&](absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) {
- for (absl::string_view fragment : data) {
- parser_.IngestCapsuleFragment(fragment);
+ for (const quiche::QuicheMemSlice& fragment : data) {
+ parser_.IngestCapsuleFragment(fragment.AsStringView());
}
writer_.ProcessOptions(options);
return absl::OkStatus();
@@ -553,7 +554,7 @@
quiche::StreamWriteOptions options;
options.set_send_fin(true);
- EXPECT_THAT(stream->Writev(absl::Span<const absl::string_view>(), options),
+ EXPECT_THAT(stream->Writev(absl::Span<quiche::QuicheMemSlice>(), options),
StatusIs(absl::StatusCode::kOk));
session->GarbageCollectStreams();
EXPECT_TRUE(deleted);
@@ -619,7 +620,7 @@
options.set_send_fin(true);
EXPECT_TRUE(stream->CanWrite());
absl::Status status =
- stream->Writev(absl::Span<const absl::string_view>(), options);
+ stream->Writev(absl::Span<quiche::QuicheMemSlice>(), options);
EXPECT_THAT(status, StatusIs(absl::StatusCode::kOk));
EXPECT_FALSE(stream->CanWrite());
}
diff --git a/quiche/web_transport/test_tools/in_memory_stream.h b/quiche/web_transport/test_tools/in_memory_stream.h
index 48da99a..078ca94 100644
--- a/quiche/web_transport/test_tools/in_memory_stream.h
+++ b/quiche/web_transport/test_tools/in_memory_stream.h
@@ -15,6 +15,7 @@
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_stream.h"
#include "quiche/web_transport/web_transport.h"
@@ -34,7 +35,7 @@
bool SkipBytes(size_t bytes) override;
// quiche::WriteStream implementation.
- absl::Status Writev(absl::Span<const absl::string_view> data,
+ absl::Status Writev(absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options) override {
QUICHE_NOTREACHED() << "Writev called on a read-only stream";
return absl::UnimplementedError("Writev called on a read-only stream");
diff --git a/quiche/web_transport/test_tools/mock_web_transport.h b/quiche/web_transport/test_tools/mock_web_transport.h
index 656c25c..77b1595 100644
--- a/quiche/web_transport/test_tools/mock_web_transport.h
+++ b/quiche/web_transport/test_tools/mock_web_transport.h
@@ -20,6 +20,7 @@
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/platform/api/quiche_test.h"
#include "quiche/common/quiche_callbacks.h"
+#include "quiche/common/quiche_mem_slice.h"
#include "quiche/common/quiche_stream.h"
#include "quiche/web_transport/web_transport.h"
@@ -40,7 +41,7 @@
MOCK_METHOD(ReadResult, Read, (absl::Span<char> buffer), (override));
MOCK_METHOD(ReadResult, Read, (std::string * output), (override));
MOCK_METHOD(absl::Status, Writev,
- (absl::Span<const absl::string_view> data,
+ (absl::Span<quiche::QuicheMemSlice> data,
const quiche::StreamWriteOptions& options),
(override));
MOCK_METHOD(PeekResult, PeekNextReadableRegion, (), (const, override));