/* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ // Based on the Quic implementation in Chromium. #ifndef MODULES_CONGESTION_CONTROLLER_BBR_PACKET_NUMBER_INDEXED_QUEUE_H_ #define MODULES_CONGESTION_CONTROLLER_BBR_PACKET_NUMBER_INDEXED_QUEUE_H_ #include #include #include #include "rtc_base/checks.h" namespace webrtc { namespace bbr { // PacketNumberIndexedQueue is a queue of mostly continuous numbered entries // which supports the following operations: // - adding elements to the end of the queue, or at some point past the end // - removing elements in any order // - retrieving elements // If all elements are inserted in order, all of the operations above are // amortized O(1) time. // // Internally, the data structure is a deque where each element is marked as // present or not. The deque starts at the lowest present index. Whenever an // element is removed, it's marked as not present, and the front of the deque is // cleared of elements that are not present. // // The tail of the queue is not cleared due to the assumption of entries being // inserted in order, though removing all elements of the queue will return it // to its initial state. // // Note that this data structure is inherently hazardous, since an addition of // just two entries will cause it to consume all of the memory available. // Because of that, it is not a general-purpose container and should not be used // as one. template class PacketNumberIndexedQueue { public: PacketNumberIndexedQueue() : number_of_present_entries_(0), first_packet_(0) {} // Retrieve the entry associated with the packet number. Returns the pointer // to the entry in case of success, or nullptr if the entry does not exist. T* GetEntry(int64_t packet_number); const T* GetEntry(int64_t packet_number) const; // Inserts data associated |packet_number| into (or past) the end of the // queue, filling up the missing intermediate entries as necessary. Returns // true if the element has been inserted successfully, false if it was already // in the queue or inserted out of order. template bool Emplace(int64_t packet_number, Args&&... args); // Removes data associated with |packet_number| and frees the slots in the // queue as necessary. bool Remove(int64_t packet_number); bool IsEmpty() const { return number_of_present_entries_ == 0; } // Returns the number of entries in the queue. size_t number_of_present_entries() const { return number_of_present_entries_; } // Returns the number of entries allocated in the underlying deque. This is // proportional to the memory usage of the queue. size_t entry_slots_used() const { return entries_.size(); } // Packet number of the first entry in the queue. Zero if the queue is empty. int64_t first_packet() const { return first_packet_; } // Packet number of the last entry ever inserted in the queue. Note that the // entry in question may have already been removed. Zero if the queue is // empty. int64_t last_packet() const { if (IsEmpty()) { return 0; } return first_packet_ + entries_.size() - 1; } private: // Wrapper around T used to mark whether the entry is actually in the map. struct EntryWrapper { T data; bool present; EntryWrapper() : data(), present(false) {} template explicit EntryWrapper(Args&&... args) : data(std::forward(args)...), present(true) {} }; // Cleans up unused slots in the front after removing an element. void Cleanup(); const EntryWrapper* GetEntryWrapper(int64_t offset) const; EntryWrapper* GetEntryWrapper(int64_t offset) { const auto* const_this = this; return const_cast(const_this->GetEntryWrapper(offset)); } std::deque entries_; size_t number_of_present_entries_; int64_t first_packet_; }; template T* PacketNumberIndexedQueue::GetEntry(int64_t packet_number) { EntryWrapper* entry = GetEntryWrapper(packet_number); if (entry == nullptr) { return nullptr; } return &entry->data; } template const T* PacketNumberIndexedQueue::GetEntry(int64_t packet_number) const { const EntryWrapper* entry = GetEntryWrapper(packet_number); if (entry == nullptr) { return nullptr; } return &entry->data; } template template bool PacketNumberIndexedQueue::Emplace(int64_t packet_number, Args&&... args) { if (IsEmpty()) { RTC_DCHECK(entries_.empty()); RTC_DCHECK_EQ(0u, first_packet_); entries_.emplace_back(std::forward(args)...); number_of_present_entries_ = 1; first_packet_ = packet_number; return true; } // Do not allow insertion out-of-order. if (packet_number <= last_packet()) { return false; } // Handle potentially missing elements. int64_t offset = packet_number - first_packet_; if (offset > static_cast(entries_.size())) { entries_.resize(offset); } number_of_present_entries_++; entries_.emplace_back(std::forward(args)...); RTC_DCHECK_EQ(packet_number, last_packet()); return true; } template bool PacketNumberIndexedQueue::Remove(int64_t packet_number) { EntryWrapper* entry = GetEntryWrapper(packet_number); if (entry == nullptr) { return false; } entry->present = false; number_of_present_entries_--; if (packet_number == first_packet()) { Cleanup(); } return true; } template void PacketNumberIndexedQueue::Cleanup() { while (!entries_.empty() && !entries_.front().present) { entries_.pop_front(); first_packet_++; } if (entries_.empty()) { first_packet_ = 0; } } template auto PacketNumberIndexedQueue::GetEntryWrapper(int64_t offset) const -> const EntryWrapper* { if (offset < first_packet_) { return nullptr; } offset -= first_packet_; if (offset >= static_cast(entries_.size())) { return nullptr; } const EntryWrapper* entry = &entries_[offset]; if (!entry->present) { return nullptr; } return entry; } } // namespace bbr } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_BBR_PACKET_NUMBER_INDEXED_QUEUE_H_