Add QpackHeaderTable::draining_index().

gfe-relnote: n/a, change to QUIC v99-only code.  Protected by existing disabled
gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 259954990
Change-Id: I896a5b039f2d61e7b4e9f97299b9ebb2234e2820
diff --git a/quic/core/qpack/qpack_header_table.cc b/quic/core/qpack/qpack_header_table.cc
index 6fa6412..92eb072 100644
--- a/quic/core/qpack/qpack_header_table.cc
+++ b/quic/core/qpack/qpack_header_table.cc
@@ -173,6 +173,31 @@
   observers_.push({observer, required_insert_count});
 }
 
+uint64_t QpackHeaderTable::draining_index(float draining_fraction) const {
+  DCHECK_LE(0.0, draining_fraction);
+  DCHECK_LE(draining_fraction, 1.0);
+
+  const uint64_t required_space = draining_fraction * dynamic_table_capacity_;
+  uint64_t space_above_draining_index =
+      dynamic_table_capacity_ - dynamic_table_size_;
+
+  if (dynamic_entries_.empty() ||
+      space_above_draining_index >= required_space) {
+    return dropped_entry_count_;
+  }
+
+  auto it = dynamic_entries_.begin();
+  while (space_above_draining_index < required_space) {
+    space_above_draining_index += it->Size();
+    ++it;
+    if (it == dynamic_entries_.end()) {
+      return inserted_entry_count();
+    }
+  }
+
+  return it->InsertionIndex();
+}
+
 bool QpackHeaderTable::ObserverWithThreshold::operator>(
     const ObserverWithThreshold& other) const {
   return required_insert_count > other.required_insert_count;
diff --git a/quic/core/qpack/qpack_header_table.h b/quic/core/qpack/qpack_header_table.h
index 39d7326..54b0829 100644
--- a/quic/core/qpack/qpack_header_table.h
+++ b/quic/core/qpack/qpack_header_table.h
@@ -99,6 +99,14 @@
   // The number of entries dropped from the dynamic table.
   uint64_t dropped_entry_count() const { return dropped_entry_count_; }
 
+  // Returns the draining index described at
+  // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#avoiding-blocked-insertions.
+  // Entries with an index larger than or equal to the draining index take up
+  // approximately |1.0 - draining_fraction| of dynamic table capacity.  The
+  // remaining capacity is taken up by draining entries and unused space.
+  // The returned index might not be the index of a valid entry.
+  uint64_t draining_index(float draining_fraction) const;
+
  private:
   // Evict entries from the dynamic table until table size is less than or equal
   // to current value of |dynamic_table_capacity_|.
diff --git a/quic/core/qpack/qpack_header_table_test.cc b/quic/core/qpack/qpack_header_table_test.cc
index ff7ef48..7d1044b 100644
--- a/quic/core/qpack/qpack_header_table_test.cc
+++ b/quic/core/qpack/qpack_header_table_test.cc
@@ -404,6 +404,42 @@
   Mock::VerifyAndClearExpectations(&observer5);
 }
 
+TEST_F(QpackHeaderTableTest, DrainingIndex) {
+  QpackHeaderTable table;
+  table.SetMaximumDynamicTableCapacity(4 * QpackEntry::Size("foo", "bar"));
+
+  // Empty table: no draining entry.
+  EXPECT_EQ(0u, table.draining_index(0.0));
+  EXPECT_EQ(0u, table.draining_index(1.0));
+
+  // Table with one entry.
+  EXPECT_TRUE(table.InsertEntry("foo", "bar"));
+  // Any entry can be referenced if none of the table is draining.
+  EXPECT_EQ(0u, table.draining_index(0.0));
+  // No entry can be referenced if all of the table is draining.
+  EXPECT_EQ(1u, table.draining_index(1.0));
+
+  // Table with two entries is at half capacity.
+  EXPECT_TRUE(table.InsertEntry("foo", "bar"));
+  // Any entry can be referenced if at most half of the table is draining,
+  // because current entries only take up half of total capacity.
+  EXPECT_EQ(0u, table.draining_index(0.0));
+  EXPECT_EQ(0u, table.draining_index(0.5));
+  // No entry can be referenced if all of the table is draining.
+  EXPECT_EQ(2u, table.draining_index(1.0));
+
+  // Table with four entries is full.
+  EXPECT_TRUE(table.InsertEntry("foo", "bar"));
+  EXPECT_TRUE(table.InsertEntry("foo", "bar"));
+  // Any entry can be referenced if none of the table is draining.
+  EXPECT_EQ(0u, table.draining_index(0.0));
+  // In a full table with identically sized entries, |draining_fraction| of all
+  // entries are draining.
+  EXPECT_EQ(2u, table.draining_index(0.5));
+  // No entry can be referenced if all of the table is draining.
+  EXPECT_EQ(4u, table.draining_index(1.0));
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic