blob: 9b7a0fb7c468237f81f7a06bbd42f0b091fa07a0 [file] [log] [blame]
// Copyright 2014 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/quic/core/quic_unacked_packet_map.h"
#include <cstddef>
#include <limits>
#include <type_traits>
#include "absl/container/inlined_vector.h"
#include "quiche/quic/core/quic_connection_stats.h"
#include "quiche/quic/core/quic_packet_number.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/core/quic_utils.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
#include "quiche/quic/platform/api/quic_flag_utils.h"
namespace quic {
namespace {
bool WillStreamFrameLengthSumWrapAround(QuicPacketLength lhs,
QuicPacketLength rhs) {
static_assert(
std::is_unsigned<QuicPacketLength>::value,
"This function assumes QuicPacketLength is an unsigned integer type.");
return std::numeric_limits<QuicPacketLength>::max() - lhs < rhs;
}
enum QuicFrameTypeBitfield : uint32_t {
kInvalidFrameBitfield = 0,
kPaddingFrameBitfield = 1,
kRstStreamFrameBitfield = 1 << 1,
kConnectionCloseFrameBitfield = 1 << 2,
kGoawayFrameBitfield = 1 << 3,
kWindowUpdateFrameBitfield = 1 << 4,
kBlockedFrameBitfield = 1 << 5,
kStopWaitingFrameBitfield = 1 << 6,
kPingFrameBitfield = 1 << 7,
kCryptoFrameBitfield = 1 << 8,
kHandshakeDoneFrameBitfield = 1 << 9,
kStreamFrameBitfield = 1 << 10,
kAckFrameBitfield = 1 << 11,
kMtuDiscoveryFrameBitfield = 1 << 12,
kNewConnectionIdFrameBitfield = 1 << 13,
kMaxStreamsFrameBitfield = 1 << 14,
kStreamsBlockedFrameBitfield = 1 << 15,
kPathResponseFrameBitfield = 1 << 16,
kPathChallengeFrameBitfield = 1 << 17,
kStopSendingFrameBitfield = 1 << 18,
kMessageFrameBitfield = 1 << 19,
kNewTokenFrameBitfield = 1 << 20,
kRetireConnectionIdFrameBitfield = 1 << 21,
kAckFrequencyFrameBitfield = 1 << 22,
};
QuicFrameTypeBitfield GetFrameTypeBitfield(QuicFrameType type) {
switch (type) {
case PADDING_FRAME:
return kPaddingFrameBitfield;
case RST_STREAM_FRAME:
return kRstStreamFrameBitfield;
case CONNECTION_CLOSE_FRAME:
return kConnectionCloseFrameBitfield;
case GOAWAY_FRAME:
return kGoawayFrameBitfield;
case WINDOW_UPDATE_FRAME:
return kWindowUpdateFrameBitfield;
case BLOCKED_FRAME:
return kBlockedFrameBitfield;
case STOP_WAITING_FRAME:
return kStopWaitingFrameBitfield;
case PING_FRAME:
return kPingFrameBitfield;
case CRYPTO_FRAME:
return kCryptoFrameBitfield;
case HANDSHAKE_DONE_FRAME:
return kHandshakeDoneFrameBitfield;
case STREAM_FRAME:
return kStreamFrameBitfield;
case ACK_FRAME:
return kAckFrameBitfield;
case MTU_DISCOVERY_FRAME:
return kMtuDiscoveryFrameBitfield;
case NEW_CONNECTION_ID_FRAME:
return kNewConnectionIdFrameBitfield;
case MAX_STREAMS_FRAME:
return kMaxStreamsFrameBitfield;
case STREAMS_BLOCKED_FRAME:
return kStreamsBlockedFrameBitfield;
case PATH_RESPONSE_FRAME:
return kPathResponseFrameBitfield;
case PATH_CHALLENGE_FRAME:
return kPathChallengeFrameBitfield;
case STOP_SENDING_FRAME:
return kStopSendingFrameBitfield;
case MESSAGE_FRAME:
return kMessageFrameBitfield;
case NEW_TOKEN_FRAME:
return kNewTokenFrameBitfield;
case RETIRE_CONNECTION_ID_FRAME:
return kRetireConnectionIdFrameBitfield;
case ACK_FREQUENCY_FRAME:
return kAckFrequencyFrameBitfield;
case NUM_FRAME_TYPES:
QUIC_BUG(quic_bug_10518_1) << "Unexpected frame type";
return kInvalidFrameBitfield;
}
QUIC_BUG(quic_bug_10518_2) << "Unexpected frame type";
return kInvalidFrameBitfield;
}
} // namespace
QuicUnackedPacketMap::QuicUnackedPacketMap(Perspective perspective)
: perspective_(perspective),
least_unacked_(FirstSendingPacketNumber()),
bytes_in_flight_(0),
bytes_in_flight_per_packet_number_space_{0, 0, 0},
packets_in_flight_(0),
last_inflight_packet_sent_time_(QuicTime::Zero()),
last_inflight_packets_sent_time_{
{QuicTime::Zero()}, {QuicTime::Zero()}, {QuicTime::Zero()}},
last_crypto_packet_sent_time_(QuicTime::Zero()),
session_notifier_(nullptr),
supports_multiple_packet_number_spaces_(false) {}
QuicUnackedPacketMap::~QuicUnackedPacketMap() {
for (QuicTransmissionInfo& transmission_info : unacked_packets_) {
DeleteFrames(&(transmission_info.retransmittable_frames));
}
}
void QuicUnackedPacketMap::AddSentPacket(SerializedPacket* mutable_packet,
TransmissionType transmission_type,
QuicTime sent_time, bool set_in_flight,
bool measure_rtt) {
const SerializedPacket& packet = *mutable_packet;
QuicPacketNumber packet_number = packet.packet_number;
QuicPacketLength bytes_sent = packet.encrypted_length;
QUIC_BUG_IF(quic_bug_12645_1, largest_sent_packet_.IsInitialized() &&
largest_sent_packet_ >= packet_number)
<< "largest_sent_packet_: " << largest_sent_packet_
<< ", packet_number: " << packet_number;
QUICHE_DCHECK_GE(packet_number, least_unacked_ + unacked_packets_.size());
while (least_unacked_ + unacked_packets_.size() < packet_number) {
unacked_packets_.push_back(QuicTransmissionInfo());
unacked_packets_.back().state = NEVER_SENT;
}
const bool has_crypto_handshake = packet.has_crypto_handshake == IS_HANDSHAKE;
QuicTransmissionInfo info(packet.encryption_level, transmission_type,
sent_time, bytes_sent, has_crypto_handshake,
packet.has_ack_frequency);
info.largest_acked = packet.largest_acked;
largest_sent_largest_acked_.UpdateMax(packet.largest_acked);
if (!measure_rtt) {
QUIC_BUG_IF(quic_bug_12645_2, set_in_flight)
<< "Packet " << mutable_packet->packet_number << ", transmission type "
<< TransmissionTypeToString(mutable_packet->transmission_type)
<< ", retransmittable frames: "
<< QuicFramesToString(mutable_packet->retransmittable_frames)
<< ", nonretransmittable_frames: "
<< QuicFramesToString(mutable_packet->nonretransmittable_frames);
info.state = NOT_CONTRIBUTING_RTT;
}
largest_sent_packet_ = packet_number;
if (set_in_flight) {
const PacketNumberSpace packet_number_space =
GetPacketNumberSpace(info.encryption_level);
bytes_in_flight_ += bytes_sent;
bytes_in_flight_per_packet_number_space_[packet_number_space] += bytes_sent;
++packets_in_flight_;
info.in_flight = true;
largest_sent_retransmittable_packets_[packet_number_space] = packet_number;
last_inflight_packet_sent_time_ = sent_time;
last_inflight_packets_sent_time_[packet_number_space] = sent_time;
}
unacked_packets_.push_back(std::move(info));
// Swap the retransmittable frames to avoid allocations.
// TODO(ianswett): Could use emplace_back when Chromium can.
if (has_crypto_handshake) {
last_crypto_packet_sent_time_ = sent_time;
}
mutable_packet->retransmittable_frames.swap(
unacked_packets_.back().retransmittable_frames);
}
void QuicUnackedPacketMap::RemoveObsoletePackets() {
while (!unacked_packets_.empty()) {
if (!IsPacketUseless(least_unacked_, unacked_packets_.front())) {
break;
}
DeleteFrames(&unacked_packets_.front().retransmittable_frames);
unacked_packets_.pop_front();
++least_unacked_;
}
}
bool QuicUnackedPacketMap::HasRetransmittableFrames(
QuicPacketNumber packet_number) const {
QUICHE_DCHECK_GE(packet_number, least_unacked_);
QUICHE_DCHECK_LT(packet_number, least_unacked_ + unacked_packets_.size());
return HasRetransmittableFrames(
unacked_packets_[packet_number - least_unacked_]);
}
bool QuicUnackedPacketMap::HasRetransmittableFrames(
const QuicTransmissionInfo& info) const {
if (!QuicUtils::IsAckable(info.state)) {
return false;
}
for (const auto& frame : info.retransmittable_frames) {
if (session_notifier_->IsFrameOutstanding(frame)) {
return true;
}
}
return false;
}
void QuicUnackedPacketMap::RemoveRetransmittability(
QuicTransmissionInfo* info) {
DeleteFrames(&info->retransmittable_frames);
info->first_sent_after_loss.Clear();
}
void QuicUnackedPacketMap::RemoveRetransmittability(
QuicPacketNumber packet_number) {
QUICHE_DCHECK_GE(packet_number, least_unacked_);
QUICHE_DCHECK_LT(packet_number, least_unacked_ + unacked_packets_.size());
QuicTransmissionInfo* info =
&unacked_packets_[packet_number - least_unacked_];
RemoveRetransmittability(info);
}
void QuicUnackedPacketMap::IncreaseLargestAcked(
QuicPacketNumber largest_acked) {
QUICHE_DCHECK(!largest_acked_.IsInitialized() ||
largest_acked_ <= largest_acked);
largest_acked_ = largest_acked;
}
void QuicUnackedPacketMap::MaybeUpdateLargestAckedOfPacketNumberSpace(
PacketNumberSpace packet_number_space, QuicPacketNumber packet_number) {
largest_acked_packets_[packet_number_space].UpdateMax(packet_number);
}
bool QuicUnackedPacketMap::IsPacketUsefulForMeasuringRtt(
QuicPacketNumber packet_number, const QuicTransmissionInfo& info) const {
// Packet can be used for RTT measurement if it may yet be acked as the
// largest observed packet by the receiver.
return QuicUtils::IsAckable(info.state) &&
(!largest_acked_.IsInitialized() || packet_number > largest_acked_) &&
info.state != NOT_CONTRIBUTING_RTT;
}
bool QuicUnackedPacketMap::IsPacketUsefulForCongestionControl(
const QuicTransmissionInfo& info) const {
// Packet contributes to congestion control if it is considered inflight.
return info.in_flight;
}
bool QuicUnackedPacketMap::IsPacketUsefulForRetransmittableData(
const QuicTransmissionInfo& info) const {
// Wait for 1 RTT before giving up on the lost packet.
return info.first_sent_after_loss.IsInitialized() &&
(!largest_acked_.IsInitialized() ||
info.first_sent_after_loss > largest_acked_);
}
bool QuicUnackedPacketMap::IsPacketUseless(
QuicPacketNumber packet_number, const QuicTransmissionInfo& info) const {
return !IsPacketUsefulForMeasuringRtt(packet_number, info) &&
!IsPacketUsefulForCongestionControl(info) &&
!IsPacketUsefulForRetransmittableData(info);
}
bool QuicUnackedPacketMap::IsUnacked(QuicPacketNumber packet_number) const {
if (packet_number < least_unacked_ ||
packet_number >= least_unacked_ + unacked_packets_.size()) {
return false;
}
return !IsPacketUseless(packet_number,
unacked_packets_[packet_number - least_unacked_]);
}
void QuicUnackedPacketMap::RemoveFromInFlight(QuicTransmissionInfo* info) {
if (info->in_flight) {
QUIC_BUG_IF(quic_bug_12645_3, bytes_in_flight_ < info->bytes_sent);
QUIC_BUG_IF(quic_bug_12645_4, packets_in_flight_ == 0);
bytes_in_flight_ -= info->bytes_sent;
--packets_in_flight_;
const PacketNumberSpace packet_number_space =
GetPacketNumberSpace(info->encryption_level);
if (bytes_in_flight_per_packet_number_space_[packet_number_space] <
info->bytes_sent) {
QUIC_BUG(quic_bug_10518_3)
<< "bytes_in_flight: "
<< bytes_in_flight_per_packet_number_space_[packet_number_space]
<< " is smaller than bytes_sent: " << info->bytes_sent
<< " for packet number space: "
<< PacketNumberSpaceToString(packet_number_space);
bytes_in_flight_per_packet_number_space_[packet_number_space] = 0;
} else {
bytes_in_flight_per_packet_number_space_[packet_number_space] -=
info->bytes_sent;
}
if (bytes_in_flight_per_packet_number_space_[packet_number_space] == 0) {
last_inflight_packets_sent_time_[packet_number_space] = QuicTime::Zero();
}
info->in_flight = false;
}
}
void QuicUnackedPacketMap::RemoveFromInFlight(QuicPacketNumber packet_number) {
QUICHE_DCHECK_GE(packet_number, least_unacked_);
QUICHE_DCHECK_LT(packet_number, least_unacked_ + unacked_packets_.size());
QuicTransmissionInfo* info =
&unacked_packets_[packet_number - least_unacked_];
RemoveFromInFlight(info);
}
absl::InlinedVector<QuicPacketNumber, 2>
QuicUnackedPacketMap::NeuterUnencryptedPackets() {
absl::InlinedVector<QuicPacketNumber, 2> neutered_packets;
QuicPacketNumber packet_number = GetLeastUnacked();
for (QuicUnackedPacketMap::iterator it = begin(); it != end();
++it, ++packet_number) {
if (!it->retransmittable_frames.empty() &&
it->encryption_level == ENCRYPTION_INITIAL) {
QUIC_DVLOG(2) << "Neutering unencrypted packet " << packet_number;
// Once the connection swithes to forward secure, no unencrypted packets
// will be sent. The data has been abandoned in the cryto stream. Remove
// it from in flight.
RemoveFromInFlight(packet_number);
it->state = NEUTERED;
neutered_packets.push_back(packet_number);
// Notify session that the data has been delivered (but do not notify
// send algorithm).
// TODO(b/148868195): use NotifyFramesNeutered.
NotifyFramesAcked(*it, QuicTime::Delta::Zero(), QuicTime::Zero());
QUICHE_DCHECK(!HasRetransmittableFrames(*it));
}
}
QUICHE_DCHECK(!supports_multiple_packet_number_spaces_ ||
last_inflight_packets_sent_time_[INITIAL_DATA] ==
QuicTime::Zero());
return neutered_packets;
}
absl::InlinedVector<QuicPacketNumber, 2>
QuicUnackedPacketMap::NeuterHandshakePackets() {
absl::InlinedVector<QuicPacketNumber, 2> neutered_packets;
QuicPacketNumber packet_number = GetLeastUnacked();
for (QuicUnackedPacketMap::iterator it = begin(); it != end();
++it, ++packet_number) {
if (!it->retransmittable_frames.empty() &&
GetPacketNumberSpace(it->encryption_level) == HANDSHAKE_DATA) {
QUIC_DVLOG(2) << "Neutering handshake packet " << packet_number;
RemoveFromInFlight(packet_number);
// Notify session that the data has been delivered (but do not notify
// send algorithm).
it->state = NEUTERED;
neutered_packets.push_back(packet_number);
// TODO(b/148868195): use NotifyFramesNeutered.
NotifyFramesAcked(*it, QuicTime::Delta::Zero(), QuicTime::Zero());
}
}
QUICHE_DCHECK(!supports_multiple_packet_number_spaces() ||
last_inflight_packets_sent_time_[HANDSHAKE_DATA] ==
QuicTime::Zero());
return neutered_packets;
}
bool QuicUnackedPacketMap::HasInFlightPackets() const {
return bytes_in_flight_ > 0;
}
const QuicTransmissionInfo& QuicUnackedPacketMap::GetTransmissionInfo(
QuicPacketNumber packet_number) const {
return unacked_packets_[packet_number - least_unacked_];
}
QuicTransmissionInfo* QuicUnackedPacketMap::GetMutableTransmissionInfo(
QuicPacketNumber packet_number) {
return &unacked_packets_[packet_number - least_unacked_];
}
QuicTime QuicUnackedPacketMap::GetLastInFlightPacketSentTime() const {
return last_inflight_packet_sent_time_;
}
QuicTime QuicUnackedPacketMap::GetLastCryptoPacketSentTime() const {
return last_crypto_packet_sent_time_;
}
size_t QuicUnackedPacketMap::GetNumUnackedPacketsDebugOnly() const {
size_t unacked_packet_count = 0;
QuicPacketNumber packet_number = least_unacked_;
for (auto it = begin(); it != end(); ++it, ++packet_number) {
if (!IsPacketUseless(packet_number, *it)) {
++unacked_packet_count;
}
}
return unacked_packet_count;
}
bool QuicUnackedPacketMap::HasMultipleInFlightPackets() const {
if (bytes_in_flight_ > kDefaultTCPMSS) {
return true;
}
size_t num_in_flight = 0;
for (auto it = rbegin(); it != rend(); ++it) {
if (it->in_flight) {
++num_in_flight;
}
if (num_in_flight > 1) {
return true;
}
}
return false;
}
bool QuicUnackedPacketMap::HasPendingCryptoPackets() const {
return session_notifier_->HasUnackedCryptoData();
}
bool QuicUnackedPacketMap::HasUnackedRetransmittableFrames() const {
for (auto it = rbegin(); it != rend(); ++it) {
if (it->in_flight && HasRetransmittableFrames(*it)) {
return true;
}
}
return false;
}
QuicPacketNumber QuicUnackedPacketMap::GetLeastUnacked() const {
return least_unacked_;
}
void QuicUnackedPacketMap::SetSessionNotifier(
SessionNotifierInterface* session_notifier) {
session_notifier_ = session_notifier;
}
bool QuicUnackedPacketMap::NotifyFramesAcked(const QuicTransmissionInfo& info,
QuicTime::Delta ack_delay,
QuicTime receive_timestamp) {
if (session_notifier_ == nullptr) {
return false;
}
bool new_data_acked = false;
for (const QuicFrame& frame : info.retransmittable_frames) {
if (session_notifier_->OnFrameAcked(frame, ack_delay, receive_timestamp)) {
new_data_acked = true;
}
}
return new_data_acked;
}
void QuicUnackedPacketMap::NotifyFramesLost(const QuicTransmissionInfo& info,
TransmissionType /*type*/) {
for (const QuicFrame& frame : info.retransmittable_frames) {
session_notifier_->OnFrameLost(frame);
}
}
bool QuicUnackedPacketMap::RetransmitFrames(const QuicFrames& frames,
TransmissionType type) {
return session_notifier_->RetransmitFrames(frames, type);
}
void QuicUnackedPacketMap::MaybeAggregateAckedStreamFrame(
const QuicTransmissionInfo& info, QuicTime::Delta ack_delay,
QuicTime receive_timestamp) {
if (session_notifier_ == nullptr) {
return;
}
for (const auto& frame : info.retransmittable_frames) {
// Determine whether acked stream frame can be aggregated.
const bool can_aggregate =
frame.type == STREAM_FRAME &&
frame.stream_frame.stream_id == aggregated_stream_frame_.stream_id &&
frame.stream_frame.offset == aggregated_stream_frame_.offset +
aggregated_stream_frame_.data_length &&
// We would like to increment aggregated_stream_frame_.data_length by
// frame.stream_frame.data_length, so we need to make sure their sum is
// representable by QuicPacketLength, which is the type of the former.
!WillStreamFrameLengthSumWrapAround(
aggregated_stream_frame_.data_length,
frame.stream_frame.data_length);
if (can_aggregate) {
// Aggregate stream frame.
aggregated_stream_frame_.data_length += frame.stream_frame.data_length;
aggregated_stream_frame_.fin = frame.stream_frame.fin;
if (aggregated_stream_frame_.fin) {
// Notify session notifier aggregated stream frame gets acked if fin is
// acked.
NotifyAggregatedStreamFrameAcked(ack_delay);
}
continue;
}
NotifyAggregatedStreamFrameAcked(ack_delay);
if (frame.type != STREAM_FRAME || frame.stream_frame.fin) {
session_notifier_->OnFrameAcked(frame, ack_delay, receive_timestamp);
continue;
}
// Delay notifying session notifier stream frame gets acked in case it can
// be aggregated with following acked ones.
aggregated_stream_frame_.stream_id = frame.stream_frame.stream_id;
aggregated_stream_frame_.offset = frame.stream_frame.offset;
aggregated_stream_frame_.data_length = frame.stream_frame.data_length;
aggregated_stream_frame_.fin = frame.stream_frame.fin;
}
}
void QuicUnackedPacketMap::NotifyAggregatedStreamFrameAcked(
QuicTime::Delta ack_delay) {
if (aggregated_stream_frame_.stream_id == static_cast<QuicStreamId>(-1) ||
session_notifier_ == nullptr) {
// Aggregated stream frame is empty.
return;
}
// Note: there is no receive_timestamp for an aggregated stream frame. The
// frames that are aggregated may not have been received at the same time.
session_notifier_->OnFrameAcked(QuicFrame(aggregated_stream_frame_),
ack_delay,
/*receive_timestamp=*/QuicTime::Zero());
// Clear aggregated stream frame.
aggregated_stream_frame_.stream_id = -1;
}
PacketNumberSpace QuicUnackedPacketMap::GetPacketNumberSpace(
QuicPacketNumber packet_number) const {
return GetPacketNumberSpace(
GetTransmissionInfo(packet_number).encryption_level);
}
PacketNumberSpace QuicUnackedPacketMap::GetPacketNumberSpace(
EncryptionLevel encryption_level) const {
if (supports_multiple_packet_number_spaces_) {
return QuicUtils::GetPacketNumberSpace(encryption_level);
}
if (perspective_ == Perspective::IS_CLIENT) {
return encryption_level == ENCRYPTION_INITIAL ? HANDSHAKE_DATA
: APPLICATION_DATA;
}
return encryption_level == ENCRYPTION_FORWARD_SECURE ? APPLICATION_DATA
: HANDSHAKE_DATA;
}
QuicPacketNumber QuicUnackedPacketMap::GetLargestAckedOfPacketNumberSpace(
PacketNumberSpace packet_number_space) const {
if (packet_number_space >= NUM_PACKET_NUMBER_SPACES) {
QUIC_BUG(quic_bug_10518_4)
<< "Invalid packet number space: " << packet_number_space;
return QuicPacketNumber();
}
return largest_acked_packets_[packet_number_space];
}
QuicTime QuicUnackedPacketMap::GetLastInFlightPacketSentTime(
PacketNumberSpace packet_number_space) const {
if (packet_number_space >= NUM_PACKET_NUMBER_SPACES) {
QUIC_BUG(quic_bug_10518_5)
<< "Invalid packet number space: " << packet_number_space;
return QuicTime::Zero();
}
return last_inflight_packets_sent_time_[packet_number_space];
}
QuicPacketNumber
QuicUnackedPacketMap::GetLargestSentRetransmittableOfPacketNumberSpace(
PacketNumberSpace packet_number_space) const {
if (packet_number_space >= NUM_PACKET_NUMBER_SPACES) {
QUIC_BUG(quic_bug_10518_6)
<< "Invalid packet number space: " << packet_number_space;
return QuicPacketNumber();
}
return largest_sent_retransmittable_packets_[packet_number_space];
}
const QuicTransmissionInfo*
QuicUnackedPacketMap::GetFirstInFlightTransmissionInfo() const {
QUICHE_DCHECK(HasInFlightPackets());
for (auto it = begin(); it != end(); ++it) {
if (it->in_flight) {
return &(*it);
}
}
QUICHE_DCHECK(false);
return nullptr;
}
const QuicTransmissionInfo*
QuicUnackedPacketMap::GetFirstInFlightTransmissionInfoOfSpace(
PacketNumberSpace packet_number_space) const {
// TODO(fayang): Optimize this part if arm 1st PTO with first in flight sent
// time works.
for (auto it = begin(); it != end(); ++it) {
if (it->in_flight &&
GetPacketNumberSpace(it->encryption_level) == packet_number_space) {
return &(*it);
}
}
return nullptr;
}
void QuicUnackedPacketMap::EnableMultiplePacketNumberSpacesSupport() {
if (supports_multiple_packet_number_spaces_) {
QUIC_BUG(quic_bug_10518_7)
<< "Multiple packet number spaces has already been enabled";
return;
}
if (largest_sent_packet_.IsInitialized()) {
QUIC_BUG(quic_bug_10518_8)
<< "Try to enable multiple packet number spaces support after any "
"packet has been sent.";
return;
}
supports_multiple_packet_number_spaces_ = true;
}
int32_t QuicUnackedPacketMap::GetLastPacketContent() const {
if (empty()) {
// Use -1 to distinguish with packets with no retransmittable frames nor
// acks.
return -1;
}
int32_t content = 0;
const QuicTransmissionInfo& last_packet = unacked_packets_.back();
for (const auto& frame : last_packet.retransmittable_frames) {
content |= GetFrameTypeBitfield(frame.type);
}
if (last_packet.largest_acked.IsInitialized()) {
content |= GetFrameTypeBitfield(ACK_FRAME);
}
return content;
}
} // namespace quic