Commit c8f4d1c7 authored by jan.koester's avatar jan.koester
Browse files

deb



Co-authored-by: default avatarCopilot <copilot@github.com>
parent f779fc90
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
libnetplus (20260505+9) unstable; urgency=medium

  * QUIC: fix connection-level flow control double-counting — _data_recv
    was incremented by the full STREAM frame length on every receive,
    including retransmissions and overlapping data.  Over many streams
    (e.g. during long cluster imports) this inflated the counter and
    could trigger a spurious FLOW_CONTROL_ERROR that killed the
    connection mid-transfer.  Fix: compute overlap with existing
    recv_ranges to determine new unique bytes; only count those for
    _data_recv and the connection-level limit check.
  * QUIC: fix sendStreamData(vector) silent data loss on congestion
    control stall — after 50 stalls the function silently broke out of
    the send loop, losing remaining data with no error to the caller.
    Fix: throw NetException so callers (e.g. paritypp store_stripe)
    know the stream write failed and can retry.
  * QUIC: fix unidirectional stream dispatch before data complete —
    uni-stream callbacks fired on recv_fin (FIN frame arrival) instead
    of recv_complete (all data contiguous from offset 0).  If packets
    arrived out of order or needed retransmission, the callback received
    a buffer with zero-filled gaps.  Fix: check recv_complete (matching
    the bidi path) and std::move the buffer to avoid copy + memory waste.

 -- Jan Koester <jan.koester@tuxist.de>  Mon, 05 May 2026 12:00:00 +0200

libnetplus (20260504+8) unstable; urgency=medium

  * QUIC: fix PTO re-queue spin loop — when sendPacket() failed (EAGAIN),
+36 −9
Original line number Diff line number Diff line
@@ -3357,6 +3357,20 @@ void quic::processStreamFrame(const uint8_t* data, size_t len, size_t& offset) {
        }
    }
    
    // Compute new unique bytes (exclude retransmitted/overlapping data)
    // so that connection-level flow control only counts each byte once.
    uint64_t new_unique_bytes = 0;
    if (stream_len > 0) {
        uint64_t end_offset = stream_offset + stream_len;
        uint64_t overlap = 0;
        for (const auto& r : stream.recv_ranges) {
            if (r.first >= end_offset) break;
            if (r.second <= stream_offset) continue;
            overlap += std::min(r.second, end_offset) - std::max(r.first, stream_offset);
        }
        new_unique_bytes = stream_len - overlap;
    }
    
    // Enforce receive-side flow control: peer must not exceed our advertised limits
    if (stream_len > 0) {
        uint64_t end_offset = stream_offset + stream_len;
@@ -3374,8 +3388,8 @@ void quic::processStreamFrame(const uint8_t* data, size_t len, size_t& offset) {
            return;
        }
        
        // Connection-level check
        if (_data_recv + stream_len > _max_data_local_committed) {
        // Connection-level check (only count new unique bytes)
        if (_data_recv + new_unique_bytes > _max_data_local_committed) {
            // FLOW_CONTROL_ERROR (0x03): peer exceeded connection flow control limit
            std::vector<uint8_t> close_frame = buildConnectionCloseFrame(0x03, "connection flow control exceeded");
            std::vector<uint8_t> packet = buildShortHeaderPacket(close_frame);
@@ -3463,8 +3477,8 @@ void quic::processStreamFrame(const uint8_t* data, size_t len, size_t& offset) {
    
    offset += stream_len;
    
    // Update connection-level receive counter
    _data_recv += stream_len;
    // Update connection-level receive counter (only new unique bytes)
    _data_recv += new_unique_bytes;
    
    // Flow control: defer MAX_STREAM_DATA when >50% of window consumed
    uint64_t consumed = stream.recv_buffer.size();
@@ -3532,13 +3546,19 @@ void quic::processStreamFrame(const uint8_t* data, size_t len, size_t& offset) {
            stream.recv_buffer.clear();
            stream.recv_buffer.shrink_to_fit();
            stream.recv_ranges.clear();
        } else if (!is_bidi_request && stream.recv_fin && !stream.recv_dispatched) {
            // Unidirectional stream with FIN — defer via _pending_dispatches
            // to avoid deadlock (callback may call sendStreamData which locks _quic_mutex)
        } else if (!is_bidi_request && stream.recv_complete && !stream.recv_dispatched) {
            // Unidirectional stream fully received — defer via _pending_dispatches
            // to avoid deadlock (callback may call sendStreamData which locks _quic_mutex).
            // Must check recv_complete (not just recv_fin) to ensure all data is
            // contiguous before dispatching — otherwise gaps from lost/reordered
            // packets would deliver a buffer with zero-filled holes.
            stream.recv_dispatched = true;
            _pending_dispatches.push_back({stream_id,
                                           std::vector<uint8_t>(stream.recv_buffer),
                                           std::move(stream.recv_buffer),
                                           true});
            stream.recv_buffer.clear();
            stream.recv_buffer.shrink_to_fit();
            stream.recv_ranges.clear();
        }
    }
}
@@ -3852,7 +3872,14 @@ void quic::sendStreamData(uint64_t stream_id, const std::vector<uint8_t>& data,
    while (remaining > 0) {
        size_t sent = sendStreamData(stream_id, data.data() + offset, remaining, fin);
        if (sent == 0) {
            if (++stall_count > 50) break; // give up after ~5s of total CC stall
            if (++stall_count > 50) {
                // Congestion control stall exceeded — data was not fully sent.
                // Throw so the caller knows the stream is incomplete.
                NetException exception;
                exception[NetException::Error] << "sendStreamData: congestion stall, "
                    << remaining << " of " << data.size() << " bytes unsent on stream " << stream_id;
                throw exception;
            }
            // Pump incoming to process ACKs and open congestion window
            pumpIncoming();
            continue;