blob: 3d81a28176dd51e03e7da7e5305975911fef450c [file] [edit]
#ifndef SRC_QUIC_NODE_QUIC_BUFFER_H_
#define SRC_QUIC_NODE_QUIC_BUFFER_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "memory_tracker-inl.h"
#include "ngtcp2/ngtcp2.h"
#include "node.h"
#include "node_bob.h"
#include "node_internals.h"
#include "util.h"
#include "uv.h"
#include <vector>
namespace node {
namespace quic {
class QuicBuffer;
constexpr size_t kMaxVectorCount = 16;
using DoneCB = std::function<void(int)>;
// When data is sent over QUIC, we are required to retain it in memory
// until we receive an acknowledgement that it has been successfully
// received. The QuicBuffer object is what we use to handle that
// and track until it is acknowledged. To understand the QuicBuffer
// object itself, it is important to understand how ngtcp2 and nghttp3
// handle data that is given to it to serialize into QUIC packets.
//
// An individual QUIC packet may contain multiple QUIC frames. Whenever
// we create a QUIC packet, we really have no idea what frames are going
// to be encoded or how much buffered handshake or stream data is going
// to be included within that QuicPacket. If there is buffered data
// available for a stream, we provide an array of pointers to that data
// and an indication about how much data is available, then we leave it
// entirely up to ngtcp2 and nghttp3 to determine how much of the data
// to encode into the QUIC packet. It is only *after* the QUIC packet
// is encoded that we can know how much was actually written.
//
// Once written to a QUIC Packet, we have to keep the data in memory
// until an acknowledgement is received. In QUIC, acknowledgements are
// received per range of packets.
//
// QuicBuffer is complicated because it needs to be able to accomplish
// three things: (a) buffering uv_buf_t instances passed down from
// JavaScript without memcpy and keeping track of the Write callback
// associated with each, (b) tracking what data has already been
// encoded in a QUIC packet and what data is remaining to be read, and
// (c) tracking which data has been acknowledged and which hasn't.
// QuicBuffer is further complicated by design quirks and limitations
// of the StreamBase API and how it interacts with the JavaScript side.
//
// QuicBuffer is a linked list of QuicBufferChunk instances.
// A single QuicBufferChunk wraps a single non-zero-length uv_buf_t.
// When the QuicBufferChunk is created, we capture the total length
// of the buffer and the total number of bytes remaining to be sent.
// Initially, these numbers are identical.
//
// When data is encoded into a QuicPacket, we advance the QuicBufferChunk's
// remaining-to-be-read by the number of bytes actually encoded. If there
// are no more bytes remaining to be encoded, we move to the next chunk
// in the linked list.
//
// When an acknowledgement is received, we decrement the QuicBufferChunk's
// length by the number of acknowledged bytes. Once the unacknowledged
// length reaches 0, we invoke the callback function associated with the
// QuicBufferChunk (if any).
//
// QuicStream is a StreamBase implementation. For every DoWrite call,
// it receives one or more uv_buf_t instances in a single batch associated
// with a single write callback. For each uv_buf_t DoWrite gets, a
// corresponding QuicBufferChunk is added to the QuicBuffer, with the
// callback associated with the final chunk added to the list.
// A QuicBufferChunk contains the actual buffered data
// along with a callback to be called when the data has
// been consumed.
//
// Any given chunk has a remaining-to-be-acknowledged length (length()) and a
// remaining-to-be-read-length (remaining()). The former tracks the number
// of bytes that have yet to be acknowledged by the QUIC peer. Once the
// remaining-to-be-acknowledged length reaches zero, the done callback
// associated with the QuicBufferChunk can be called and the QuicBufferChunk
// can be discarded. The remaining-to-be-read length is adjusted as data is
// serialized into QUIC packets and sent.
// The remaining-to-be-acknowledged length is adjusted using consume(),
// while the remaining-to-be-ead length is adjusted using seek().
class QuicBufferChunk final : public MemoryRetainer {
public:
// Default non-op done handler.
static void default_done(int status) {}
// In this variant, the QuicBufferChunk owns the underlying
// data storage within a vector. The data will be
// freed when the QuicBufferChunk is destroyed.
inline explicit QuicBufferChunk(size_t len);
// In this variant, the QuicBufferChunk only maintains a
// pointer to the underlying data buffer. The QuicBufferChunk
// does not take ownership of the buffer. The done callback
// is invoked to let the caller know when the chunk is no
// longer being used.
inline QuicBufferChunk(uv_buf_t buf_, DoneCB done_);
inline ~QuicBufferChunk() override;
// Invokes the done callback associated with the QuicBufferChunk.
inline void Done(int status);
// length() provides the remaining-to-be-acknowledged length.
// The QuicBufferChunk must be retained in memory while this
// count is greater than zero. The length is adjusted by
// calling Consume();
inline size_t length() const { return length_; }
// remaining() provides the remaining-to-be-read length number of bytes.
// The length is adjusted by calling Seek()
inline size_t remaining() const { return buf_.len; }
// Consumes (acknowledges) the given number of bytes. If amount
// is greater than length(), only length() bytes are consumed.
// Returns the actual number of bytes consumed.
inline size_t Consume(size_t amount);
// Seeks (reads) the given number of bytes. If amount is greater
// than remaining(), only remaining() bytes are read. Returns
// the actual number of bytes read.
inline size_t Seek(size_t amount);
uint8_t* out() { return reinterpret_cast<uint8_t*>(buf_.base); }
uv_buf_t buf() { return buf_; }
const uv_buf_t buf() const { return buf_; }
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(QuicBufferChunk)
SET_SELF_SIZE(QuicBufferChunk)
private:
std::vector<uint8_t> data_buf_;
uv_buf_t buf_;
DoneCB done_ = default_done;
size_t length_ = 0;
bool done_called_ = false;
std::unique_ptr<QuicBufferChunk> next_;
friend class QuicBuffer;
};
class QuicBuffer final : public bob::SourceImpl<ngtcp2_vec>,
public MemoryRetainer {
public:
QuicBuffer() = default;
inline QuicBuffer(QuicBuffer&& src) noexcept;
inline QuicBuffer& operator=(QuicBuffer&& src) noexcept;
~QuicBuffer() override {
Cancel(); // Cancel the remaining data
CHECK_EQ(length_, 0);
}
// Marks the QuicBuffer as having ended, preventing new QuicBufferChunk
// instances from being appended to the linked list and allowing the
// Pull operation to know when to signal that the flow of data is
// completed.
void End() { ended_ = true; }
bool is_ended() const { return ended_; }
// Push one or more uv_buf_t instances into the buffer.
// the DoneCB callback will be invoked when the last
// uv_buf_t in the bufs array is consumed and popped out
// of the internal linked list. Ownership of the uv_buf_t
// remains with the caller.
size_t Push(
uv_buf_t* bufs,
size_t nbufs,
DoneCB done = QuicBufferChunk::default_done);
// Pushes a single QuicBufferChunk into the linked list
void Push(std::unique_ptr<QuicBufferChunk> chunk);
// Consume the given number of bytes within the buffer. If amount
// is greater than length(), length() bytes are consumed. Returns
// the actual number of bytes consumed.
inline size_t Consume(size_t amount);
// Cancels the remaining bytes within the buffer.
inline size_t Cancel(int status = UV_ECANCELED);
// Seeks (reads) the given number of bytes. If amount is greater
// than remaining(), seeks remaining() bytes. Returns the actual
// number of bytes read.
size_t Seek(size_t amount);
// The total number of unacknowledged bytes remaining.
size_t length() const { return length_; }
// The total number of unread bytes remaining.
size_t remaining() const { return remaining_; }
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(QuicBuffer);
SET_SELF_SIZE(QuicBuffer);
protected:
int DoPull(
bob::Next<ngtcp2_vec> next,
int options,
ngtcp2_vec* data,
size_t count,
size_t max_count_hint) override;
private:
inline static bool is_empty(uv_buf_t buf);
size_t Consume(int status, size_t amount);
bool Pop(int status = 0);
inline void Push(uv_buf_t buf, DoneCB done = nullptr);
std::unique_ptr<QuicBufferChunk> root_;
QuicBufferChunk* head_ = nullptr; // Current Read Position
QuicBufferChunk* tail_ = nullptr; // Current Write Position
bool canceled_ = false;
bool ended_ = false;
size_t length_ = 0;
size_t remaining_ = 0;
friend class QuicBufferChunk;
};
} // namespace quic
} // namespace node
#endif // NODE_WANT_INTERNALS
#endif // SRC_QUIC_NODE_QUIC_BUFFER_H_