Add functions to interop between absl::Cord and QuicheMemSlice.

PiperOrigin-RevId: 911445172
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 3c8fcbe..b2b27f6 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -44,6 +44,7 @@
     "common/quiche_buffer_allocator.h",
     "common/quiche_callbacks.h",
     "common/quiche_circular_deque.h",
+    "common/quiche_cord_utils.h",
     "common/quiche_crypto_logging.h",
     "common/quiche_data_reader.h",
     "common/quiche_data_writer.h",
@@ -432,6 +433,7 @@
     "common/moq_varint.cc",
     "common/platform/api/quiche_hostname_utils.cc",
     "common/quiche_buffer_allocator.cc",
+    "common/quiche_cord_utils.cc",
     "common/quiche_crypto_logging.cc",
     "common/quiche_data_reader.cc",
     "common/quiche_data_writer.cc",
@@ -1143,6 +1145,7 @@
     "common/quiche_buffer_allocator_test.cc",
     "common/quiche_callbacks_test.cc",
     "common/quiche_circular_deque_test.cc",
+    "common/quiche_cord_utils_test.cc",
     "common/quiche_data_reader_test.cc",
     "common/quiche_data_writer_fuzz_test.cc",
     "common/quiche_data_writer_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index b926f30..c68b800 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -44,6 +44,7 @@
     "src/quiche/common/quiche_buffer_allocator.h",
     "src/quiche/common/quiche_callbacks.h",
     "src/quiche/common/quiche_circular_deque.h",
+    "src/quiche/common/quiche_cord_utils.h",
     "src/quiche/common/quiche_crypto_logging.h",
     "src/quiche/common/quiche_data_reader.h",
     "src/quiche/common/quiche_data_writer.h",
@@ -432,6 +433,7 @@
     "src/quiche/common/moq_varint.cc",
     "src/quiche/common/platform/api/quiche_hostname_utils.cc",
     "src/quiche/common/quiche_buffer_allocator.cc",
+    "src/quiche/common/quiche_cord_utils.cc",
     "src/quiche/common/quiche_crypto_logging.cc",
     "src/quiche/common/quiche_data_reader.cc",
     "src/quiche/common/quiche_data_writer.cc",
@@ -1144,6 +1146,7 @@
     "src/quiche/common/quiche_buffer_allocator_test.cc",
     "src/quiche/common/quiche_callbacks_test.cc",
     "src/quiche/common/quiche_circular_deque_test.cc",
+    "src/quiche/common/quiche_cord_utils_test.cc",
     "src/quiche/common/quiche_data_reader_test.cc",
     "src/quiche/common/quiche_data_writer_fuzz_test.cc",
     "src/quiche/common/quiche_data_writer_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 6f39024..215e90a 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -43,6 +43,7 @@
     "quiche/common/quiche_buffer_allocator.h",
     "quiche/common/quiche_callbacks.h",
     "quiche/common/quiche_circular_deque.h",
+    "quiche/common/quiche_cord_utils.h",
     "quiche/common/quiche_crypto_logging.h",
     "quiche/common/quiche_data_reader.h",
     "quiche/common/quiche_data_writer.h",
@@ -431,6 +432,7 @@
     "quiche/common/moq_varint.cc",
     "quiche/common/platform/api/quiche_hostname_utils.cc",
     "quiche/common/quiche_buffer_allocator.cc",
+    "quiche/common/quiche_cord_utils.cc",
     "quiche/common/quiche_crypto_logging.cc",
     "quiche/common/quiche_data_reader.cc",
     "quiche/common/quiche_data_writer.cc",
@@ -1143,6 +1145,7 @@
     "quiche/common/quiche_buffer_allocator_test.cc",
     "quiche/common/quiche_callbacks_test.cc",
     "quiche/common/quiche_circular_deque_test.cc",
+    "quiche/common/quiche_cord_utils_test.cc",
     "quiche/common/quiche_data_reader_test.cc",
     "quiche/common/quiche_data_writer_fuzz_test.cc",
     "quiche/common/quiche_data_writer_test.cc",
diff --git a/quiche/common/quiche_cord_utils.cc b/quiche/common/quiche_cord_utils.cc
new file mode 100644
index 0000000..51d9403
--- /dev/null
+++ b/quiche/common/quiche_cord_utils.cc
@@ -0,0 +1,79 @@
+// Copyright 2026 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.
+
+#include "quiche/common/quiche_cord_utils.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <utility>
+
+#include "absl/strings/cord.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/quiche_callbacks.h"
+#include "quiche/common/quiche_mem_slice.h"
+
+namespace quiche {
+
+namespace {
+// Explicitly stated in the absl::Cord documentation.
+constexpr size_t kMaxInlinedCordSize = 15;
+}  // namespace
+
+absl::Cord MemSliceToCord(QuicheMemSlice slice) {
+  if (slice.empty()) {
+    return absl::Cord();
+  }
+  QuicheMemSlice::ReleasedSlice released_slice = std::move(slice).Release();
+  if (released_slice.callback == nullptr) {
+    released_slice.callback = [](absl::string_view) {};
+  }
+  return absl::MakeCordFromExternal(released_slice.data,
+                                    std::move(released_slice.callback));
+}
+
+absl::Cord MemSliceSpanToCord(absl::Span<QuicheMemSlice> slices) {
+  absl::Cord cord;
+  for (QuicheMemSlice& slice : slices) {
+    cord.Append(MemSliceToCord(std::move(slice)));
+  }
+  return cord;
+}
+
+void CordToMemSlices(const absl::Cord& cord,
+                     UnretainedCallback<void(QuicheMemSlice)> sink) {
+  size_t current_offset = 0;
+  for (absl::string_view chunk : cord.Chunks()) {
+    // absl::Cord does not provide any API to access individual chunks or to
+    // extract the release callback from a chunk. To side-step this issue,
+    // allocate an instance of absl::Cord on the heap, and delete it from the
+    // QuicheMemSlice release callback.  The heap allocation is necessary since
+    // absl::Cord supports small string inlining, meaning the resulting
+    // absl::string_view is not guaranteed to point to a stable heap-allocated
+    // address otherwise.
+    auto subcord = std::make_unique<absl::Cord>(
+        cord.Subcord(current_offset, chunk.size()));
+    QUICHE_DCHECK(subcord->TryFlat().has_value());
+    // Despite the QUICHE_DCHECK above, the production code below still uses
+    // `Flatten()` as a fail-safe.
+    absl::string_view stored_chunk = subcord->Flatten();
+
+    QUICHE_DCHECK_EQ(stored_chunk, chunk);
+    if (chunk.size() > kMaxInlinedCordSize) {
+      // absl::Cord has a documented inlining threshold; ensure that if it is
+      // exceeded, the data address does not change, since the goal of this API
+      // is to avoid copies.
+      QUICHE_DCHECK_EQ(reinterpret_cast<uintptr_t>(stored_chunk.data()),
+                       reinterpret_cast<uintptr_t>(chunk.data()));
+    }
+    sink(QuicheMemSlice(
+        stored_chunk.data(), stored_chunk.size(),
+        [ptr = subcord.release()](absl::string_view) { delete ptr; }));
+    current_offset += chunk.size();
+  }
+}
+
+}  // namespace quiche
diff --git a/quiche/common/quiche_cord_utils.h b/quiche/common/quiche_cord_utils.h
new file mode 100644
index 0000000..87ed18c
--- /dev/null
+++ b/quiche/common/quiche_cord_utils.h
@@ -0,0 +1,44 @@
+// Copyright 2026 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.
+
+#ifndef QUICHE_COMMON_QUICHE_CORD_UTILS_H_
+#define QUICHE_COMMON_QUICHE_CORD_UTILS_H_
+
+#include <utility>
+
+#include "absl/strings/cord.h"
+#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 {
+
+// Converts an instance of QuicheMemSlice into an instance of absl::Cord that
+// owns the underlying MemSlice.
+absl::Cord QUICHE_EXPORT MemSliceToCord(QuicheMemSlice slice);
+// Converts a span of MemSlices into a single absl::Cord instance.  All of the
+// slices in `slices` are moved into the Cord, and are no longer valid.
+absl::Cord QUICHE_EXPORT MemSliceSpanToCord(absl::Span<QuicheMemSlice> slices);
+
+// Converts an absl::Cord into a sequence of QuicheMemSlice objects without
+// copying any of the data in the Cord.  Calls `sink` for every QuicheMemSlice
+// object generated.
+void QUICHE_EXPORT CordToMemSlices(
+    const absl::Cord& cord, UnretainedCallback<void(QuicheMemSlice)> sink);
+
+// Converts an absl::Cord into a sequence of QuicheMemSlice objects, and appends
+// them to the provided container.  The container has to provide a `push_back`
+// method.
+template <typename Container>
+void QUICHE_NO_EXPORT CordToMemSlicesTo(const absl::Cord& cord,
+                                        Container& container) {
+  CordToMemSlices(cord, [&](QuicheMemSlice slice) {
+    container.push_back(std::move(slice));
+  });
+}
+
+}  // namespace quiche
+
+#endif  // QUICHE_COMMON_QUICHE_CORD_UTILS_H_
diff --git a/quiche/common/quiche_cord_utils_test.cc b/quiche/common/quiche_cord_utils_test.cc
new file mode 100644
index 0000000..120afb4
--- /dev/null
+++ b/quiche/common/quiche_cord_utils_test.cc
@@ -0,0 +1,148 @@
+// Copyright 2026 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.
+
+#include "quiche/common/quiche_cord_utils.h"
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/strings/cord.h"
+#include "absl/strings/str_cat.h"
+#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"
+
+namespace quiche::test {
+namespace {
+
+TEST(QuicheCordUtilsTest, MemSliceToCord) {
+  QuicheMemSlice slice1 = QuicheMemSlice::Copy("foo");
+  QuicheMemSlice slice2 = QuicheMemSlice::Copy("bar");
+  absl::Cord cord = MemSliceToCord(std::move(slice1));
+  cord.Append(MemSliceToCord(std::move(slice2)));
+  EXPECT_EQ(cord, "foobar");
+}
+
+TEST(QuicheCordUtilsTest, MemSliceToCordEmpty) {
+  absl::Cord cord = MemSliceToCord(QuicheMemSlice());
+  EXPECT_TRUE(cord.empty());
+}
+
+TEST(QuicheCordUtilsTest, MemSliceToCordNullDeleter) {
+  absl::string_view kText = "test";
+  absl::Cord cord =
+      MemSliceToCord(QuicheMemSlice(kText.data(), kText.size(), nullptr));
+  EXPECT_EQ(cord, kText);
+}
+
+TEST(QuicheCordUtilsTest, MemSliceSpanToCord) {
+  std::array<QuicheMemSlice, 2> slices = {QuicheMemSlice::Copy("foo"),
+                                          QuicheMemSlice::Copy("bar")};
+  absl::Cord cord = MemSliceSpanToCord(absl::MakeSpan(slices));
+  EXPECT_EQ(cord, "foobar");
+}
+
+TEST(QuicheCordUtilsTest, CordToMemSlicesInlined) {
+  absl::Cord cord("test");
+  std::vector<QuicheMemSlice> slices;
+  CordToMemSlicesTo(cord, slices);
+  ASSERT_EQ(slices.size(), 1);
+  EXPECT_EQ(slices[0].AsStringView(), "test");
+}
+
+TEST(QuicheCordUtilsTest, CordToMemSlicesNotInlined) {
+  constexpr size_t kSize = 8192;
+  auto buffer = std::make_unique<char[]>(kSize);
+  uintptr_t original_address = reinterpret_cast<uintptr_t>(buffer.get());
+  memset(buffer.get(), 'a', kSize);
+  absl::Cord cord = absl::MakeCordFromExternal(
+      absl::string_view(buffer.release(), kSize),
+      [](absl::string_view data) { delete[] data.data(); });
+  std::vector<QuicheMemSlice> slices;
+  CordToMemSlicesTo(cord, slices);
+  cord.Clear();
+
+  ASSERT_EQ(slices.size(), 1);
+  EXPECT_EQ(slices[0].length(), kSize);
+  EXPECT_EQ(reinterpret_cast<uintptr_t>(slices[0].data()), original_address);
+  EXPECT_EQ(slices[0].AsStringView(), std::string(kSize, 'a'));
+}
+
+TEST(QuicheCordUtilsTest, CordToMemSlicesLarge) {
+  const std::string kBlock(1024, 'a');
+  constexpr size_t kBlockCount = 128;
+  absl::Cord cord;
+  for (size_t i = 0; i < kBlockCount; ++i) {
+    cord.Append(kBlock);
+  }
+
+  std::vector<QuicheMemSlice> slices;
+  CordToMemSlicesTo(cord, slices);
+  cord.Clear();
+
+  ASSERT_GT(slices.size(), 1);
+  size_t total_size = 0;
+  for (const QuicheMemSlice& slice : slices) {
+    total_size += slice.length();
+  }
+  EXPECT_EQ(total_size, kBlock.size() * kBlockCount);
+  for (const QuicheMemSlice& slice : slices) {
+    ASSERT_THAT(slice.AsStringView(), testing::Each('a'));
+  }
+}
+
+TEST(QuicheCordUtilsTest, CordWithMixedTypes) {
+  absl::Cord cord("bar");
+  cord.Prepend("foo");
+
+  auto block_before = std::make_unique<std::string>(8192, 'a');
+  absl::string_view block_before_view(*block_before);
+  cord.Prepend(absl::MakeCordFromExternal(
+      block_before_view,
+      [ptr = block_before.release()](absl::string_view) { delete ptr; }));
+
+  auto block_after = std::make_unique<std::string>(8192, 'b');
+  absl::string_view block_after_view(*block_after);
+  cord.Append(absl::MakeCordFromExternal(
+      block_after_view,
+      [ptr = block_after.release()](absl::string_view) { delete ptr; }));
+
+  std::vector<QuicheMemSlice> slices;
+  CordToMemSlicesTo(cord, slices);
+  cord.Clear();
+
+  ASSERT_GT(slices.size(), 1);
+  std::string concatenated;
+  for (const QuicheMemSlice& slice : slices) {
+    absl::StrAppend(&concatenated, slice.AsStringView());
+  }
+  EXPECT_EQ(concatenated, absl::StrCat(std::string(8192, 'a'), "foobar",
+                                       std::string(8192, 'b')));
+}
+
+TEST(QuicheCordUtilsTest, Subcord) {
+  constexpr size_t kCount = 100;
+  absl::Cord cord;
+  for (size_t i = 0; i < kCount; ++i) {
+    cord.Append("foobar");
+  }
+  absl::Cord subcord = cord.Subcord(99, 6);
+  std::vector<QuicheMemSlice> slices;
+  CordToMemSlicesTo(subcord, slices);
+  ASSERT_EQ(slices.size(), 1);
+  EXPECT_EQ(slices[0].AsStringView(), "barfoo");  // 99 = 16 * 6 + 3
+  cord.Clear();
+  subcord.Clear();
+  EXPECT_EQ(slices[0].AsStringView(), "barfoo");
+}
+
+}  // namespace
+}  // namespace quiche::test
diff --git a/quiche/common/quiche_mem_slice.cc b/quiche/common/quiche_mem_slice.cc
index 84d1214..7979d87 100644
--- a/quiche/common/quiche_mem_slice.cc
+++ b/quiche/common/quiche_mem_slice.cc
@@ -64,6 +64,15 @@
   done_callback_ = nullptr;
 }
 
+QuicheMemSlice::ReleasedSlice QuicheMemSlice::Release() && {
+  ReleasedSlice slice{.data = AsStringView(),
+                      .callback = std::move(done_callback_)};
+  data_ = nullptr;
+  size_ = 0;
+  done_callback_ = nullptr;
+  return slice;
+}
+
 QuicheMemSlice QuicheMemSlice::Copy(absl::string_view data) {
   if (data.empty()) {
     return QuicheMemSlice();
diff --git a/quiche/common/quiche_mem_slice.h b/quiche/common/quiche_mem_slice.h
index cf2d173..4fb5035 100644
--- a/quiche/common/quiche_mem_slice.h
+++ b/quiche/common/quiche_mem_slice.h
@@ -19,6 +19,11 @@
 class QUICHE_EXPORT QuicheMemSlice {
  public:
   using ReleaseCallback = SingleUseCallback<void(absl::string_view)>;
+  // QuicheMemSlice released into the underlying components.
+  struct ReleasedSlice {
+    absl::string_view data;
+    ReleaseCallback callback;  // Can be nullptr.
+  };
 
   // Creates a QuicheMemSlice by allocating memory on heap and copying the
   // specified bytes.
@@ -54,6 +59,11 @@
   // undefined behavior.
   void Reset();
 
+  // Releases the underlying deleter callback, and clears the state of the
+  // object.  Returns both the callback and the view of the data owned by the
+  // slice.
+  ReleasedSlice Release() &&;
+
   // Returns a const char pointer to underlying data buffer.
   const char* data() const { return data_; }
   // Returns the length of underlying data buffer.
diff --git a/quiche/common/quiche_mem_slice_test.cc b/quiche/common/quiche_mem_slice_test.cc
index e891195..5abe46d 100644
--- a/quiche/common/quiche_mem_slice_test.cc
+++ b/quiche/common/quiche_mem_slice_test.cc
@@ -140,6 +140,26 @@
   EXPECT_EQ(slice.length(), 0u);
 }
 
+TEST_F(QuicheMemSliceTest, Release) {
+  constexpr absl::string_view kData = "test";
+  bool deleted = false;
+  QuicheMemSlice slice(kData.data(), kData.size(), [&](absl::string_view data) {
+    EXPECT_EQ(kData, data);
+    deleted = true;
+  });
+  EXPECT_FALSE(slice.empty());
+  QuicheMemSlice::ReleasedSlice released = std::move(slice).Release();
+  EXPECT_TRUE(slice.empty());  // NOLINT(bugprone-use-after-move)
+  EXPECT_EQ(released.data, kData);
+  EXPECT_FALSE(deleted);
+  std::move(released.callback)(kData);
+  EXPECT_TRUE(deleted);
+
+  released = std::move(slice).Release();  // NOLINT(bugprone-use-after-move)
+  EXPECT_TRUE(released.data.empty());
+  EXPECT_EQ(released.callback, nullptr);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quiche