Project import generated by Copybara.
PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/core/quic_buffered_packet_store.cc b/quic/core/quic_buffered_packet_store.cc
new file mode 100644
index 0000000..68ebac9
--- /dev/null
+++ b/quic/core/quic_buffered_packet_store.cc
@@ -0,0 +1,236 @@
+// Copyright (c) 2016 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 "net/third_party/quiche/src/quic/core/quic_buffered_packet_store.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_map_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+namespace quic {
+
+typedef QuicBufferedPacketStore::BufferedPacket BufferedPacket;
+typedef QuicBufferedPacketStore::BufferedPacketList BufferedPacketList;
+typedef QuicBufferedPacketStore::EnqueuePacketResult EnqueuePacketResult;
+
+// Max number of connections this store can keep track.
+static const size_t kDefaultMaxConnectionsInStore = 100;
+// Up to half of the capacity can be used for storing non-CHLO packets.
+static const size_t kMaxConnectionsWithoutCHLO =
+ kDefaultMaxConnectionsInStore / 2;
+
+namespace {
+
+// This alarm removes expired entries in map each time this alarm fires.
+class ConnectionExpireAlarm : public QuicAlarm::Delegate {
+ public:
+ explicit ConnectionExpireAlarm(QuicBufferedPacketStore* store)
+ : connection_store_(store) {}
+
+ void OnAlarm() override { connection_store_->OnExpirationTimeout(); }
+
+ ConnectionExpireAlarm(const ConnectionExpireAlarm&) = delete;
+ ConnectionExpireAlarm& operator=(const ConnectionExpireAlarm&) = delete;
+
+ private:
+ QuicBufferedPacketStore* connection_store_;
+};
+
+} // namespace
+
+BufferedPacket::BufferedPacket(std::unique_ptr<QuicReceivedPacket> packet,
+ QuicSocketAddress self_address,
+ QuicSocketAddress peer_address)
+ : packet(std::move(packet)),
+ self_address(self_address),
+ peer_address(peer_address) {}
+
+BufferedPacket::BufferedPacket(BufferedPacket&& other) = default;
+
+BufferedPacket& BufferedPacket::operator=(BufferedPacket&& other) = default;
+
+BufferedPacket::~BufferedPacket() {}
+
+BufferedPacketList::BufferedPacketList()
+ : creation_time(QuicTime::Zero()),
+ ietf_quic(false),
+ version(PROTOCOL_UNSUPPORTED, QUIC_VERSION_UNSUPPORTED) {}
+
+BufferedPacketList::BufferedPacketList(BufferedPacketList&& other) = default;
+
+BufferedPacketList& BufferedPacketList::operator=(BufferedPacketList&& other) =
+ default;
+
+BufferedPacketList::~BufferedPacketList() {}
+
+QuicBufferedPacketStore::QuicBufferedPacketStore(
+ VisitorInterface* visitor,
+ const QuicClock* clock,
+ QuicAlarmFactory* alarm_factory)
+ : connection_life_span_(
+ QuicTime::Delta::FromSeconds(kInitialIdleTimeoutSecs)),
+ visitor_(visitor),
+ clock_(clock),
+ expiration_alarm_(
+ alarm_factory->CreateAlarm(new ConnectionExpireAlarm(this))) {}
+
+QuicBufferedPacketStore::~QuicBufferedPacketStore() {}
+
+EnqueuePacketResult QuicBufferedPacketStore::EnqueuePacket(
+ QuicConnectionId connection_id,
+ bool ietf_quic,
+ const QuicReceivedPacket& packet,
+ QuicSocketAddress self_address,
+ QuicSocketAddress peer_address,
+ bool is_chlo,
+ const QuicString& alpn,
+ const ParsedQuicVersion& version) {
+ QUIC_BUG_IF(!FLAGS_quic_allow_chlo_buffering)
+ << "Shouldn't buffer packets if disabled via flag.";
+ QUIC_BUG_IF(is_chlo && QuicContainsKey(connections_with_chlo_, connection_id))
+ << "Shouldn't buffer duplicated CHLO on connection " << connection_id;
+ QUIC_BUG_IF(!is_chlo && !alpn.empty())
+ << "Shouldn't have an ALPN defined for a non-CHLO packet.";
+ QUIC_BUG_IF(is_chlo && version.transport_version == QUIC_VERSION_UNSUPPORTED)
+ << "Should have version for CHLO packet.";
+
+ if (!QuicContainsKey(undecryptable_packets_, connection_id) &&
+ ShouldBufferPacket(is_chlo)) {
+ // Drop the packet if the upper limit of undecryptable packets has been
+ // reached or the whole capacity of the store has been reached.
+ return TOO_MANY_CONNECTIONS;
+ } else if (!QuicContainsKey(undecryptable_packets_, connection_id)) {
+ undecryptable_packets_.emplace(
+ std::make_pair(connection_id, BufferedPacketList()));
+ undecryptable_packets_.back().second.ietf_quic = ietf_quic;
+ undecryptable_packets_.back().second.version = version;
+ }
+ CHECK(QuicContainsKey(undecryptable_packets_, connection_id));
+ BufferedPacketList& queue =
+ undecryptable_packets_.find(connection_id)->second;
+
+ if (!is_chlo) {
+ // If current packet is not CHLO, it might not be buffered because store
+ // only buffers certain number of undecryptable packets per connection.
+ size_t num_non_chlo_packets =
+ QuicContainsKey(connections_with_chlo_, connection_id)
+ ? (queue.buffered_packets.size() - 1)
+ : queue.buffered_packets.size();
+ if (num_non_chlo_packets >= kDefaultMaxUndecryptablePackets) {
+ // If there are kMaxBufferedPacketsPerConnection packets buffered up for
+ // this connection, drop the current packet.
+ return TOO_MANY_PACKETS;
+ }
+ }
+
+ if (queue.buffered_packets.empty()) {
+ // If this is the first packet arrived on a new connection, initialize the
+ // creation time.
+ queue.creation_time = clock_->ApproximateNow();
+ }
+
+ BufferedPacket new_entry(std::unique_ptr<QuicReceivedPacket>(packet.Clone()),
+ self_address, peer_address);
+ if (is_chlo) {
+ // Add CHLO to the beginning of buffered packets so that it can be delivered
+ // first later.
+ queue.buffered_packets.push_front(std::move(new_entry));
+ queue.alpn = alpn;
+ connections_with_chlo_[connection_id] = false; // Dummy value.
+ // Set the version of buffered packets of this connection on CHLO.
+ queue.version = version;
+ } else {
+ // Buffer non-CHLO packets in arrival order.
+ queue.buffered_packets.push_back(std::move(new_entry));
+ }
+ MaybeSetExpirationAlarm();
+ return SUCCESS;
+}
+
+bool QuicBufferedPacketStore::HasBufferedPackets(
+ QuicConnectionId connection_id) const {
+ return QuicContainsKey(undecryptable_packets_, connection_id);
+}
+
+bool QuicBufferedPacketStore::HasChlosBuffered() const {
+ return !connections_with_chlo_.empty();
+}
+
+BufferedPacketList QuicBufferedPacketStore::DeliverPackets(
+ QuicConnectionId connection_id) {
+ BufferedPacketList packets_to_deliver;
+ auto it = undecryptable_packets_.find(connection_id);
+ if (it != undecryptable_packets_.end()) {
+ packets_to_deliver = std::move(it->second);
+ undecryptable_packets_.erase(connection_id);
+ }
+ return packets_to_deliver;
+}
+
+void QuicBufferedPacketStore::DiscardPackets(QuicConnectionId connection_id) {
+ undecryptable_packets_.erase(connection_id);
+ connections_with_chlo_.erase(connection_id);
+}
+
+void QuicBufferedPacketStore::OnExpirationTimeout() {
+ QuicTime expiration_time = clock_->ApproximateNow() - connection_life_span_;
+ while (!undecryptable_packets_.empty()) {
+ auto& entry = undecryptable_packets_.front();
+ if (entry.second.creation_time > expiration_time) {
+ break;
+ }
+ QuicConnectionId connection_id = entry.first;
+ visitor_->OnExpiredPackets(connection_id, std::move(entry.second));
+ undecryptable_packets_.pop_front();
+ connections_with_chlo_.erase(connection_id);
+ }
+ if (!undecryptable_packets_.empty()) {
+ MaybeSetExpirationAlarm();
+ }
+}
+
+void QuicBufferedPacketStore::MaybeSetExpirationAlarm() {
+ if (!expiration_alarm_->IsSet()) {
+ expiration_alarm_->Set(clock_->ApproximateNow() + connection_life_span_);
+ }
+}
+
+bool QuicBufferedPacketStore::ShouldBufferPacket(bool is_chlo) {
+ bool is_store_full =
+ undecryptable_packets_.size() >= kDefaultMaxConnectionsInStore;
+
+ if (is_chlo) {
+ return is_store_full;
+ }
+
+ size_t num_connections_without_chlo =
+ undecryptable_packets_.size() - connections_with_chlo_.size();
+ bool reach_non_chlo_limit =
+ num_connections_without_chlo >= kMaxConnectionsWithoutCHLO;
+
+ return is_store_full || reach_non_chlo_limit;
+}
+
+BufferedPacketList QuicBufferedPacketStore::DeliverPacketsForNextConnection(
+ QuicConnectionId* connection_id) {
+ if (connections_with_chlo_.empty()) {
+ // Returns empty list if no CHLO has been buffered.
+ return BufferedPacketList();
+ }
+ *connection_id = connections_with_chlo_.front().first;
+ connections_with_chlo_.pop_front();
+
+ BufferedPacketList packets = DeliverPackets(*connection_id);
+ DCHECK(!packets.buffered_packets.empty())
+ << "Try to deliver connectons without CHLO";
+ return packets;
+}
+
+bool QuicBufferedPacketStore::HasChloForConnection(
+ QuicConnectionId connection_id) {
+ return QuicContainsKey(connections_with_chlo_, connection_id);
+}
+
+} // namespace quic