Commit 851841c0 authored by jan.koester's avatar jan.koester
Browse files

deb



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

  * QUIC: fix FIN-only STREAM frame never sent on the wire — when
    sendStreamData(sid, nullptr, 0, true) was called after prior data
    sends, the main send loop did not execute (send_len == 0) and no
    packet carrying the FIN bit was ever emitted.  The local stream
    state was marked send_fin=true but the peer never received the FIN,
    causing the remote side to wait indefinitely for stream completion.
    Fix: emit an empty STREAM frame with FIN+OFF when send_len == 0.
  * test: add RFC 9000 QUIC transport compliance test suite
    (quic_rfc9000_test) — 43 tests covering connection state machine,
    version negotiation, connection IDs, cipher selection, bidirectional
    and unidirectional stream types, FIN handling (inline, separate,
    empty), data integrity (1 B to 1 MB echo), connection-level and
    stream-level flow control, concurrent streams, rapid stream
    creation (MAX_STREAMS), and idle survival

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

libnetplus (20260505+9) unstable; urgency=medium

  * QUIC: fix connection-level flow control double-counting — _data_recv
+22 −27
Original line number Diff line number Diff line
@@ -4724,33 +4724,6 @@ size_t quic::sendData(buffer& data, int flags) {
        sent_total += chunk;
    }
    
    // FIN-only: send an empty STREAM frame with FIN when there's no data
    // (e.g. sendStreamData(sid, nullptr, 0, true) after prior data sends).
    // The main loop doesn't execute when send_len == 0, so handle it here.
    if (send_len == 0 && fin && !stream.send_fin) {
        // Build STREAM frame: type=0x09 (FIN + no LEN), OFF bit if offset > 0
        std::vector<uint8_t> stream_frame;
        uint8_t frame_type = 0x08 | 0x01; // FIN bit
        if (stream.send_offset > 0) frame_type |= 0x04; // OFF bit
        stream_frame.push_back(frame_type);
        uint8_t vbuf[8];
        size_t vlen = encodeVarInt(stream_id, vbuf);
        stream_frame.insert(stream_frame.end(), vbuf, vbuf + vlen);
        if (stream.send_offset > 0) {
            vlen = encodeVarInt(stream.send_offset, vbuf);
            stream_frame.insert(stream_frame.end(), vbuf, vbuf + vlen);
        }
        // No data, no LEN — just the FIN flag

        uint64_t pn_before = _app_pn_send;
        std::vector<uint8_t> packet = buildShortHeaderPacket(stream_frame);
        const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
        applyHeaderProtection(packet, hp_key);
        sendPacket(packet.data(), packet.size());
        recordSentPacket(pn_before, stream_id, stream.send_offset, nullptr, 0, true);
        stream.send_fin = true;
    }
    
    return sent_total;
}

@@ -5026,6 +4999,28 @@ size_t quic::sendStreamData(uint64_t stream_id, const uint8_t* data, size_t len,
    // Only mark FIN if we actually sent all requested data (FIN is on the last chunk).
    // If CC broke the loop early, the FIN was never put on the wire.
    if (fin && sent_total >= send_len) {
        // FIN-only: when send_len == 0, the main loop never ran, so we must
        // send an empty STREAM frame with the FIN bit on the wire.
        if (send_len == 0 && !stream.send_fin) {
            uint8_t frame_type = 0x08 | 0x01; // STREAM + FIN
            if (stream.send_offset > 0) frame_type |= 0x04; // OFF bit
            std::vector<uint8_t> fin_frame;
            fin_frame.push_back(frame_type);
            uint8_t vbuf[8];
            size_t vlen = encodeVarInt(stream_id, vbuf);
            fin_frame.insert(fin_frame.end(), vbuf, vbuf + vlen);
            if (stream.send_offset > 0) {
                vlen = encodeVarInt(stream.send_offset, vbuf);
                fin_frame.insert(fin_frame.end(), vbuf, vbuf + vlen);
            }

            uint64_t pn = _app_pn_send;
            std::vector<uint8_t> packet = buildShortHeaderPacket(fin_frame);
            const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
            applyHeaderProtection(packet, hp_key);
            sendPacket(packet.data(), packet.size());
            recordSentPacket(pn, stream_id, stream.send_offset, nullptr, 0, true);
        }
        stream.send_fin = true;
        // Auto-cleanup: both sides FIN'd → erase stream & replenish peer's stream budget
        if (stream.recv_fin) {