/*******************************************************************************
 * Copyright (c) 2026, Jan Koester jan.koester@gmx.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

#include "socket.h"
#include "exception.h"
#include "random.h"
#include "crypto/aes.h"
#include "crypto/sha.h"
#include "crypto/curve25519.h"
#include "crypto/rsa.h"
#include "crypto/rsa_pss_sha256.h"
#include "crypto/rsa_pss_sha384.h"

#include <algorithm>
#include <thread>
#include <chrono>
#include <cstdio>
#include <cmath>

#define QUIC_DBG(fmt, ...) do {} while(0)

namespace netplus {

// ============================================================================
// Constructors & Destructor
// ============================================================================

quic::quic() : udp() {
    _Socket = -1;
    _Type = sockettype::QUIC;  // QUIC runs over UDP but has its own type
    _conn_state.store(ConnectionState::Idle);
    _is_server = false;
    _handshake_complete = false;
    _version = QUIC_VERSION_1;
    _last_activity = std::chrono::steady_clock::now();
    
    // Generate local connection ID
    generateConnectionId(_local_cid, 8);
}

quic::quic(SOCKET sock) : udp(sock) {
    _Type = sockettype::QUIC;
    _conn_state.store(ConnectionState::Idle);
    _is_server = false;
    _handshake_complete = false;
    _version = QUIC_VERSION_1;
    _last_activity = std::chrono::steady_clock::now();
    
    // Generate local connection ID
    generateConnectionId(_local_cid, 8);
}

quic::quic(const std::string& addr, int port, int maxconnections, int sockopts)
    : udp(addr, port, maxconnections, sockopts) {
    _Type = sockettype::QUIC;
    _is_server = true;
    _conn_state.store(ConnectionState::Idle);
    _handshake_complete = false;
    _version = QUIC_VERSION_1;
    _last_activity = std::chrono::steady_clock::now();
    
    // Enlarge the receive buffer so high-throughput clients don't
    // overrun the kernel queue between epoll_wait cycles.  4 MB keeps
    // up with ~3000 in-flight 1200-byte QUIC datagrams.

    int rcvbuf = 4 * 1024 * 1024;
    int sndbuf = 4 * 1024 * 1024;

    #ifdef _WIN32
    const char* rcvopt = reinterpret_cast<const char*>(&rcvbuf);
    const char* sndopt = reinterpret_cast<const char*>(&sndbuf);
    #else
    int* rcvopt = &rcvbuf;
    int* sndopt = &sndbuf;
    #endif
        
    ::setsockopt(_Socket, SOL_SOCKET, SO_RCVBUF, rcvopt, sizeof(rcvbuf));
    
    ::setsockopt(_Socket, SOL_SOCKET, SO_SNDBUF, sndopt, sizeof(sndbuf));
    
    // Generate local connection ID for server
    generateConnectionId(_local_cid, 8);
}

quic::quic(const std::map<std::string, ssl::CertificateBundle>& certs,
           const std::string& addr, int port, int maxconnections, int sockopts)
    : udp(addr, port, maxconnections, sockopts) {
    _Type = sockettype::QUIC;
    _is_server = true;
    _conn_state.store(ConnectionState::Idle);
    _handshake_complete = false;
    _version = QUIC_VERSION_1;
    _cert_map = certs;
    _last_activity = std::chrono::steady_clock::now();
    
    // Enlarge the receive/send buffers so high-throughput clients don't
    // overrun the kernel queue between epoll_wait cycles.  4 MB keeps
    // up with ~3000 in-flight 1200-byte QUIC datagrams.
    int rcvbuf = 4 * 1024 * 1024;
    int sndbuf = 4 * 1024 * 1024;

#ifdef _WIN32
    const char* rcvopt = reinterpret_cast<const char*>(&rcvbuf);
    const char* sndopt = reinterpret_cast<const char*>(&sndbuf);
#else
    int* rcvopt = &rcvbuf;
    int* sndopt = &sndbuf;
#endif

    ::setsockopt(_Socket, SOL_SOCKET, SO_RCVBUF, rcvopt, sizeof(rcvbuf));
    
    ::setsockopt(_Socket, SOL_SOCKET, SO_SNDBUF, sndopt, sizeof(sndbuf));
    
    // Generate local connection ID for server
    generateConnectionId(_local_cid, 8);
}

quic::~quic() {
    // quic_mtx() returns parent's mutex for children, our own for the parent.
    // recursive_mutex allows re-entry when parent already holds the lock
    // (e.g. during _child_owned_connections cleanup).
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    
    if (_parent != nullptr) {
        _parent->_child_connections.erase(_original_dcid);
        _parent->_child_connections.erase(_local_cid);
    }
    
    // Clean up streams
    _streams.clear();
    
    // Clear send queue
    _send_queue.clear();
    
    // Clear crypto buffers
    _crypto_recv_initial.clear();
    _crypto_recv_handshake.clear();
    _crypto_recv_app.clear();
    _crypto_send_initial.clear();
    _crypto_send_handshake.clear();
    _crypto_ranges_initial.clear();
    _crypto_ranges_handshake.clear();
    _crypto_ranges_app.clear();
    
    // Clear TLS state
    _tls_transcript.clear();
    
    // Zero out keys for security
    std::memset(_initial_key_client, 0, sizeof(_initial_key_client));
    std::memset(_initial_iv_client, 0, sizeof(_initial_iv_client));
    std::memset(_initial_hp_client, 0, sizeof(_initial_hp_client));
    std::memset(_initial_key_server, 0, sizeof(_initial_key_server));
    std::memset(_initial_iv_server, 0, sizeof(_initial_iv_server));
    std::memset(_initial_hp_server, 0, sizeof(_initial_hp_server));
    
    std::memset(_hs_key_client, 0, sizeof(_hs_key_client));
    std::memset(_hs_iv_client, 0, sizeof(_hs_iv_client));
    std::memset(_hs_hp_client, 0, sizeof(_hs_hp_client));
    std::memset(_hs_key_server, 0, sizeof(_hs_key_server));
    std::memset(_hs_iv_server, 0, sizeof(_hs_iv_server));
    std::memset(_hs_hp_server, 0, sizeof(_hs_hp_server));
    
    std::memset(_app_key_client, 0, sizeof(_app_key_client));
    std::memset(_app_iv_client, 0, sizeof(_app_iv_client));
    std::memset(_app_hp_client, 0, sizeof(_app_hp_client));
    std::memset(_app_key_server, 0, sizeof(_app_key_server));
    std::memset(_app_iv_server, 0, sizeof(_app_iv_server));
    std::memset(_app_hp_server, 0, sizeof(_app_hp_server));
    
    _conn_state.store(ConnectionState::Closed);
}

// ============================================================================
// Helper: Generate Connection ID
// ============================================================================

void quic::generateConnectionId(std::vector<uint8_t>& cid, size_t len) {
    cid.resize(len);
    fillRandomBytes(cid.data(), len);
}

// ============================================================================
// Helper: Send packet to peer (uses udp class methods)
// Retries on EAGAIN up to 5 times with 1ms back-off to avoid losing
// packets when the kernel TX buffer is temporarily full (e.g. after
// sending the 1200-byte ServerHello, the Handshake flight may EAGAIN).
// ============================================================================

ssize_t quic::sendPacket(const uint8_t* data, size_t len) {
    if (_Socket < 0) {
        return -1;
    }

    for (int attempt = 0; attempt < 5; ++attempt) {
        buffer send_buf(reinterpret_cast<const char*>(data), len);

        try {
            size_t sent;
            if (_parent != nullptr) {
                sent = udp::sendTo(send_buf, MSG_DONTWAIT);
            } else {
                sent = udp::sendData(send_buf, MSG_DONTWAIT);
            }

            if (sent > 0) {
                _last_activity = std::chrono::steady_clock::now();
            }
            return static_cast<ssize_t>(sent);
        } catch (NetException& e) {
            // EAGAIN/EWOULDBLOCK — retry after brief pause
            if (e.getErrorType() == NetException::Note && attempt < 4) {
                std::this_thread::sleep_for(std::chrono::milliseconds(1));
                continue;
            }
            return -1;
        }
    }
    return -1;
}

// ============================================================================
// Variable-length Integer Encoding/Decoding (RFC 9000 Section 16)
// ============================================================================

size_t quic::encodeVarInt(uint64_t value, uint8_t* out) {
    if (value <= 63) {
        out[0] = static_cast<uint8_t>(value);
        return 1;
    } else if (value <= 16383) {
        out[0] = static_cast<uint8_t>(0x40 | (value >> 8));
        out[1] = static_cast<uint8_t>(value & 0xFF);
        return 2;
    } else if (value <= 1073741823) {
        out[0] = static_cast<uint8_t>(0x80 | (value >> 24));
        out[1] = static_cast<uint8_t>((value >> 16) & 0xFF);
        out[2] = static_cast<uint8_t>((value >> 8) & 0xFF);
        out[3] = static_cast<uint8_t>(value & 0xFF);
        return 4;
    } else {
        out[0] = static_cast<uint8_t>(0xC0 | (value >> 56));
        out[1] = static_cast<uint8_t>((value >> 48) & 0xFF);
        out[2] = static_cast<uint8_t>((value >> 40) & 0xFF);
        out[3] = static_cast<uint8_t>((value >> 32) & 0xFF);
        out[4] = static_cast<uint8_t>((value >> 24) & 0xFF);
        out[5] = static_cast<uint8_t>((value >> 16) & 0xFF);
        out[6] = static_cast<uint8_t>((value >> 8) & 0xFF);
        out[7] = static_cast<uint8_t>(value & 0xFF);
        return 8;
    }
}

uint64_t quic::decodeVarInt(const uint8_t* data, size_t& bytes_read) {
    uint8_t prefix = data[0] >> 6;
    uint64_t value = data[0] & 0x3F;
    
    switch (prefix) {
        case 0:
            bytes_read = 1;
            return value;
        case 1:
            bytes_read = 2;
            return (value << 8) | data[1];
        case 2:
            bytes_read = 4;
            return (value << 24) | (static_cast<uint64_t>(data[1]) << 16) |
                   (static_cast<uint64_t>(data[2]) << 8) | data[3];
        case 3:
            bytes_read = 8;
            return (value << 56) | (static_cast<uint64_t>(data[1]) << 48) |
                   (static_cast<uint64_t>(data[2]) << 40) | (static_cast<uint64_t>(data[3]) << 32) |
                   (static_cast<uint64_t>(data[4]) << 24) | (static_cast<uint64_t>(data[5]) << 16) |
                   (static_cast<uint64_t>(data[6]) << 8) | data[7];
        default:
            bytes_read = 1;
            return 0;
    }
}

size_t quic::varIntSize(uint64_t value) {
    if (value <= 63) return 1;
    if (value <= 16383) return 2;
    if (value <= 1073741823) return 4;
    return 8;
}

// ============================================================================
// Packet Number Decoding (RFC 9000 Section 17.1)
// ============================================================================

uint64_t quic::decodePacketNumber(uint32_t truncated_pn, size_t pn_len, uint64_t largest_pn) {
    uint64_t expected_pn = largest_pn + 1;
    uint64_t pn_win = 1ULL << (pn_len * 8);
    uint64_t pn_hwin = pn_win / 2;
    uint64_t pn_mask = pn_win - 1;
    
    // The candidate packet number
    uint64_t candidate_pn = (expected_pn & ~pn_mask) | truncated_pn;
    
    // Be careful with underflow - check if expected_pn >= pn_hwin before subtracting
    if (expected_pn >= pn_hwin && 
        candidate_pn <= expected_pn - pn_hwin && 
        candidate_pn < (1ULL << 62) - pn_win) {
        return candidate_pn + pn_win;
    }
    if (candidate_pn > expected_pn + pn_hwin && candidate_pn >= pn_win) {
        return candidate_pn - pn_win;
    }
    return candidate_pn;
}

// ============================================================================
// Retry Token Validation
// ============================================================================

bool quic::validateRetryToken(const std::vector<uint8_t>& token) {
    // Basic validation - token should not be empty and have reasonable size
    if (token.empty() || token.size() > 256) {
        return false;
    }
    
    // In a full implementation, this would verify the token's integrity tag
    // and check that it hasn't expired. For now, accept non-empty tokens.
    // TODO: Implement proper AEAD-based token validation
    return true;
}

// ============================================================================
// Certificate Configuration
// ============================================================================

void quic::setCertificates(const std::map<std::string, ssl::CertificateBundle>& certs) {
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    _cert_map = certs;
}

// ============================================================================
// Crypto Key Derivation - Initial Keys (RFC 9001 Section 5.2)
// ============================================================================

void quic::deriveInitialKeys(const std::vector<uint8_t>& dcid, bool is_server) {
    // QUIC v1 initial salt (RFC 9001)
    static const uint8_t QUIC_V1_INITIAL_SALT[] = {
        0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3,
        0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad,
        0xcc, 0xbb, 0x7f, 0x0a
    };
    
    // Use TLS class HKDF methods with AES-128-GCM-SHA256 (0x1301)
    std::vector<uint8_t> salt(QUIC_V1_INITIAL_SALT, 
                               QUIC_V1_INITIAL_SALT + sizeof(QUIC_V1_INITIAL_SALT));
    std::vector<uint8_t> initial_secret = netplus::tls::hkdf_extract(salt, dcid, 0x1301);
    
    // Derive client initial secret using TLS HKDF expand
    std::vector<uint8_t> client_initial_secret = netplus::tls::hkdf_expand_label(
        initial_secret, "client in", {}, 32, 0x1301);
    
    // Derive server initial secret
    std::vector<uint8_t> server_initial_secret = netplus::tls::hkdf_expand_label(
        initial_secret, "server in", {}, 32, 0x1301);
    
    // Derive client keys using QUIC-specific labels
    std::vector<uint8_t> client_key = netplus::tls::hkdf_expand_label(
        client_initial_secret, "quic key", {}, 16, 0x1301);
    std::vector<uint8_t> client_iv = netplus::tls::hkdf_expand_label(
        client_initial_secret, "quic iv", {}, 12, 0x1301);
    std::vector<uint8_t> client_hp = netplus::tls::hkdf_expand_label(
        client_initial_secret, "quic hp", {}, 16, 0x1301);
    
    // Derive server keys
    std::vector<uint8_t> server_key = netplus::tls::hkdf_expand_label(
        server_initial_secret, "quic key", {}, 16, 0x1301);
    std::vector<uint8_t> server_iv = netplus::tls::hkdf_expand_label(
        server_initial_secret, "quic iv", {}, 12, 0x1301);
    std::vector<uint8_t> server_hp = netplus::tls::hkdf_expand_label(
        server_initial_secret, "quic hp", {}, 16, 0x1301);
    
    // Copy to member arrays
    std::memcpy(_initial_key_client, client_key.data(), 16);
    std::memcpy(_initial_iv_client, client_iv.data(), 12);
    std::memcpy(_initial_hp_client, client_hp.data(), 16);
    
    std::memcpy(_initial_key_server, server_key.data(), 16);
    std::memcpy(_initial_iv_server, server_iv.data(), 12);
    std::memcpy(_initial_hp_server, server_hp.data(), 16);
    
    // Create AES instances for initial encryption level
    _aes_initial_send = std::make_unique<aes128>(
        is_server ? std::vector<uint8_t>(server_key.begin(), server_key.end())
                  : std::vector<uint8_t>(client_key.begin(), client_key.end()));
    _aes_initial_recv = std::make_unique<aes128>(
        is_server ? std::vector<uint8_t>(client_key.begin(), client_key.end())
                  : std::vector<uint8_t>(server_key.begin(), server_key.end()));
    
    // Cache AES instances for header protection (ECB mode, reused across packets)
    _aes_hp_initial_send = std::make_unique<aes128>(
        is_server ? std::vector<uint8_t>(server_hp.begin(), server_hp.end())
                  : std::vector<uint8_t>(client_hp.begin(), client_hp.end()));
    _aes_hp_initial_recv = std::make_unique<aes128>(
        is_server ? std::vector<uint8_t>(client_hp.begin(), client_hp.end())
                  : std::vector<uint8_t>(server_hp.begin(), server_hp.end()));
    
}

// ============================================================================
// Crypto Key Derivation - Handshake Keys
// ============================================================================

void quic::deriveHandshakeKeys(const std::vector<uint8_t>& shared_secret) {
    
    // Determine key/hash sizes based on selected cipher
    const bool use_sha384 = (_selected_cipher == 0x1302);  // TLS_AES_256_GCM_SHA384
    const size_t hash_len = use_sha384 ? 48 : 32;
    const size_t key_len = use_sha384 ? 32 : 16;  // AES-256 uses 32-byte keys
    const size_t hp_len = use_sha384 ? 32 : 16;   // HP key length matches AEAD key size
    const uint16_t cipher_suite = use_sha384 ? 0x1302 : 0x1301;
    
    
    // Compute transcript hash using appropriate hash function
    std::vector<uint8_t> transcript_hash = use_sha384 
        ? sha384_hash(_tls_transcript)
        : sha256_hash(_tls_transcript);
    
    // Compute empty hash
    std::vector<uint8_t> empty_hash = use_sha384
        ? sha384_hash({})
        : sha256_hash({});
    
    // Use TLS class HKDF methods for key derivation
    std::vector<uint8_t> zero_key(hash_len, 0);
    std::vector<uint8_t> early_secret = netplus::tls::hkdf_extract({}, zero_key, cipher_suite);
    std::vector<uint8_t> derived_secret = netplus::tls::hkdf_expand_label(
        early_secret, "derived", empty_hash, hash_len, cipher_suite);
    std::vector<uint8_t> handshake_secret = netplus::tls::hkdf_extract(
        derived_secret, shared_secret, cipher_suite);
    std::vector<uint8_t> c_hs_traffic = netplus::tls::hkdf_expand_label(
        handshake_secret, "c hs traffic", transcript_hash, hash_len, cipher_suite);
    std::vector<uint8_t> s_hs_traffic = netplus::tls::hkdf_expand_label(
        handshake_secret, "s hs traffic", transcript_hash, hash_len, cipher_suite);
    
    // Derive QUIC-specific keys from traffic secrets
    std::vector<uint8_t> client_key = netplus::tls::hkdf_expand_label(
        c_hs_traffic, "quic key", {}, key_len, cipher_suite);
    std::vector<uint8_t> client_iv = netplus::tls::hkdf_expand_label(
        c_hs_traffic, "quic iv", {}, 12, cipher_suite);
    std::vector<uint8_t> client_hp = netplus::tls::hkdf_expand_label(
        c_hs_traffic, "quic hp", {}, hp_len, cipher_suite);
    std::vector<uint8_t> server_key = netplus::tls::hkdf_expand_label(
        s_hs_traffic, "quic key", {}, key_len, cipher_suite);
    std::vector<uint8_t> server_iv = netplus::tls::hkdf_expand_label(
        s_hs_traffic, "quic iv", {}, 12, cipher_suite);
    std::vector<uint8_t> server_hp = netplus::tls::hkdf_expand_label(
        s_hs_traffic, "quic hp", {}, hp_len, cipher_suite);
    
    for (size_t i = 0; i < std::min(_tls_transcript.size(), (size_t)100); i++) {
    }
    
    // Store traffic secrets for Finished key derivation
    _c_hs_traffic_secret = c_hs_traffic;
    _s_hs_traffic_secret = s_hs_traffic;
    
    // Copy to member arrays
    std::memcpy(_hs_key_client, client_key.data(), key_len);
    std::memcpy(_hs_iv_client, client_iv.data(), 12);
    std::memset(_hs_hp_client, 0, sizeof(_hs_hp_client));
    std::memcpy(_hs_hp_client, client_hp.data(), hp_len);
    
    std::memcpy(_hs_key_server, server_key.data(), key_len);
    std::memcpy(_hs_iv_server, server_iv.data(), 12);
    std::memset(_hs_hp_server, 0, sizeof(_hs_hp_server));
    std::memcpy(_hs_hp_server, server_hp.data(), hp_len);
    
    // Create AES instances for handshake encryption level
    if (use_sha384) {
        _aes_hs_send = std::make_unique<aes256>(
            _is_server ? server_key : client_key);
        _aes_hs_recv = std::make_unique<aes256>(
            _is_server ? client_key : server_key);
        _aes_hp_hs_send = std::make_unique<aes256>(
            _is_server ? server_hp : client_hp);
        _aes_hp_hs_recv = std::make_unique<aes256>(
            _is_server ? client_hp : server_hp);
    } else {
        _aes_hs_send = std::make_unique<aes128>(
            _is_server ? server_key : client_key);
        _aes_hs_recv = std::make_unique<aes128>(
            _is_server ? client_key : server_key);
        _aes_hp_hs_send = std::make_unique<aes128>(
            _is_server ? server_hp : client_hp);
        _aes_hp_hs_recv = std::make_unique<aes128>(
            _is_server ? client_hp : server_hp);
    }
}

// ============================================================================
// Crypto Key Derivation - Application Keys
// ============================================================================

void quic::deriveApplicationKeys() {
    // Determine key/hash sizes based on selected cipher
    const bool use_sha384 = (_selected_cipher == 0x1302);  // TLS_AES_256_GCM_SHA384
    const size_t hash_len = use_sha384 ? 48 : 32;
    const size_t key_len = use_sha384 ? 32 : 16;  // AES-256 uses 32-byte keys
    const size_t hp_len = use_sha384 ? 32 : 16;   // HP key length matches AEAD key size
    const uint16_t cipher_suite = use_sha384 ? 0x1302 : 0x1301;
    
    
    // Compute transcript hash using appropriate hash function
    std::vector<uint8_t> transcript_hash = use_sha384 
        ? sha384_hash(_tls_transcript)
        : sha256_hash(_tls_transcript);
    
    // Compute empty hash
    std::vector<uint8_t> empty_hash = use_sha384
        ? sha384_hash({})
        : sha256_hash({});
    
    // Use TLS class HKDF methods for key derivation
    std::vector<uint8_t> zero_key(hash_len, 0);
    std::vector<uint8_t> early_secret = netplus::tls::hkdf_extract({}, zero_key, cipher_suite);
    std::vector<uint8_t> derived_secret = netplus::tls::hkdf_expand_label(
        early_secret, "derived", empty_hash, hash_len, cipher_suite);
    std::vector<uint8_t> handshake_secret = netplus::tls::hkdf_extract(
        derived_secret, _ecdhe_shared, cipher_suite);
    std::vector<uint8_t> derived_hs = netplus::tls::hkdf_expand_label(
        handshake_secret, "derived", empty_hash, hash_len, cipher_suite);
    std::vector<uint8_t> master_secret = netplus::tls::hkdf_extract(
        derived_hs, zero_key, cipher_suite);
    std::vector<uint8_t> c_app_traffic = netplus::tls::hkdf_expand_label(
        master_secret, "c ap traffic", transcript_hash, hash_len, cipher_suite);
    std::vector<uint8_t> s_app_traffic = netplus::tls::hkdf_expand_label(
        master_secret, "s ap traffic", transcript_hash, hash_len, cipher_suite);
    
    // Derive QUIC-specific keys from traffic secrets
    std::vector<uint8_t> client_key = netplus::tls::hkdf_expand_label(
        c_app_traffic, "quic key", {}, key_len, cipher_suite);
    std::vector<uint8_t> client_iv = netplus::tls::hkdf_expand_label(
        c_app_traffic, "quic iv", {}, 12, cipher_suite);
    std::vector<uint8_t> client_hp = netplus::tls::hkdf_expand_label(
        c_app_traffic, "quic hp", {}, hp_len, cipher_suite);
    std::vector<uint8_t> server_key = netplus::tls::hkdf_expand_label(
        s_app_traffic, "quic key", {}, key_len, cipher_suite);
    std::vector<uint8_t> server_iv = netplus::tls::hkdf_expand_label(
        s_app_traffic, "quic iv", {}, 12, cipher_suite);
    std::vector<uint8_t> server_hp = netplus::tls::hkdf_expand_label(
        s_app_traffic, "quic hp", {}, hp_len, cipher_suite);
    
    
    // Copy to member arrays
    std::memcpy(_app_key_client, client_key.data(), key_len);
    std::memcpy(_app_iv_client, client_iv.data(), 12);
    std::memset(_app_hp_client, 0, sizeof(_app_hp_client));
    std::memcpy(_app_hp_client, client_hp.data(), hp_len);
    
    std::memcpy(_app_key_server, server_key.data(), key_len);
    std::memcpy(_app_iv_server, server_iv.data(), 12);
    std::memset(_app_hp_server, 0, sizeof(_app_hp_server));
    std::memcpy(_app_hp_server, server_hp.data(), hp_len);
    
    // Create AES instances for application encryption level
    if (use_sha384) {
        _aes_app_send = std::make_unique<aes256>(
            _is_server ? server_key : client_key);
        _aes_app_recv = std::make_unique<aes256>(
            _is_server ? client_key : server_key);
        _aes_hp_app_send = std::make_unique<aes256>(
            _is_server ? server_hp : client_hp);
        _aes_hp_app_recv = std::make_unique<aes256>(
            _is_server ? client_hp : server_hp);
    } else {
        _aes_app_send = std::make_unique<aes128>(
            _is_server ? server_key : client_key);
        _aes_app_recv = std::make_unique<aes128>(
            _is_server ? client_key : server_key);
        _aes_hp_app_send = std::make_unique<aes128>(
            _is_server ? server_hp : client_hp);
        _aes_hp_app_recv = std::make_unique<aes128>(
            _is_server ? client_hp : server_hp);
    }
}

// ============================================================================
// Header Protection (RFC 9001 Section 5.4)
// ============================================================================

void quic::applyHeaderProtection(std::vector<uint8_t>& packet, const uint8_t* hp_key, bool force_aes128) {
    if (packet.size() < 20) return;  // Need at least sample size
    
    // Find sample position (4 bytes after packet number)
    // For long header: header + pn_length
    // For short header: 1 + dcid_len + pn_length
    
    bool is_long = (packet[0] & 0x80) != 0;
    size_t pn_offset;
    
    if (is_long) {
        // Long header: skip version (4), DCID len (1), DCID, SCID len (1), SCID
        // Then token length (varint) for Initial, then length (varint), then PN
        size_t offset = 5;  // After version
        uint8_t dcid_len = packet[offset++];
        offset += dcid_len;
        uint8_t scid_len = packet[offset++];
        offset += scid_len;
        
        // Skip token for Initial packets
        uint8_t packet_type = (packet[0] & 0x30) >> 4;
        if (packet_type == 0x00) {  // Initial
            size_t tok_bytes;
            uint64_t tok_len = decodeVarInt(&packet[offset], tok_bytes);
            offset += tok_bytes + tok_len;
        }
        
        // Skip length
        size_t len_bytes;
        decodeVarInt(&packet[offset], len_bytes);
        offset += len_bytes;
        
        pn_offset = offset;
    } else {
        // Short header: 1 byte + DCID
        pn_offset = 1 + _remote_cid.size();
    }
    
    // Get PN length BEFORE applying mask (for protection application)
    size_t pn_len = (packet[0] & 0x03) + 1;
    
    // Sample starts 4 bytes after PN
    size_t sample_offset = pn_offset + 4;
    if (sample_offset + 16 > packet.size()) return;
    
    // Extract sample to get mask
    std::vector<uint8_t> sample(packet.begin() + sample_offset, 
                                 packet.begin() + sample_offset + 16);
    
    // Select cached HP AES instance based on encryption level and direction
    aes* cached_hp = nullptr;
    if (force_aes128) {
        // Initial level
        cached_hp = _is_server ? _aes_hp_initial_send.get() : _aes_hp_initial_send.get();
    } else if (!_handshake_complete && is_long) {
        // Handshake level
        cached_hp = _aes_hp_hs_send.get();
    } else {
        // Application level
        cached_hp = _aes_hp_app_send.get();
    }
    
    std::vector<uint8_t> mask;
    if (cached_hp) {
        mask = cached_hp->encrypt(sample);
    } else {
        // Fallback: create temporary instance
        const bool use_aes256 = !force_aes128 && (_selected_cipher == 0x1302);
        if (use_aes256) {
            aes256 hp_aes(std::vector<uint8_t>(hp_key, hp_key + 32));
            mask = hp_aes.encrypt(sample);
        } else {
            aes128 hp_aes(std::vector<uint8_t>(hp_key, hp_key + 16));
            mask = hp_aes.encrypt(sample);
        }
    }
    
    // Apply mask to first byte
    if (is_long) {
        packet[0] ^= (mask[0] & 0x0F);
    } else {
        packet[0] ^= (mask[0] & 0x1F);
    }
    
    // Apply mask to packet number bytes
    for (size_t i = 0; i < pn_len; ++i) {
        packet[pn_offset + i] ^= mask[1 + i];
    }
}

void quic::removeHeaderProtection(std::vector<uint8_t>& packet, const uint8_t* hp_key, bool force_aes128) {
    if (packet.size() < 20) return;  // Need at least sample size
    
    
    bool is_long = (packet[0] & 0x80) != 0;
    size_t pn_offset;
    
    if (is_long) {
        size_t offset = 5;  // After version
        uint8_t dcid_len = packet[offset++];
        offset += dcid_len;
        uint8_t scid_len = packet[offset++];
        offset += scid_len;
        
        
        uint8_t packet_type = (packet[0] & 0x30) >> 4;
        if (packet_type == 0x00) {  // Initial
            size_t tok_bytes;
            uint64_t tok_len = decodeVarInt(&packet[offset], tok_bytes);
            offset += tok_bytes + tok_len;
        }
        
        size_t len_bytes;
        decodeVarInt(&packet[offset], len_bytes);
        offset += len_bytes;
        
        pn_offset = offset;
    } else {
        // Short header: 1 byte flags + DCID (which is our local CID)
        pn_offset = 1 + _local_cid.size();
    }
    
    // Sample starts 4 bytes after PN (where ciphertext starts)
    size_t sample_offset = pn_offset + 4;
    if (sample_offset + 16 > packet.size()) return;
    
    // Extract sample to get mask
    std::vector<uint8_t> sample(packet.begin() + sample_offset, 
                                 packet.begin() + sample_offset + 16);
    
    // Select cached HP AES instance for receiving (decryption direction)
    aes* cached_hp = nullptr;
    if (force_aes128) {
        // Initial level
        cached_hp = _aes_hp_initial_recv.get();
    } else if (!_handshake_complete && is_long) {
        // Handshake level
        cached_hp = _aes_hp_hs_recv.get();
    } else {
        // Application level
        cached_hp = _aes_hp_app_recv.get();
    }
    
    std::vector<uint8_t> mask;
    if (cached_hp) {
        mask = cached_hp->encrypt(sample);
    } else {
        // Fallback: create temporary instance
        const bool use_aes256 = !force_aes128 && (_selected_cipher == 0x1302);
        if (use_aes256) {
            aes256 hp_aes(std::vector<uint8_t>(hp_key, hp_key + 32));
            mask = hp_aes.encrypt(sample);
        } else {
            aes128 hp_aes(std::vector<uint8_t>(hp_key, hp_key + 16));
            mask = hp_aes.encrypt(sample);
        }
    }
    
    
    // Remove mask from first byte to reveal PN length
    if (is_long) {
        packet[0] ^= (mask[0] & 0x0F);
    } else {
        packet[0] ^= (mask[0] & 0x1F);
    }
    
    // NOW we can read the correct PN length
    size_t pn_len = (packet[0] & 0x03) + 1;
    
    // Remove mask from packet number bytes
    for (size_t i = 0; i < pn_len; ++i) {
        packet[pn_offset + i] ^= mask[1 + i];
    }
}

// ============================================================================
// Packet Protection (AES-GCM - supports both AES-128 and AES-256)
// ============================================================================

std::vector<uint8_t> quic::protectPacket(const std::vector<uint8_t>& header,
                                          const std::vector<uint8_t>& payload,
                                          uint64_t packet_number,
                                          const uint8_t* key, const uint8_t* iv,
                                          bool force_aes128) {
    // Build nonce: IV XOR packet_number (left-padded to 12 bytes)
    uint8_t nonce[12];
    std::memcpy(nonce, iv, 12);
    for (int i = 0; i < 8; ++i) {
        nonce[11 - i] ^= static_cast<uint8_t>((packet_number >> (8 * i)) & 0xFF);
    }
    
    // Initial packets always use AES-128, others use the negotiated cipher
    const bool use_aes256 = !force_aes128 && (_selected_cipher == 0x1302);
    
    // Encrypt payload with header as AAD
    std::vector<uint8_t> ciphertext(payload.size());
    uint8_t tag[16];
    
    // Select cached AES instance for encryption based on key identity.
    // Using the key pointer avoids the _handshake_complete race: a Handshake
    // ACK sent after processFinished() must still use the handshake key.
    aes* cached_aes = nullptr;
    if (force_aes128) {
        cached_aes = _aes_initial_send.get();
    } else {
        const uint8_t* hs_send_key = _is_server ? _hs_key_server : _hs_key_client;
        if (key == hs_send_key) {
            cached_aes = _aes_hs_send.get();
        } else {
            cached_aes = _aes_app_send.get();
        }
    }
    
    if (cached_aes) {
        cached_aes->aes_gcm_encrypt(nonce, header.data(), header.size(),
                                    payload.data(), payload.size(),
                                    ciphertext.data(), tag);
    } else if (use_aes256) {
        aes256 aes_gcm(std::vector<uint8_t>(key, key + 32));
        aes_gcm.aes_gcm_encrypt(nonce, header.data(), header.size(),
                                payload.data(), payload.size(),
                                ciphertext.data(), tag);
    } else {
        aes128 aes_gcm(std::vector<uint8_t>(key, key + 16));
        aes_gcm.aes_gcm_encrypt(nonce, header.data(), header.size(),
                                payload.data(), payload.size(),
                                ciphertext.data(), tag);
    }
    
    // Combine header + ciphertext + tag
    std::vector<uint8_t> result;
    const size_t total = header.size() + ciphertext.size() + 16;
    result.reserve(total);
    result.insert(result.end(), header.begin(), header.end());
    result.insert(result.end(), ciphertext.begin(), ciphertext.end());
    result.insert(result.end(), tag, tag + 16);
    
    return result;
}

bool quic::unprotectPacket(const uint8_t* data, size_t len,
                            std::vector<uint8_t>& header,
                            std::vector<uint8_t>& payload,
                            uint64_t& packet_number,
                            const uint8_t* key, const uint8_t* iv,
                            const uint8_t* hp_key, bool force_aes128) {
    if (len < 20) {
        return false;  // Minimum packet size
    }
    
    // Copy packet for modification
    std::vector<uint8_t> packet(data, data + len);
    
    // Remove header protection first
    removeHeaderProtection(packet, hp_key, force_aes128);
    
    // Parse header to find payload offset
    bool is_long = (packet[0] & 0x80) != 0;
    size_t header_len;
    size_t pn_len = (packet[0] & 0x03) + 1;
    
    if (is_long) {
        size_t offset = 5;  // After version
        uint8_t dcid_len = packet[offset++];
        offset += dcid_len;
        uint8_t scid_len = packet[offset++];
        offset += scid_len;
        
        uint8_t packet_type = (packet[0] & 0x30) >> 4;
        if (packet_type == 0x00) {  // Initial - skip token
            size_t tok_bytes;
            uint64_t tok_len = decodeVarInt(&packet[offset], tok_bytes);
            offset += tok_bytes + tok_len;
        }
        
        // Skip length
        size_t len_bytes;
        decodeVarInt(&packet[offset], len_bytes);
        offset += len_bytes;
        
        header_len = offset + pn_len;
    } else {
        // Short header: 1 byte flags + DCID (which is our local CID) + PN
        header_len = 1 + _local_cid.size() + pn_len;
    }
    
    // Extract packet number
    uint32_t truncated_pn = 0;
    for (size_t i = 0; i < pn_len; ++i) {
        uint8_t pn_byte = packet[header_len - pn_len + i];
        truncated_pn = (truncated_pn << 8) | pn_byte;
    }
    
    // Decode full packet number (each PN space is independent per RFC 9000 §12.3)
    uint64_t largest_pn;
    if (!is_long) {
        // Short header → Application PN space
        largest_pn = (_app_pn_recv > 0 ? _app_pn_recv - 1 : 0);
    } else {
        // Long header → distinguish Initial vs Handshake by packet type
        uint8_t pkt_type = (packet[0] & 0x30) >> 4;
        if (pkt_type == 0x02) {
            // Handshake PN space
            largest_pn = (_handshake_pn_recv > 0 ? _handshake_pn_recv - 1 : 0);
        } else {
            // Initial (and 0-RTT) PN space
            largest_pn = (_initial_pn_recv > 0 ? _initial_pn_recv - 1 : 0);
        }
    }
    packet_number = decodePacketNumber(truncated_pn, pn_len, largest_pn);
    QUIC_DBG("unprotectPacket: is_long=%d truncated_pn=%u pn_len=%zu largest_pn=%lu -> decoded_pn=%lu",
             (int)is_long, truncated_pn, pn_len, (unsigned long)largest_pn, (unsigned long)packet_number);
    
    // Copy header
    header.assign(packet.begin(), packet.begin() + header_len);
    
    // Build nonce
    uint8_t nonce[12];
    std::memcpy(nonce, iv, 12);
    for (int i = 0; i < 8; ++i) {
        uint8_t pn_byte = static_cast<uint8_t>((packet_number >> (8 * i)) & 0xFF);
        nonce[11 - i] ^= pn_byte;
    }
    
    // Decrypt
    size_t ciphertext_len = len - header_len - 16;  // Subtract tag
    if (ciphertext_len > len) {
        return false;  // Overflow check
    }
    
    // Sanity check: verify we have enough data for header+ciphertext+tag
    if (len < header_len + 16) {
        return false;
    }
    
    // Initial packets always use AES-128, others use the negotiated cipher
    const bool use_aes256 = !force_aes128 && (_selected_cipher == 0x1302);
    
    payload.resize(ciphertext_len);
    const uint8_t* ct = packet.data() + header_len;
    const uint8_t* tag = packet.data() + len - 16;
    
    // Select cached AES instance for decryption based on key identity.
    aes* cached_aes = nullptr;
    if (force_aes128) {
        cached_aes = _aes_initial_recv.get();
    } else {
        const uint8_t* hs_recv_key = _is_server ? _hs_key_client : _hs_key_server;
        if (key == hs_recv_key) {
            cached_aes = _aes_hs_recv.get();
        } else {
            cached_aes = _aes_app_recv.get();
        }
    }
    
    if (cached_aes) {
        return cached_aes->aes_gcm_decrypt(nonce, header.data(), header.size(),
                                           ct, ciphertext_len, tag, payload.data());
    } else if (use_aes256) {
        aes256 aes_gcm(std::vector<uint8_t>(key, key + 32));
        return aes_gcm.aes_gcm_decrypt(nonce, header.data(), header.size(),
                                       ct, ciphertext_len, tag, payload.data());
    } else {
        aes128 aes_gcm(std::vector<uint8_t>(key, key + 16));
        return aes_gcm.aes_gcm_decrypt(nonce, header.data(), header.size(),
                                       ct, ciphertext_len, tag, payload.data());
    }
}

// ============================================================================
// Socket Interface: pushReceivedData (Push raw UDP data for processing)
// ============================================================================

void quic::pushReceivedData(const uint8_t* data, size_t len) {
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    _recv_buffer.insert(_recv_buffer.end(), data, data + len);
}

// ============================================================================
// Socket Interface: handshake_after_accept (Continue QUIC handshake)
// ============================================================================

void quic::handshake_after_accept() {
    std::vector<PendingDispatch> dispatches;
    {
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    
    
    // === HANDSHAKE TIMEOUT CHECK (SERIAL) ===
    // This runs every time handshake_after_accept() is called by the event loop
    // It catches timeout even if no packets arrive
    if (_incomplete_ch_detected && !_handshake_timeout_triggered && !_handshake_complete) {
        auto now = std::chrono::steady_clock::now();
        auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
            now - _handshake_start_time).count();
        
        if (elapsed_ms > 2000) {  // 2 second timeout for fragmented messages
            _handshake_timeout_triggered = true;
            _conn_state.store(ConnectionState::Closed);
        }
    }
    
    // Process any buffered data
    if (!_recv_buffer.empty()) {
        std::vector<uint8_t> packet = std::move(_recv_buffer);
        _recv_buffer.clear();
        processIncomingPacket(packet.data(), packet.size());
    }
    
    // Try to receive more data if handshake not complete
    if (!_handshake_complete && _Socket >= 0) {
        buffer recv_buf(65535);
        try {
            size_t recv_len = udp::recvData(recv_buf, MSG_DONTWAIT);
            if (recv_len > 0) {
                processIncomingPacket(reinterpret_cast<const uint8_t*>(recv_buf.data.buf),
                                     recv_len);
            }
        } catch (NetException&) {
            // recvData throws on EAGAIN/EWOULDBLOCK or errors
        }
        {
            
            // If no data on child socket and parent exists, check if parent has data for us
            if (_parent != nullptr) {
                // Note: Parent will route packets to us via _child_connections in its accept()
                // We just need the event loop to call accept() on the parent
            }
        }
        
        // Handshake retransmission: if server sent handshake flight but
        // no completion after HS_RETRANSMIT_INTERVAL, resend the flight.
        if (_is_server && !_hs_flight_packets.empty()) {
            auto now = std::chrono::steady_clock::now();
            auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
                now - _hs_last_send).count();
            int timeout = 200 * (1 << std::min(_hs_retransmit_count, 4));  // 200, 400, 800, 1600, 3200
            if (elapsed > timeout) {
                _hs_retransmit_count++;
                _hs_last_send = now;
                for (auto& pkt : _hs_flight_packets) {
                    sendPacket(pkt.data(), pkt.size());
                }
            }
        }
    }
    
    // PING keepalive: send PING frame if connection is established but idle
    if (_handshake_complete &&
        _conn_state.load() == ConnectionState::Connected &&
        _Socket >= 0) {
        auto now = std::chrono::steady_clock::now();
        auto idle_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
            now - _last_activity).count();
        if (idle_ms >= KEEPALIVE_INTERVAL_MS) {
            // PING frame is just type 0x01, no payload
            std::vector<uint8_t> ping_frame = {0x01};
            std::vector<uint8_t> packet = buildShortHeaderPacket(ping_frame);
            const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
            applyHeaderProtection(packet, hp_key);
            sendPacket(packet.data(), packet.size());
            _last_activity = now;
        }
    }

    // Collect pending dispatches before releasing lock
    dispatches = std::move(_pending_dispatches);
    _pending_dispatches.clear();
    } // Release _quic_mutex

    // Dispatch stream callbacks outside the lock
    if (!dispatches.empty() && _stream_callback) {
        for (auto& pd : dispatches) {
            _stream_callback(this, pd.stream_id, pd.data, pd.fin);
        }
    }
}

// ============================================================================
// Socket Interface: accept (Server-side connection acceptance)
// ============================================================================

void quic::accept(std::unique_ptr<socket>& csock, bool nonblock) {
    std::unique_lock<std::recursive_mutex> lock(quic_mtx());
    
    
    // Purge stale (Closed) connections from ownership vector
    _child_owned_connections.erase(
        std::remove_if(_child_owned_connections.begin(), _child_owned_connections.end(),
            [this](const std::unique_ptr<quic>& p) {
                if (p && p->_conn_state.load() == ConnectionState::Closed) {
                    _child_connections.erase(p->_original_dcid);
                    _child_connections.erase(p->_local_cid);
                    return true;
                }
                return false;
            }),
        _child_owned_connections.end());
    
    // QUIC accept is different from TCP - we receive Initial packets on the
    // listening socket and create new quic instances for each connection.
    
    // Check if socket is valid
    if (_Socket < 0) {
        csock = nullptr;
        return;
    }
    
    // Drain up to 512 datagrams per call.  On high-throughput links (esp.
    // localhost) the client may burst many QUIC packets between epoll_wait
    // cycles.  If we only read one datagram per cycle the kernel UDP
    // receive buffer overflows, the client detects packet loss and its
    // congestion window collapses.  Draining here keeps up with the
    // sender.  For packets belonging to *existing* connections we process
    // and ACK immediately, then loop back for the next datagram.  A new
    // Initial packet (new connection) exits the loop so the caller can
    // register it.
    static constexpr int MAX_DRAIN = 512;
    
    for (int drain_i = 0; drain_i < MAX_DRAIN; ++drain_i) {
    
    // Allocate receive buffer for incoming UDP datagram
    std::vector<uint8_t> dgram(65535);
    sockaddr_storage peer_addr{};
    socklen_t peer_len = sizeof(peer_addr);
    
    // Receive UDP datagram
    ssize_t recv_len = ::recvfrom(_Socket, reinterpret_cast<char*>(dgram.data()), 
                                   static_cast<int>(dgram.size()), nonblock ? MSG_DONTWAIT : 0,
                                   reinterpret_cast<sockaddr*>(&peer_addr), &peer_len);
    
    
    if (recv_len < 0) {
#ifdef Windows
        int err = ::WSAGetLastError();
        if (err == WSAEWOULDBLOCK) {
            csock = nullptr;
            return;
        }
#else
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            csock = nullptr;
            return;
        }
        if (errno == EBADF) {
            csock = nullptr;
            return;
        }
#endif
        NetException e;
        e[NetException::Error] << "quic::accept recvfrom failed";
        throw e;
    }
    
    if (recv_len < 1) {
        csock = nullptr;
        return;
    }
    
    dgram.resize(static_cast<size_t>(recv_len));
    
    // Check if this is a long header packet (Initial, Handshake, etc.)
    if ((dgram[0] & 0x80) == 0) {
        // Short header - this is for an existing connection
        // The DCID is right after the first byte (no length prefix for short headers)
        // We assume 8-byte CID (configurable)
        size_t cid_len = 8;  // Default CID length
        if (dgram.size() < 1 + cid_len) {
            // Malformed — skip and try next datagram
            continue;
        }
        
        std::vector<uint8_t> dcid(dgram.begin() + 1, dgram.begin() + 1 + cid_len);
        
        // Look up the connection
        auto it = _child_connections.find(dcid);
        if (it != _child_connections.end() && it->second != nullptr) {
            quic* existing = it->second;
            
            // Skip stale (Closed/Draining) connections
            if (existing->_conn_state.load() == ConnectionState::Closed ||
                existing->_conn_state.load() == ConnectionState::Draining) {
                _child_connections.erase(it);
            } else {
                // Parent lock covers child state — no separate child lock needed
                existing->processIncomingPacket(dgram.data(), dgram.size());

                // Flush child's pending flow control frames
                if (existing->_handshake_complete &&
                    existing->_conn_state.load() == ConnectionState::Connected) {
                    for (auto& [sid, limit] : existing->_pending_max_stream_data) {
                        existing->sendMaxStreamData(sid, limit);
                    }
                    existing->_pending_max_stream_data.clear();
                    if (existing->_pending_max_data) {
                        existing->sendMaxData(existing->_pending_max_data_value);
                        existing->_pending_max_data = false;
                    }
                }

                // Dispatch stream callbacks outside the lock
                if (!existing->_pending_dispatches.empty() && existing->_stream_callback) {
                    std::vector<PendingDispatch> dispatches = std::move(existing->_pending_dispatches);
                    existing->_pending_dispatches.clear();
                    lock.unlock();
                    for (auto& pd : dispatches) {
                        existing->_stream_callback(existing, pd.stream_id, pd.data, pd.fin);
                    }
                    lock.lock();
                }
            }
        }
        
        // lock is always held here — just continue to next datagram
        continue;
    }
    
    // Parse long header to extract connection IDs
    if (dgram.size() < 7) {
        continue;  // Malformed — skip
    }
    
    // Extract version
    uint32_t version = (static_cast<uint32_t>(dgram[1]) << 24) |
                       (static_cast<uint32_t>(dgram[2]) << 16) |
                       (static_cast<uint32_t>(dgram[3]) << 8) |
                       static_cast<uint32_t>(dgram[4]);
    
    // Check for version negotiation (version == 0 means VN)
    if (version == 0) {
        continue;  // Skip VN packets
    }
    
    // Parse DCID (Destination Connection ID - this is our CID)
    size_t offset = 5;
    if (offset >= dgram.size()) {
        continue;  // Malformed — skip
    }
    
    uint8_t dcid_len = dgram[offset++];
    if (dcid_len > 20 || offset + dcid_len > dgram.size()) {
        continue;  // Malformed — skip
    }
    
    std::vector<uint8_t> dcid(dgram.begin() + offset, dgram.begin() + offset + dcid_len);
    offset += dcid_len;
    
    
    // Parse SCID (Source Connection ID - client's CID)
    if (offset >= dgram.size()) {
        continue;  // Malformed — skip
    }
    
    uint8_t scid_len = dgram[offset++];
    if (scid_len > 20 || offset + scid_len > dgram.size()) {
        continue;  // Malformed — skip
    }
    
    std::vector<uint8_t> scid(dgram.begin() + offset, dgram.begin() + offset + scid_len);
    offset += scid_len;
    
    
    // Check if this is for an existing connection (by original DCID)
    // The DCID in subsequent packets should match our local_cid of an existing connection
    
    auto it = _child_connections.find(dcid);
    if (it != _child_connections.end() && it->second != nullptr) {
        quic* existing = it->second;
        
        // Check if existing connection is stale (Closed/Draining)
        // AND this is a new Initial packet - client is reconnecting
        uint8_t pkt_type = (dgram[0] & 0x30) >> 4;
        if (pkt_type == 0x00 &&
            (existing->_conn_state.load() == ConnectionState::Closed ||
             existing->_conn_state.load() == ConnectionState::Draining)) {
            
            // Remove from routing maps
            _child_connections.erase(existing->_local_cid);
            _child_connections.erase(existing->_original_dcid);
            
            // Remove from ownership vector (destroys the old connection)
            _child_owned_connections.erase(
                std::remove_if(_child_owned_connections.begin(), _child_owned_connections.end(),
                    [existing](const std::unique_ptr<quic>& p) { return p.get() == existing; }),
                _child_owned_connections.end());
            
            // Fall through to create a new connection
        } else {
            // Active connection - parent lock covers child state
            existing->processIncomingPacket(dgram.data(), dgram.size());

            // Flush child's pending flow control frames
            if (existing->_handshake_complete &&
                existing->_conn_state.load() == ConnectionState::Connected) {
                for (auto& [sid, limit] : existing->_pending_max_stream_data) {
                    existing->sendMaxStreamData(sid, limit);
                }
                existing->_pending_max_stream_data.clear();
                if (existing->_pending_max_data) {
                    existing->sendMaxData(existing->_pending_max_data_value);
                    existing->_pending_max_data = false;
                }
            }

            // Dispatch stream callbacks outside the lock
            if (!existing->_pending_dispatches.empty() && existing->_stream_callback) {
                std::vector<PendingDispatch> dispatches = std::move(existing->_pending_dispatches);
                existing->_pending_dispatches.clear();
                lock.unlock();
                for (auto& pd : dispatches) {
                    existing->_stream_callback(existing, pd.stream_id, pd.data, pd.fin);
                }
                lock.lock();
            }
            continue;
        }
    }
    
    // Check packet type - must be Initial (0x00) for new connections
    uint8_t packet_type = (dgram[0] & 0x30) >> 4;
    if (packet_type != 0x00) {
        // Not an Initial packet and no existing connection found - skip
        continue;
    }
    
    {
        char peer_ip[INET6_ADDRSTRLEN] = {};
        uint16_t peer_port = 0;
        if (peer_addr.ss_family == AF_INET) {
            auto* sa4 = reinterpret_cast<struct sockaddr_in*>(&peer_addr);
            inet_ntop(AF_INET, &sa4->sin_addr, peer_ip, sizeof(peer_ip));
            peer_port = ntohs(sa4->sin_port);
        } else if (peer_addr.ss_family == AF_INET6) {
            auto* sa6 = reinterpret_cast<struct sockaddr_in6*>(&peer_addr);
            inet_ntop(AF_INET6, &sa6->sin6_addr, peer_ip, sizeof(peer_ip));
            peer_port = ntohs(sa6->sin6_port);
        }
        std::cerr << "[QUIC] Initial packet from " << peer_ip << ":" << peer_port
                  << " len=" << dgram.size() << " ver=0x" << std::hex << version << std::dec << std::endl;
    }
    
    // Verify supported version
    if (version != QUIC_VERSION_1 && version != QUIC_VERSION_2) {
        // Send Version Negotiation packet
        // TODO: Implement version negotiation
        continue;
    }
    
    // Parse token (for Initial packets)
    if (offset >= dgram.size()) {
        continue;  // Malformed — skip
    }
    
    size_t tok_bytes;
    uint64_t token_len = decodeVarInt(&dgram[offset], tok_bytes);
    offset += tok_bytes;
    
    if (offset + token_len > dgram.size()) {
        continue;  // Malformed — skip
    }
    
    std::vector<uint8_t> token(dgram.begin() + offset, dgram.begin() + offset + token_len);
    offset += token_len;
    
    // Create new QUIC connection socket for this client
    auto new_quic = std::make_unique<quic>();
    
    // Configure the new connection
    new_quic->_is_server = true;
    new_quic->_conn_state.store(ConnectionState::Handshaking);
    new_quic->_version = version;
    // RFC 9000: Server sends packets TO the client using the client's SCID as DCID
    new_quic->_remote_cid = scid;           // Client's SCID becomes our DCID for responses
    new_quic->_original_dcid = dcid;        // Original DCID for key derivation
    new_quic->_cert_map = _cert_map;        // Copy certificate configuration
    new_quic->_parent = this;               // Link to parent for routing
    new_quic->_stream_callback = _stream_callback;  // Copy stream callback
    
    // Generate our own connection ID for the client to use
    new_quic->generateConnectionId(new_quic->_local_cid, 8);
    
    // Store the unique_ptr in the ownership vector to keep it alive
    // Then store raw pointers in the lookup map for fast access
    quic* child_ptr = new_quic.get();
    _child_owned_connections.push_back(std::move(new_quic));
    
    // Register this connection with both the original DCID and our local CID
    _child_connections[dcid] = child_ptr;
    _child_connections[child_ptr->_local_cid] = child_ptr;
    
    
    // Store peer address
    std::memcpy(&child_ptr->_Addr, &peer_addr, peer_len);
    child_ptr->_AddrLen = peer_len;
    
    if (peer_addr.ss_family == AF_INET) {
        struct sockaddr_in* addr_in = reinterpret_cast<struct sockaddr_in*>(&peer_addr);
        char ip_str[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, INET_ADDRSTRLEN);
    } else if (peer_addr.ss_family == AF_INET6) {
        struct sockaddr_in6* addr_in6 = reinterpret_cast<struct sockaddr_in6*>(&peer_addr);
        char ip_str[INET6_ADDRSTRLEN];
        inet_ntop(AF_INET6, &addr_in6->sin6_addr, ip_str, INET6_ADDRSTRLEN);
    } else {
    }
    
    // QUIC: Child connections share the parent's socket for receiving
    // All packets arrive at the parent and are routed via _child_connections
    // The child uses the parent socket with sendto() for sending
    child_ptr->_Socket = _Socket;  // Share parent socket
    
    // Set non-blocking if requested (parent should already be non-blocking)
    if (nonblock) {
        child_ptr->setNonBlock();
    }
    
    // Derive initial keys from the original DCID
    child_ptr->deriveInitialKeys(dcid, true);
    
    // Store the received Initial packet for processing
    child_ptr->_recv_buffer = dgram;
    
    // Process the Initial packet to start handshake
    child_ptr->processIncomingPacket(dgram.data(), dgram.size());
    
    // Flush child's pending flow control frames
    if (child_ptr->_handshake_complete &&
        child_ptr->_conn_state.load() == ConnectionState::Connected) {
        for (auto& [sid, limit] : child_ptr->_pending_max_stream_data) {
            child_ptr->sendMaxStreamData(sid, limit);
        }
        child_ptr->_pending_max_stream_data.clear();
        if (child_ptr->_pending_max_data) {
            child_ptr->sendMaxData(child_ptr->_pending_max_data_value);
            child_ptr->_pending_max_data = false;
        }
    }

    // Dispatch any pending child callbacks outside the parent lock
    {
        std::vector<PendingDispatch> child_dispatches;
        child_dispatches = std::move(child_ptr->_pending_dispatches);
        child_ptr->_pending_dispatches.clear();
        if (!child_dispatches.empty() && child_ptr->_stream_callback) {
            lock.unlock();
            for (auto& pd : child_dispatches) {
                child_ptr->_stream_callback(child_ptr, pd.stream_id, pd.data, pd.fin);
            }
            lock.lock();
        }
    }
    
    // Child connection is owned by parent's _child_owned_connections.
    // Don't transfer ownership to csock (would cause double-free).
    // QUIC connections share the parent socket and are routed via accept().
    csock = nullptr;
    return;  // New connection created — exit drain loop
    
    } // end drain loop (for drain_i)
    
    // Reached MAX_DRAIN limit — will be called again by epoll
    csock = nullptr;
}

// ============================================================================
// Packet Processing: Incoming Packets
// ============================================================================

void quic::processIncomingPacket(const uint8_t* data, size_t len) {
    if (len < 1) return;
    QUIC_DBG("processIncomingPacket: len=%zu first_byte=0x%02x", len, data[0]);
    
    // Process coalesced packets by parsing each one's length and processing
    size_t offset = 0;
    while (offset < len) {
        // Check if we can read enough for a complete packet header
        if (offset >= len) break;
        
        // Check header form bit
        if (data[offset] & 0x80) {
            // Long header packet - parse length to find packet boundary
            size_t pkt_offset = offset + 5;  // After first byte + version
            
            if (pkt_offset + 2 > len) {
                // Can't read DCID/SCID lengths, process what we have as single packet
                processLongHeaderPacket(data + offset, len - offset);
                break;
            }
            
            // Parse DCID length and skip it
            uint8_t dcid_len = data[pkt_offset++];
            if (pkt_offset + dcid_len + 1 > len) {
                // Incomplete header
                processLongHeaderPacket(data + offset, len - offset);
                break;
            }
            pkt_offset += dcid_len;
            
            // Parse SCID length and skip it
            uint8_t scid_len = data[pkt_offset++];
            if (pkt_offset + scid_len > len) {
                // Incomplete header
                processLongHeaderPacket(data + offset, len - offset);
                break;
            }
            pkt_offset += scid_len;
            
            // For Initial packets, skip token
            uint8_t packet_type = (data[offset] & 0x30) >> 4;
            if (packet_type == 0x00) {  // Initial
                size_t tok_bytes;
                uint64_t tok_len = decodeVarInt(&data[pkt_offset], tok_bytes);
                if (pkt_offset + tok_bytes + tok_len > len) {
                    // Incomplete packet
                    processLongHeaderPacket(data + offset, len - offset);
                    break;
                }
                pkt_offset += tok_bytes + tok_len;
            }
            
            // Parse Length field
            if (pkt_offset + 1 > len) {
                // Can't read length
                processLongHeaderPacket(data + offset, len - offset);
                break;
            }
            
            size_t len_bytes;
            uint64_t payload_len = decodeVarInt(&data[pkt_offset], len_bytes);
            if (pkt_offset + len_bytes + payload_len > len) {
                // Incomplete packet, process remaining datagram
                processLongHeaderPacket(data + offset, len - offset);
                break;
            }
            
            size_t packet_len = pkt_offset + len_bytes + payload_len;
            processLongHeaderPacket(data + offset, packet_len);
            offset += packet_len;
        } else {
            // Short header packet - rest of datagram is one packet
            processShortHeaderPacket(data + offset, len - offset);
            break;
        }
    }
    
    // Flush pending ACKs for Initial and Handshake encryption levels
    if (_need_initial_ack) {
        _need_initial_ack = false;
        QUIC_DBG("flush: sending Initial ACK for pn=%lu", (unsigned long)_initial_pn_recv);
        std::vector<uint8_t> ack_frame = buildAckFrame(_initial_pn_recv, 0, _initial_pn_recv_ranges);
        std::vector<uint8_t> packet = buildInitialPacket(ack_frame);
        const uint8_t* hp_key = _is_server ? _initial_hp_server : _initial_hp_client;
        applyHeaderProtection(packet, hp_key);
        sendPacket(packet.data(), packet.size());
    }
    if (_need_handshake_ack) {
        _need_handshake_ack = false;
        QUIC_DBG("flush: sending Handshake ACK for pn=%lu (handshake_complete=%d)", 
                 (unsigned long)_handshake_pn_recv, (int)_handshake_complete);
        std::vector<uint8_t> ack_frame = buildAckFrame(_handshake_pn_recv, 0, _handshake_pn_recv_ranges);
        std::vector<uint8_t> packet = buildHandshakePacket(ack_frame);
        const uint8_t* hp_key = _is_server ? _hs_hp_server : _hs_hp_client;
        applyHeaderProtection(packet, hp_key);
        sendPacket(packet.data(), packet.size());
    }
}

void quic::processLongHeaderPacket(const uint8_t* data, size_t len) {
    if (len < 7) return;
    
    uint8_t packet_type = (data[0] & 0x30) >> 4;
    QUIC_DBG("processLongHeaderPacket: type=%u len=%zu", packet_type, len);
    
    
    switch (packet_type) {
        case 0x00:  // Initial
            processInitialPacket(data, len);
            break;
        case 0x01:  // 0-RTT
            // TODO: Implement 0-RTT handling
            break;
        case 0x02:  // Handshake
            processHandshakePacket(data, len);
            break;
        case 0x03:  // Retry
            // TODO: Implement Retry handling
            break;
    }
}

void quic::processShortHeaderPacket(const uint8_t* data, size_t len) {
    // Short header packets are 1-RTT (application data)
    QUIC_DBG("processShortHeaderPacket: len=%zu", len);
    processApplicationPacket(data, len);
}

void quic::processInitialPacket(const uint8_t* data, size_t len) {
    // First, parse the header to get the actual packet length (for coalesced packets)
    if (len < 20) return;
    QUIC_DBG("processInitialPacket: len=%zu", len);
    
    size_t offset = 5;  // After first byte + version
    uint8_t dcid_len = data[offset++];
    offset += dcid_len;
    uint8_t scid_len = data[offset++];
    offset += scid_len;
    
    // Token length (varint)
    size_t tok_bytes;
    uint64_t tok_len = decodeVarInt(&data[offset], tok_bytes);
    offset += tok_bytes + tok_len;
    
    // Payload length (varint) - this includes PN + encrypted payload + tag
    size_t len_bytes;
    uint64_t payload_len = decodeVarInt(&data[offset], len_bytes);
    offset += len_bytes;
    
    // Total packet length = header length (offset) + payload length
    size_t packet_len = offset + payload_len;
    if (packet_len > len) {
        return;
    }
    
    
    // Decrypt and process Initial packet
    std::vector<uint8_t> header, payload;
    uint64_t pn;
    
    const uint8_t* key = _is_server ? _initial_key_client : _initial_key_server;
    const uint8_t* iv = _is_server ? _initial_iv_client : _initial_iv_server;
    const uint8_t* hp = _is_server ? _initial_hp_client : _initial_hp_server;
    
    // Initial packets always use AES-128 - use actual packet_len, not full datagram len
    if (!unprotectPacket(data, packet_len, header, payload, pn, key, iv, hp, true)) {
        QUIC_DBG("processInitialPacket: decrypt FAILED");
        return;
    }
    QUIC_DBG("processInitialPacket: decrypt OK pn=%lu payload=%zu", (unsigned long)pn, payload.size());
    
    // Update largest received packet number
    if (pn > _initial_pn_recv) {
        _initial_pn_recv = pn;
    }
    
    // Track received PN in ranges for proper ACK encoding
    trackRecvPN(pn, _initial_pn_recv_ranges);
    
    // Set encryption level for frame processing
    _current_enc_level = EncryptionLevel::Initial;
    
    // Process frames in payload
    size_t frame_offset = 0;
    while (frame_offset < payload.size()) {
        processFrame(payload.data(), payload.size(), frame_offset);
    }
    
    // Send ACK for received Initial packet (both client and server need this)
    _need_initial_ack = true;
}

void quic::processHandshakePacket(const uint8_t* data, size_t len) {
    QUIC_DBG("processHandshakePacket: len=%zu", len);
    if (len > 100) {
    }
    
    // Parse the header to get the actual packet length (for coalesced packets)
    if (len < 20) return;
    
    size_t offset = 5;  // After first byte + version
    uint8_t dcid_len = data[offset++];
    offset += dcid_len;
    uint8_t scid_len = data[offset++];
    offset += scid_len;
    
    // Length field
    size_t len_bytes;
    uint64_t payload_len = decodeVarInt(&data[offset], len_bytes);
    offset += len_bytes;
    
    // Total packet length = header length (offset) + payload length
    size_t packet_len = offset + payload_len;
    if (packet_len > len) {
        return;
    }
    
    
    // Decrypt and process Handshake packet
    std::vector<uint8_t> header, payload;
    uint64_t pn;
    
    const uint8_t* key = _is_server ? _hs_key_client : _hs_key_server;
    const uint8_t* iv = _is_server ? _hs_iv_client : _hs_iv_server;
    const uint8_t* hp = _is_server ? _hs_hp_client : _hs_hp_server;
    
    
    
    
    // Handshake packets use the negotiated cipher - use actual packet_len
    if (!unprotectPacket(data, packet_len, header, payload, pn, key, iv, hp, false)) {
        QUIC_DBG("processHandshakePacket: decrypt FAILED");
        return;
    }
    QUIC_DBG("processHandshakePacket: decrypt OK pn=%lu payload=%zu", (unsigned long)pn, payload.size());
    
    
    if (pn > _handshake_pn_recv) {
        _handshake_pn_recv = pn;
    }
    
    // Track received PN in ranges for proper ACK encoding
    trackRecvPN(pn, _handshake_pn_recv_ranges);
    
    // Set encryption level for frame processing
    _current_enc_level = EncryptionLevel::Handshake;
    
    size_t hs_offset = 0;
    while (hs_offset < payload.size()) {
        processFrame(payload.data(), payload.size(), hs_offset);
    }
    
    // Send ACK for received Handshake packet
    _need_handshake_ack = true;
}

void quic::processApplicationPacket(const uint8_t* data, size_t len) {
    // Decrypt and process 1-RTT packet
    QUIC_DBG("processApplicationPacket: len=%zu handshake_complete=%d", len, (int)_handshake_complete);
    std::vector<uint8_t> header, payload;
    uint64_t pn;
    
    const uint8_t* key = _is_server ? _app_key_client : _app_key_server;
    const uint8_t* iv = _is_server ? _app_iv_client : _app_iv_server;
    const uint8_t* hp = _is_server ? _app_hp_client : _app_hp_server;
    
    // Set encryption level for frame processing
    _current_enc_level = EncryptionLevel::Application;
    
    // Application packets use the negotiated cipher
    if (!unprotectPacket(data, len, header, payload, pn, key, iv, hp, false)) {
        QUIC_DBG("processApplicationPacket: decrypt FAILED");
        return;
    }
    QUIC_DBG("processApplicationPacket: decrypt OK pn=%lu payload=%zu", (unsigned long)pn, payload.size());
    
    
    if (pn > _app_pn_recv) {
        _app_pn_recv = pn;
    }

    // Track received packet number in ranges for proper ACK encoding
    trackRecvPN(pn, _app_pn_recv_ranges);

    // Send ACK immediately — before processing frames — so the peer
    // does not time out if a stream callback (e.g. RequestEvent) blocks
    // for a long time during heavy request handling.
    sendAppAck();

    size_t offset = 0;
    while (offset < payload.size()) {
        processFrame(payload.data(), payload.size(), offset);
    }
}

// ============================================================================
// Socket Interface: close (Graceful connection termination)
// ============================================================================

void quic::close() {
    // quic_mtx() returns parent's mutex for children — one lock covers both
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    
    // Don't close if already closed
    if (_conn_state.load() == ConnectionState::Closed) {
        return;
    }
    
    // If connection is established, send CONNECTION_CLOSE frame
    if (_conn_state.load() == ConnectionState::Connected || 
        _conn_state.load() == ConnectionState::Handshaking) {
        
        // Build CONNECTION_CLOSE frame (error code 0 = NO_ERROR)
        std::vector<uint8_t> close_frame = buildConnectionCloseFrame(0x00, "");
        
        // Send in appropriate packet type based on connection state
        if (_handshake_complete) {
            // Send in 1-RTT packet
            std::vector<uint8_t> packet = buildShortHeaderPacket(close_frame);
            
            // Apply header protection
            const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
            applyHeaderProtection(packet, hp_key);
            
            // Send the packet
            if (_Socket >= 0) {
                sendPacket(packet.data(), packet.size());
            }
        } else {
            // Send in Initial packet during handshake
            std::vector<uint8_t> packet = buildInitialPacket(close_frame);
            
            // Apply header protection  
            const uint8_t* hp_key = _is_server ? _initial_hp_server : _initial_hp_client;
            applyHeaderProtection(packet, hp_key, true);
            
            if (_Socket >= 0) {
                sendPacket(packet.data(), packet.size());
            }
        }
        
        // Enter draining state
        _conn_state.store(ConnectionState::Draining);
    }
    
    // Close all streams
    for (auto& [stream_id, stream] : _streams) {
        stream.send_fin = true;
        stream.recv_fin = true;
    }
    _streams.clear();
    
    // Clear send queue
    _send_queue.clear();
    
    // Clear crypto buffers
    _crypto_recv_initial.clear();
    _crypto_recv_handshake.clear();
    _crypto_recv_app.clear();
    _crypto_send_initial.clear();
    _crypto_send_handshake.clear();
    
    // Zero out keys for security
    std::memset(_initial_key_client, 0, sizeof(_initial_key_client));
    std::memset(_initial_iv_client, 0, sizeof(_initial_iv_client));
    std::memset(_initial_hp_client, 0, sizeof(_initial_hp_client));
    std::memset(_initial_key_server, 0, sizeof(_initial_key_server));
    std::memset(_initial_iv_server, 0, sizeof(_initial_iv_server));
    std::memset(_initial_hp_server, 0, sizeof(_initial_hp_server));
    
    std::memset(_hs_key_client, 0, sizeof(_hs_key_client));
    std::memset(_hs_iv_client, 0, sizeof(_hs_iv_client));
    std::memset(_hs_hp_client, 0, sizeof(_hs_hp_client));
    std::memset(_hs_key_server, 0, sizeof(_hs_key_server));
    std::memset(_hs_iv_server, 0, sizeof(_hs_iv_server));
    std::memset(_hs_hp_server, 0, sizeof(_hs_hp_server));
    
    std::memset(_app_key_client, 0, sizeof(_app_key_client));
    std::memset(_app_iv_client, 0, sizeof(_app_iv_client));
    std::memset(_app_hp_client, 0, sizeof(_app_hp_client));
    std::memset(_app_key_server, 0, sizeof(_app_key_server));
    std::memset(_app_iv_server, 0, sizeof(_app_iv_server));
    std::memset(_app_hp_server, 0, sizeof(_app_hp_server));
    
    // Release AES instances
    _aes_initial_send.reset();
    _aes_initial_recv.reset();
    _aes_hs_send.reset();
    _aes_hs_recv.reset();
    _aes_app_send.reset();
    _aes_app_recv.reset();
    
    // Close underlying UDP socket (only if we own it, not if we're a child)
    // Child connections share the parent's socket and should not close it
    if (_Socket >= 0 && _parent == nullptr) {
#ifdef Windows
        ::closesocket(_Socket);
#else
        ::close(_Socket);
#endif
        _Socket = -1;
    }
    
    _conn_state.store(ConnectionState::Closed);
    _handshake_complete = false;
    
    // Remove from parent's child map (same lock — quic_mtx() is parent's mutex)
    if (_parent != nullptr) {
        _parent->_child_connections.erase(_original_dcid);
        _parent->_child_connections.erase(_local_cid);
    }
}

// ============================================================================
// Frame Building: CONNECTION_CLOSE
// ============================================================================

std::vector<uint8_t> quic::buildConnectionCloseFrame(uint64_t error_code, const std::string& reason) {
    std::vector<uint8_t> frame;
    
    // Frame type: CONNECTION_CLOSE (0x1c for QUIC layer, 0x1d for application layer)
    uint8_t type_buf[8];
    size_t type_len = encodeVarInt(static_cast<uint64_t>(FrameType::CONNECTION_CLOSE), type_buf);
    frame.insert(frame.end(), type_buf, type_buf + type_len);
    
    // Error code
    uint8_t err_buf[8];
    size_t err_len = encodeVarInt(error_code, err_buf);
    frame.insert(frame.end(), err_buf, err_buf + err_len);
    
    // Frame type (0 if not applicable)
    uint8_t ft_buf[8];
    size_t ft_len = encodeVarInt(0, ft_buf);
    frame.insert(frame.end(), ft_buf, ft_buf + ft_len);
    
    // Reason phrase length
    uint8_t rlen_buf[8];
    size_t rlen_len = encodeVarInt(reason.size(), rlen_buf);
    frame.insert(frame.end(), rlen_buf, rlen_buf + rlen_len);
    
    // Reason phrase
    if (!reason.empty()) {
        frame.insert(frame.end(), reason.begin(), reason.end());
    }
    
    return frame;
}

// ============================================================================
// Packet Building: Initial Packet
// ============================================================================

std::vector<uint8_t> quic::buildInitialPacket(const std::vector<uint8_t>& payload) {
    std::vector<uint8_t> header;
    
    // First byte: Long header form (1) + Fixed bit (1) + Type (00) + Reserved (00) + PN length (00)
    // 0xC0 = 1100 0000 (long header, fixed bit, Initial type)
    uint8_t first_byte = 0xC0;
    size_t pn_len = 4;  // Use 4-byte packet number for simplicity
    first_byte |= (pn_len - 1);  // PN length encoded as 0-3
    header.push_back(first_byte);
    
    // Version (4 bytes)
    header.push_back(static_cast<uint8_t>(_version >> 24));
    header.push_back(static_cast<uint8_t>(_version >> 16));
    header.push_back(static_cast<uint8_t>(_version >> 8));
    header.push_back(static_cast<uint8_t>(_version));
    
    // DCID length + DCID
    header.push_back(static_cast<uint8_t>(_remote_cid.size()));
    header.insert(header.end(), _remote_cid.begin(), _remote_cid.end());
    
    // SCID length + SCID
    header.push_back(static_cast<uint8_t>(_local_cid.size()));
    header.insert(header.end(), _local_cid.begin(), _local_cid.end());
    
    // Token length (0 for client Initial, may be non-zero after Retry)
    uint8_t tok_len_buf[8];
    size_t tok_len_size = encodeVarInt(0, tok_len_buf);  // No token
    header.insert(header.end(), tok_len_buf, tok_len_buf + tok_len_size);
    
    // Calculate total length (PN + payload + AEAD tag)
    size_t total_len = pn_len + payload.size() + 16;  // 16-byte AEAD tag
    
    // Length field (variable-length integer)
    uint8_t len_buf[8];
    size_t len_size = encodeVarInt(total_len, len_buf);
    header.insert(header.end(), len_buf, len_buf + len_size);
    
    // Packet number (4 bytes)
    uint64_t pn = _initial_pn_send++;
    header.push_back(static_cast<uint8_t>(pn >> 24));
    header.push_back(static_cast<uint8_t>(pn >> 16));
    header.push_back(static_cast<uint8_t>(pn >> 8));
    header.push_back(static_cast<uint8_t>(pn));
    
    // Protect packet (encrypt payload with header as AAD)
    // Initial packets always use AES-128 regardless of negotiated cipher
    const uint8_t* key = _is_server ? _initial_key_server : _initial_key_client;
    const uint8_t* iv = _is_server ? _initial_iv_server : _initial_iv_client;
    
    return protectPacket(header, payload, pn, key, iv, true /* force_aes128 */);
}

// ============================================================================
// Packet Building: Handshake Packet
// ============================================================================

std::vector<uint8_t> quic::buildHandshakePacket(const std::vector<uint8_t>& payload) {
    std::vector<uint8_t> header;
    
    // First byte: Long header + Handshake type (bits 5-4 = 10)
    // Bit 7 = 1 (long form), Bit 6 = 1 (fixed bit), Bits 5-4 = 10 (Handshake)
    // Base byte = 1110 0000 = 0xE0 (before adding packet number length bits)
    uint8_t first_byte = 0xE0;  // 1110 0000 (long header, fixed bit, Handshake type)
    size_t pn_len = 4;
    first_byte |= (pn_len - 1);
    header.push_back(first_byte);
    
    // Version
    header.push_back(static_cast<uint8_t>(_version >> 24));
    header.push_back(static_cast<uint8_t>(_version >> 16));
    header.push_back(static_cast<uint8_t>(_version >> 8));
    header.push_back(static_cast<uint8_t>(_version));
    
    // DCID length + DCID
    header.push_back(static_cast<uint8_t>(_remote_cid.size()));
    header.insert(header.end(), _remote_cid.begin(), _remote_cid.end());
    
    // SCID length + SCID
    header.push_back(static_cast<uint8_t>(_local_cid.size()));
    header.insert(header.end(), _local_cid.begin(), _local_cid.end());
    
    // Length field
    size_t total_len = pn_len + payload.size() + 16;
    uint8_t len_buf[8];
    size_t len_size = encodeVarInt(total_len, len_buf);
    header.insert(header.end(), len_buf, len_buf + len_size);
    
    // Packet number
    uint64_t pn = _handshake_pn_send++;
    header.push_back(static_cast<uint8_t>(pn >> 24));
    header.push_back(static_cast<uint8_t>(pn >> 16));
    header.push_back(static_cast<uint8_t>(pn >> 8));
    header.push_back(static_cast<uint8_t>(pn));
    
    const uint8_t* key = _is_server ? _hs_key_server : _hs_key_client;
    const uint8_t* iv = _is_server ? _hs_iv_server : _hs_iv_client;
    
    return protectPacket(header, payload, pn, key, iv, false /* use negotiated cipher */);
}

// ============================================================================
// Packet Building: Short Header (1-RTT) Packet
// ============================================================================

std::vector<uint8_t> quic::buildShortHeaderPacket(const std::vector<uint8_t>& payload) {
    std::vector<uint8_t> header;
    
    // First byte: Short header form (0) + Fixed bit (1) + Spin bit (0) + Reserved (00) + Key phase (0) + PN length
    uint8_t first_byte = 0x40;  // 0100 0000
    size_t pn_len = 4;
    first_byte |= (pn_len - 1);
    header.push_back(first_byte);
    
    // DCID (no length field in short header)
    header.insert(header.end(), _remote_cid.begin(), _remote_cid.end());
    
    // Packet number
    uint64_t pn = _app_pn_send++;
    header.push_back(static_cast<uint8_t>(pn >> 24));
    header.push_back(static_cast<uint8_t>(pn >> 16));
    header.push_back(static_cast<uint8_t>(pn >> 8));
    header.push_back(static_cast<uint8_t>(pn));
    
    const uint8_t* key = _is_server ? _app_key_server : _app_key_client;
    const uint8_t* iv = _is_server ? _app_iv_server : _app_iv_client;
    
    return protectPacket(header, payload, pn, key, iv, false /* use negotiated cipher */);
}

// ============================================================================
// Socket Interface: connect (Client-side connection initiation)
// ============================================================================

void quic::connect(const std::string& addr, int port, bool nonblock) {
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    
    // Ensure we're in idle state
    if (_conn_state.load() != ConnectionState::Idle) {
        NetException e;
        e[NetException::Error] << "quic::connect: connection already in progress";
        throw e;
    }
    
    _is_server = false;
    
    // Delegate address resolution, socket creation and UDP connect to the base class
    udp::connect(addr, port, nonblock);
    
    // Generate random DCID for the server (we don't know their CID yet)
    generateConnectionId(_remote_cid, 8);
    _original_dcid = _remote_cid;  // Save for key derivation
    
    // Derive initial keys from the server's (destination) CID
    deriveInitialKeys(_remote_cid, false);
    
    // Generate ECDHE key pair for TLS 1.3 handshake
    generateECDHEKeyPair();
    
    // Build and send ClientHello in Initial packet
    _conn_state.store(ConnectionState::Handshaking);
    
    // Build CRYPTO frame containing ClientHello
    std::vector<uint8_t> client_hello = buildClientHello();
    std::vector<uint8_t> crypto_frame = buildCryptoFrame(client_hello, 0);
    
    // Pad Initial packet to at least 1200 bytes (RFC 9000 requirement)
    // Add PADDING frames if necessary
    size_t min_payload = 1200 - 100;  // Account for header overhead
    while (crypto_frame.size() < min_payload) {
        crypto_frame.push_back(0x00);  // PADDING frame
    }
    
    // Build Initial packet
    std::vector<uint8_t> packet = buildInitialPacket(crypto_frame);
    
    // Apply header protection
    applyHeaderProtection(packet, _initial_hp_client, true);
    
    // Send Initial packet
    {
        buffer send_buf(reinterpret_cast<const char*>(packet.data()), packet.size());
        udp::sendData(send_buf, 0);
    }
    
    // If blocking mode, wait for ServerHello and complete handshake
    if (!nonblock) {
        completeHandshake();
    }
}

// ============================================================================
// TLS 1.3 Handshake: Generate ECDHE Key Pair
// ============================================================================

void quic::generateECDHEKeyPair() {
    // Generate random private key (32 bytes)
    _ecdhe_private.resize(32);
    fillRandomBytes(_ecdhe_private.data(), 32);
    
    // Compute public key using X25519
    _ecdhe_public.resize(32);
    std::vector<uint8_t> priv_vec(_ecdhe_private.begin(), _ecdhe_private.end());
    scalarmult_curve25519_base(_ecdhe_public, priv_vec);
}

// ============================================================================
// TLS 1.3 Handshake: Build ClientHello
// ============================================================================

std::vector<uint8_t> quic::buildClientHello() {
    std::vector<uint8_t> hello;
    
    // Handshake type: ClientHello (0x01)
    hello.push_back(0x01);
    
    // Length placeholder (3 bytes) - will be filled later
    size_t length_pos = hello.size();
    hello.push_back(0x00);
    hello.push_back(0x00);
    hello.push_back(0x00);
    
    // Legacy version: TLS 1.2 (0x0303)
    hello.push_back(0x03);
    hello.push_back(0x03);
    
    // Random (32 bytes)
    _client_random.resize(32);
    fillRandomBytes(_client_random.data(), 32);
    hello.insert(hello.end(), _client_random.begin(), _client_random.end());
    
    // Legacy session ID (empty for QUIC)
    hello.push_back(0x00);
    
    // Cipher suites
    hello.push_back(0x00);
    hello.push_back(0x04);  // 2 cipher suites (4 bytes)
    hello.push_back(0x13);
    hello.push_back(0x01);  // TLS_AES_128_GCM_SHA256
    hello.push_back(0x13);
    hello.push_back(0x02);  // TLS_AES_256_GCM_SHA384
    
    // Legacy compression methods
    hello.push_back(0x01);  // 1 method
    hello.push_back(0x00);  // null compression
    
    // Extensions
    std::vector<uint8_t> extensions;
    
    // Extension: supported_versions (0x002b)
    extensions.push_back(0x00);
    extensions.push_back(0x2b);
    extensions.push_back(0x00);
    extensions.push_back(0x03);  // Length
    extensions.push_back(0x02);  // Versions length
    extensions.push_back(0x03);
    extensions.push_back(0x04);  // TLS 1.3
    
    // Extension: supported_groups (0x000a)
    extensions.push_back(0x00);
    extensions.push_back(0x0a);
    extensions.push_back(0x00);
    extensions.push_back(0x04);  // Length
    extensions.push_back(0x00);
    extensions.push_back(0x02);  // Groups length
    extensions.push_back(0x00);
    extensions.push_back(0x1d);  // x25519
    
    // Extension: signature_algorithms (0x000d)
    extensions.push_back(0x00);
    extensions.push_back(0x0d);
    extensions.push_back(0x00);
    extensions.push_back(0x06);  // Length
    extensions.push_back(0x00);
    extensions.push_back(0x04);  // Algorithms length
    extensions.push_back(0x08);
    extensions.push_back(0x04);  // rsa_pss_rsae_sha256
    extensions.push_back(0x04);
    extensions.push_back(0x03);  // ecdsa_secp256r1_sha256
    
    // Extension: key_share (0x0033)
    extensions.push_back(0x00);
    extensions.push_back(0x33);
    size_t ks_len = 2 + 2 + 2 + 32;  // group + key_len + key
    extensions.push_back(static_cast<uint8_t>(ks_len >> 8));
    extensions.push_back(static_cast<uint8_t>(ks_len));
    extensions.push_back(static_cast<uint8_t>((ks_len - 2) >> 8));
    extensions.push_back(static_cast<uint8_t>(ks_len - 2));
    extensions.push_back(0x00);
    extensions.push_back(0x1d);  // x25519
    extensions.push_back(0x00);
    extensions.push_back(0x20);  // 32 bytes
    extensions.insert(extensions.end(), _ecdhe_public.begin(), _ecdhe_public.end());
    
    // Extension: QUIC transport parameters (0x0039)
    std::vector<uint8_t> transport_params = buildTransportParameters();
    extensions.push_back(0x00);
    extensions.push_back(0x39);
    extensions.push_back(static_cast<uint8_t>(transport_params.size() >> 8));
    extensions.push_back(static_cast<uint8_t>(transport_params.size()));
    extensions.insert(extensions.end(), transport_params.begin(), transport_params.end());
    
    // Extensions length
    hello.push_back(static_cast<uint8_t>(extensions.size() >> 8));
    hello.push_back(static_cast<uint8_t>(extensions.size()));
    hello.insert(hello.end(), extensions.begin(), extensions.end());
    
    // Fill in length
    size_t msg_len = hello.size() - 4;  // Exclude type and length fields
    hello[length_pos] = static_cast<uint8_t>(msg_len >> 16);
    hello[length_pos + 1] = static_cast<uint8_t>(msg_len >> 8);
    hello[length_pos + 2] = static_cast<uint8_t>(msg_len);
    
    // Add to transcript
    _tls_transcript.insert(_tls_transcript.end(), hello.begin(), hello.end());
    
    return hello;
}

// ============================================================================
// QUIC Transport Parameters
// ============================================================================

std::vector<uint8_t> quic::buildTransportParameters() {
    std::vector<uint8_t> params;
    
    
    // original_destination_connection_id (0x00) - REQUIRED for server
    // This is the DCID from the client's first Initial packet
    if (_is_server && !_original_dcid.empty()) {
        params.push_back(0x00);
        uint8_t odcid_len_buf[8];
        size_t odcid_len_size = encodeVarInt(_original_dcid.size(), odcid_len_buf);
        params.insert(params.end(), odcid_len_buf, odcid_len_buf + odcid_len_size);
        params.insert(params.end(), _original_dcid.begin(), _original_dcid.end());
    }
    
    // initial_source_connection_id (0x0f)
    params.push_back(0x0f);
    uint8_t cid_len_buf[8];
    size_t cid_len_size = encodeVarInt(_local_cid.size(), cid_len_buf);
    params.insert(params.end(), cid_len_buf, cid_len_buf + cid_len_size);
    params.insert(params.end(), _local_cid.begin(), _local_cid.end());
    
    // initial_max_data (0x04)
    params.push_back(0x04);
    uint8_t max_data_buf[8];
    size_t max_data_len = encodeVarInt(_max_data_local, max_data_buf);
    uint8_t max_data_len_buf[8];
    size_t max_data_len_size = encodeVarInt(max_data_len, max_data_len_buf);
    params.insert(params.end(), max_data_len_buf, max_data_len_buf + max_data_len_size);
    params.insert(params.end(), max_data_buf, max_data_buf + max_data_len);
    
    // initial_max_stream_data_bidi_local (0x05)
    params.push_back(0x05);
    uint8_t stream_data_buf[8];
    size_t stream_data_len = encodeVarInt(16777216, stream_data_buf);  // 16MB
    uint8_t stream_data_len_buf[8];
    size_t stream_data_len_size = encodeVarInt(stream_data_len, stream_data_len_buf);
    params.insert(params.end(), stream_data_len_buf, stream_data_len_buf + stream_data_len_size);
    params.insert(params.end(), stream_data_buf, stream_data_buf + stream_data_len);
    
    // initial_max_stream_data_bidi_remote (0x06)
    params.push_back(0x06);
    params.insert(params.end(), stream_data_len_buf, stream_data_len_buf + stream_data_len_size);
    params.insert(params.end(), stream_data_buf, stream_data_buf + stream_data_len);
    
    // initial_max_stream_data_uni (0x07)
    params.push_back(0x07);
    params.insert(params.end(), stream_data_len_buf, stream_data_len_buf + stream_data_len_size);
    params.insert(params.end(), stream_data_buf, stream_data_buf + stream_data_len);
    
    // initial_max_streams_bidi (0x08)
    params.push_back(0x08);
    uint8_t max_streams_buf[8];
    size_t max_streams_len = encodeVarInt(100, max_streams_buf);
    uint8_t max_streams_len_buf[8];
    size_t max_streams_len_size = encodeVarInt(max_streams_len, max_streams_len_buf);
    params.insert(params.end(), max_streams_len_buf, max_streams_len_buf + max_streams_len_size);
    params.insert(params.end(), max_streams_buf, max_streams_buf + max_streams_len);
    
    // initial_max_streams_uni (0x09)
    params.push_back(0x09);
    params.insert(params.end(), max_streams_len_buf, max_streams_len_buf + max_streams_len_size);
    params.insert(params.end(), max_streams_buf, max_streams_buf + max_streams_len);
    
    // max_idle_timeout (0x01)
    params.push_back(0x01);
    uint8_t timeout_buf[8];
    size_t timeout_len = encodeVarInt(_max_idle_timeout, timeout_buf);
    uint8_t timeout_len_buf[8];
    size_t timeout_len_size = encodeVarInt(timeout_len, timeout_len_buf);
    params.insert(params.end(), timeout_len_buf, timeout_len_buf + timeout_len_size);
    params.insert(params.end(), timeout_buf, timeout_buf + timeout_len);

    // max_udp_payload_size (0x03) — advertise 1472 (Ethernet-safe)
    params.push_back(0x03);
    uint8_t mups_buf[8];
    size_t mups_len = encodeVarInt(1472, mups_buf);
    uint8_t mups_len_buf[8];
    size_t mups_len_size = encodeVarInt(mups_len, mups_len_buf);
    params.insert(params.end(), mups_len_buf, mups_len_buf + mups_len_size);
    params.insert(params.end(), mups_buf, mups_buf + mups_len);

    return params;
}

// ============================================================================
// Frame Building: CRYPTO Frame
// ============================================================================

std::vector<uint8_t> quic::buildCryptoFrame(const std::vector<uint8_t>& data, uint64_t offset) {
    std::vector<uint8_t> frame;
    
    // Frame type: CRYPTO (0x06)
    frame.push_back(0x06);
    
    // Offset (variable-length integer)
    uint8_t offset_buf[8];
    size_t offset_len = encodeVarInt(offset, offset_buf);
    frame.insert(frame.end(), offset_buf, offset_buf + offset_len);
    
    // Length (variable-length integer)
    uint8_t len_buf[8];
    size_t len_len = encodeVarInt(data.size(), len_buf);
    frame.insert(frame.end(), len_buf, len_buf + len_len);
    
    // Data
    frame.insert(frame.end(), data.begin(), data.end());
    
    return frame;
}

// ============================================================================
// Blocking Handshake Completion
// ============================================================================

void quic::completeHandshake() {
    // Receive and process packets until handshake is complete or timeout
    const auto start = std::chrono::steady_clock::now();
    const auto timeout = std::chrono::milliseconds(HANDSHAKE_TIMEOUT_MS);
    int recv_attempts = 0;
    int recv_got = 0;

    while (!_handshake_complete) {
        // Bail out if the connection was closed while we were waiting
        if (_conn_state.load() == ConnectionState::Closed ||
            _conn_state.load() == ConnectionState::Draining) {
            NetException e;
            e[NetException::Error] << "quic::completeHandshake: connection closed";
            throw e;
        }

        try {
            buffer recv_buf_wrapper(65535);
            recv_attempts++;
            size_t recv_len = udp::recvData(recv_buf_wrapper, MSG_DONTWAIT);
            if (recv_len > 0) {
                recv_got++;
                const uint8_t* pkt = reinterpret_cast<const uint8_t*>(recv_buf_wrapper.data.buf);
                processIncomingPacket(pkt, recv_len);
            }
        } catch (NetException& e) {
            if (e.getErrorType() != NetException::Note) {
                throw;
            }
        }

        if (_handshake_complete) {
            break;
        }

        if (std::chrono::steady_clock::now() - start > timeout) {
            NetException e;
            e[NetException::Error] << "quic::completeHandshake: handshake timed out";
            throw e;
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(5));
    }

    _conn_state.store(ConnectionState::Connected);
}

// ============================================================================
// Socket Interface: recvData (Receive application data)
// ============================================================================

size_t quic::recvData(buffer& data, int flags) {
    std::vector<PendingDispatch> dispatches;
    size_t result = 0;

    {   // Scoped lock — released before invoking stream callbacks
        std::lock_guard<std::recursive_mutex> lock(quic_mtx());

        // Check connection state
        if (_conn_state.load() == ConnectionState::Closed) {
            NetException e;
            e[NetException::Error] << "quic::recvData: connection closed";
            throw e;
        }

        if (_conn_state.load() != ConnectionState::Connected && !_handshake_complete) {
            NetException e;
            e[NetException::Error] << "quic::recvData: connection not established";
            throw e;
        }

        // First, check if we have buffered data from any stream
        uint64_t default_stream = _is_server ? 1 : 0;

        auto check_stream = [&](auto& stream_ref) -> bool {
            if (stream_ref.recv_offset >= stream_ref.recv_contiguous) {
                return false;  // No new contiguous data
            }
            size_t available = static_cast<size_t>(stream_ref.recv_contiguous - stream_ref.recv_offset);
            size_t to_copy = std::min(data.size, available);
            std::memcpy(data.data.buf, stream_ref.recv_buffer.data() + stream_ref.recv_offset, to_copy);
            stream_ref.recv_offset += to_copy;
            data.size = to_copy;
            result = to_copy;
            return true;
        };

        auto it = _streams.find(default_stream);
        if (it != _streams.end() && it->second.recv_offset < it->second.recv_contiguous) {
            check_stream(it->second);
        } else {
            // No buffered data, try to receive from network
            int recv_flags = (flags & MSG_DONTWAIT) ? MSG_DONTWAIT : 0;
            buffer recv_buf_wrapper(65535);
            size_t recv_len;
            try {
                recv_len = udp::recvData(recv_buf_wrapper, recv_flags);
            } catch (NetException&) {
                throw;
            }

            if (recv_len == 0) {
                data.size = 0;
                result = 0;
            } else {
                processIncomingPacket(reinterpret_cast<const uint8_t*>(recv_buf_wrapper.data.buf), recv_len);

                // After processing, check for buffered stream data
                bool found = false;
                it = _streams.find(default_stream);
                if (it != _streams.end() && it->second.recv_offset < it->second.recv_contiguous) {
                    check_stream(it->second);
                    found = true;
                }

                if (!found) {
                    // Check all streams
                    for (auto& [sid, stream] : _streams) {
                        if (stream.recv_offset < stream.recv_contiguous) {
                            check_stream(stream);
                            found = true;
                            break;
                        }
                    }
                }

                if (!found) {
                    data.size = 0;
                    result = 0;
                }
            }
        }

        // Collect pending dispatches before releasing lock
        dispatches = std::move(_pending_dispatches);
        _pending_dispatches.clear();

    }   // mutex released

    // Dispatch stream callbacks inline (mutex already released).
    if (!dispatches.empty() && _stream_callback) {
        for (auto& pd : dispatches) {
            _stream_callback(this, pd.stream_id, pd.data, pd.fin);
        }
    }

    return result;
}

// ============================================================================
// Pump Network: read and process QUIC packets without consuming stream data.
// For use by HTTP/3 clients that read specific streams via recvStreamData().
// ============================================================================

void quic::pumpNetwork(int flags) {
    int recv_flags = (flags & MSG_DONTWAIT) ? MSG_DONTWAIT : 0;
    _pump_buf.size = 65535;  // Reset capacity — udp::recvData shrinks it to bytes-received
    size_t recv_len = udp::recvData(_pump_buf, recv_flags);
    if (recv_len > 0) {
        std::vector<PendingDispatch> dispatches;
        {
            std::lock_guard<std::recursive_mutex> lock(quic_mtx());
            processIncomingPacket(reinterpret_cast<const uint8_t*>(_pump_buf.data.buf), recv_len);

            // Flush pending flow control frames so the peer doesn't stall
            if (_handshake_complete && _conn_state.load() == ConnectionState::Connected) {
                for (auto& [sid, limit] : _pending_max_stream_data) {
                    sendMaxStreamData(sid, limit);
                }
                _pending_max_stream_data.clear();
                if (_pending_max_data) {
                    sendMaxData(_pending_max_data_value);
                    _pending_max_data = false;
                }
            }

            // Collect pending dispatches before releasing lock
            dispatches = std::move(_pending_dispatches);
            _pending_dispatches.clear();
        }

        // Dispatch stream callbacks outside the lock
        if (!dispatches.empty() && _stream_callback) {
            for (auto& pd : dispatches) {
                _stream_callback(this, pd.stream_id, pd.data, pd.fin);
            }
        }
    }
}

// ============================================================================
// Stream Data Receive (for specific stream)
// ============================================================================

size_t quic::recvStreamData(uint64_t stream_id, uint8_t* data, size_t max_len) {
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    
    auto it = _streams.find(stream_id);
    if (it == _streams.end()) {
        return 0;  // Stream doesn't exist
    }
    
    Stream& stream = it->second;
    // Only return contiguous data that hasn't been consumed yet.
    // recv_buffer is indexed by absolute stream offset; recv_offset
    // tracks how far the application has consumed.
    if (stream.recv_offset >= stream.recv_contiguous) {
        return 0;  // No new contiguous data available
    }
    
    // Guard: buffer may have been moved out by pending dispatch
    if (stream.recv_buffer.empty()) {
        return 0;
    }
    
    size_t available = static_cast<size_t>(stream.recv_contiguous - stream.recv_offset);
    size_t to_copy = std::min(max_len, available);
    std::memcpy(data, stream.recv_buffer.data() + stream.recv_offset, to_copy);
    stream.recv_offset += to_copy;
    
    // Flow control: after consuming data, check if we should send
    // MAX_STREAM_DATA to open the receive window for the peer.
    // Trigger when >50% of the advertised window has been consumed.
    uint64_t consumed_from_committed = stream.recv_offset;
    if (consumed_from_committed > stream.max_data_local_committed / 2) {
        uint64_t new_limit = stream.recv_offset + stream.max_data_local;
        if (new_limit > stream.max_data_local_committed) {
            stream.max_data_local_committed = new_limit;
            _pending_max_stream_data[stream_id] = new_limit;
        }
    }
    
    // Connection-level flow control: update MAX_DATA after consumption
    if (_data_recv > _max_data_local_committed / 2) {
        uint64_t new_limit = _data_recv + _max_data_local;
        if (new_limit > _max_data_local_committed) {
            _max_data_local_committed = new_limit;
            _pending_max_data_value = new_limit;
            _pending_max_data = true;
        }
    }
    
    return to_copy;
}

// ============================================================================
// Check if stream has data
// ============================================================================

bool quic::hasStreamData(uint64_t stream_id) const {
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    
    auto it = _streams.find(stream_id);
    if (it == _streams.end()) {
        return false;
    }
    return it->second.recv_offset < it->second.recv_contiguous;
}

bool quic::isStreamComplete(uint64_t stream_id) const {
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());

    auto it = _streams.find(stream_id);
    if (it == _streams.end()) {
        return false;
    }
    return it->second.recv_complete;
}

// ============================================================================
// Frame Processing: Process individual frames
// ============================================================================

void quic::processFrame(const uint8_t* data, size_t len, size_t& offset) {
    if (offset >= len) return;
    
    // Read frame type
    size_t type_bytes;
    uint64_t frame_type = decodeVarInt(&data[offset], type_bytes);
    offset += type_bytes;
    if (frame_type != 0x00) {  // Don't log PADDING
        QUIC_DBG("processFrame: type=0x%02lx offset=%zu len=%zu enc=%d",
                 (unsigned long)frame_type, offset, len, (int)_current_enc_level);
    }

    if (!_is_server) {
        (void)frame_type;
    }
    
    switch (frame_type) {
        case 0x00:  // PADDING
            // Just skip - padding frames are 1 byte each
            break;
            
        case 0x01:  // PING
            // No payload, just acknowledge
            break;
            
        case 0x02:  // ACK
        case 0x03:  // ACK_ECN
            processAckFrame(data, len, offset);
            break;
            
        case 0x06:  // CRYPTO
            processCryptoFrame(data, len, offset);
            break;
            
        case 0x04:  // RESET_STREAM
            {
                size_t bytes;
                uint64_t sid = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                uint64_t app_error = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                uint64_t final_size = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                auto it = _streams.find(sid);
                if (it != _streams.end()) {
                    it->second.reset = true;
                    it->second.recv_fin = true;
                    it->second.recv_fin_offset = final_size;
                }
                (void)app_error;
            }
            break;
            
        case 0x05:  // STOP_SENDING
            {
                size_t bytes;
                uint64_t sid = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                uint64_t app_error = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                auto it = _streams.find(sid);
                if (it != _streams.end()) {
                    it->second.send_fin = true;
                }
                (void)app_error;
            }
            break;
            
        case 0x08: case 0x09: case 0x0a: case 0x0b:
        case 0x0c: case 0x0d: case 0x0e: case 0x0f:  // STREAM
            processStreamFrame(data, len, offset);
            break;
            
        case 0x10:  // MAX_DATA
            {
                size_t bytes;
                _max_data_remote = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                QUIC_DBG("processFrame: MAX_DATA new_limit=%lu", (unsigned long)_max_data_remote);
            }
            break;

        case 0x11:  // MAX_STREAM_DATA
            {
                size_t bytes;
                uint64_t sid = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                uint64_t max_sd = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                auto it = _streams.find(sid);
                if (it != _streams.end()) {
                    if (max_sd > it->second.max_data_remote) {
                        QUIC_DBG("processFrame: MAX_STREAM_DATA sid=%lu new_limit=%lu (was %lu)",
                                 (unsigned long)sid, (unsigned long)max_sd,
                                 (unsigned long)it->second.max_data_remote);
                        it->second.max_data_remote = max_sd;
                    }
                }
            }
            break;

        case 0x12:  // MAX_STREAMS (bidi)
            {
                size_t bytes;
                uint64_t max_streams = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                if (max_streams > _max_streams_bidi_remote)
                    _max_streams_bidi_remote = max_streams;
            }
            break;
        case 0x13:  // MAX_STREAMS (uni)
            {
                size_t bytes;
                uint64_t max_streams = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                if (max_streams > _max_streams_uni_remote)
                    _max_streams_uni_remote = max_streams;
            }
            break;

        case 0x14:  // DATA_BLOCKED
            {
                size_t bytes;
                uint64_t blocked_at = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                // Peer is blocked — send updated MAX_DATA using the
                // current committed limit (not the initial window).
                // Recalculate in case earlier updates were lost.
                uint64_t new_conn_limit = _data_recv + _max_data_local;
                if (new_conn_limit <= _max_data_local_committed)
                    new_conn_limit = _max_data_local_committed + _max_data_local;
                _max_data_local_committed = new_conn_limit;
                sendMaxData(new_conn_limit);
                (void)blocked_at;
            }
            break;

        case 0x15:  // STREAM_DATA_BLOCKED
            {
                size_t bytes;
                uint64_t sid = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                uint64_t blocked_at = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                // Peer is blocked on this stream — send updated
                // MAX_STREAM_DATA using the current committed limit.
                // Recalculate in case earlier updates were lost.
                auto it = _streams.find(sid);
                if (it != _streams.end()) {
                    uint64_t consumed = it->second.recv_buffer.size();
                    uint64_t new_limit = consumed + it->second.max_data_local;
                    if (new_limit <= it->second.max_data_local_committed)
                        new_limit = it->second.max_data_local_committed + it->second.max_data_local;
                    it->second.max_data_local_committed = new_limit;
                    sendMaxStreamData(sid, new_limit);
                }
                (void)blocked_at;
            }
            break;
            
        case 0x1c:  // CONNECTION_CLOSE
        case 0x1d:  // CONNECTION_CLOSE (application)
            {
                // Error code
                size_t bytes;
                uint64_t error_code = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                
                uint64_t triggering_frame = 0;
                if (frame_type == 0x1c) {
                    // Frame type that triggered the error
                    triggering_frame = decodeVarInt(&data[offset], bytes);
                    offset += bytes;
                }
                
                // Reason phrase length
                uint64_t reason_len = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                
                // Read reason phrase for logging
                std::string reason;
                if (reason_len > 0 && offset + reason_len <= len) {
                    reason.assign(reinterpret_cast<const char*>(&data[offset]), reason_len);
                }
                
                // Skip reason phrase
                offset += reason_len;
                
                QUIC_DBG("CONNECTION_CLOSE: error=0x%lx frame_type=0x%lx trigger_frame=0x%lx reason='%s'",
                         (unsigned long)error_code, (unsigned long)frame_type,
                         (unsigned long)triggering_frame, reason.c_str());
                
                // Connection is closing
                _conn_state.store(ConnectionState::Draining);
            }
            break;
        
        case 0x07:  // NEW_TOKEN
            {
                size_t bytes;
                uint64_t token_len = decodeVarInt(&data[offset], bytes);
                offset += bytes;
                offset += token_len;  // Skip token data
            }
            break;

        case 0x16:  // STREAMS_BLOCKED (bidi)
            {
                size_t bytes;
                decodeVarInt(&data[offset], bytes);  // maximum_streams
                offset += bytes;
            }
            break;

        case 0x17:  // STREAMS_BLOCKED (uni)
            {
                size_t bytes;
                decodeVarInt(&data[offset], bytes);  // maximum_streams
                offset += bytes;
            }
            break;

        case 0x18:  // NEW_CONNECTION_ID
            {
                size_t bytes;
                decodeVarInt(&data[offset], bytes);  // sequence_number
                offset += bytes;
                decodeVarInt(&data[offset], bytes);  // retire_prior_to
                offset += bytes;
                if (offset < len) {
                    uint8_t cid_len = data[offset++];  // connection_id length (1 byte)
                    offset += cid_len;                  // connection_id
                    offset += 16;                       // stateless_reset_token
                }
            }
            break;

        case 0x19:  // RETIRE_CONNECTION_ID
            {
                size_t bytes;
                decodeVarInt(&data[offset], bytes);  // sequence_number
                offset += bytes;
            }
            break;

        case 0x1a:  // PATH_CHALLENGE
            {
                if (offset + 8 <= len) {
                    // Respond with PATH_RESPONSE echoing the same 8 bytes
                    std::vector<uint8_t> response;
                    response.push_back(0x1b);  // PATH_RESPONSE frame type
                    response.insert(response.end(), &data[offset], &data[offset + 8]);
                    std::vector<uint8_t> packet = buildShortHeaderPacket(response);
                    const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
                    applyHeaderProtection(packet, hp_key);
                    sendPacket(packet.data(), packet.size());
                }
                offset += 8;
            }
            break;

        case 0x1b:  // PATH_RESPONSE
            {
                offset += 8;  // 8 bytes of data
            }
            break;
            
        case 0x1e:  // HANDSHAKE_DONE
            _handshake_complete = true;
            _conn_state.store(ConnectionState::Connected);
            break;
            
        default:
            // Unknown frame type - consume rest of payload to prevent
            // re-parsing frame body bytes as a new frame type
            QUIC_DBG("processFrame: UNKNOWN frame type=0x%02lx, consuming %zu remaining bytes",
                     (unsigned long)frame_type, len - offset);
            offset = len;
            return;
    }
}

// ============================================================================
// Frame Processing: CRYPTO Frame
// ============================================================================

void quic::processCryptoFrame(const uint8_t* data, size_t len, size_t& offset) {
    if (offset >= len) return;
    
    // Offset field
    size_t bytes;
    uint64_t crypto_offset = decodeVarInt(&data[offset], bytes);
    offset += bytes;
    
    // Length field
    uint64_t crypto_len = decodeVarInt(&data[offset], bytes);
    offset += bytes;
    
    
    if (offset + crypto_len > len) return;
    
    // Get references to the appropriate reassembly structures
    std::vector<uint8_t>* crypto_buffer = nullptr;
    std::vector<CryptoRange>* crypto_ranges = nullptr;
    uint64_t* crypto_next_offset = nullptr;
    
    switch (_current_enc_level) {
        case EncryptionLevel::Initial:
            crypto_buffer = &_crypto_recv_initial;
            crypto_ranges = &_crypto_ranges_initial;
            crypto_next_offset = &_crypto_offset_initial;
            break;
        case EncryptionLevel::Handshake:
            crypto_buffer = &_crypto_recv_handshake;
            crypto_ranges = &_crypto_ranges_handshake;
            crypto_next_offset = &_crypto_offset_handshake;
            break;
        case EncryptionLevel::Application:
            crypto_buffer = &_crypto_recv_app;
            crypto_ranges = &_crypto_ranges_app;
            crypto_next_offset = &_crypto_offset_app;
            break;
    }
    
    if (!crypto_buffer || !crypto_ranges || !crypto_next_offset) return;
    
    // Check if this data is a duplicate or overlapping with existing data
    uint64_t data_end = crypto_offset + crypto_len;
    if (data_end <= *crypto_next_offset) {
        offset += crypto_len;
        return;
    }
    
    // Create a range for this data
    CryptoRange range;
    range.offset = crypto_offset;
    range.length = crypto_len;
    range.data.assign(&data[offset], &data[offset + crypto_len]);
    
    // Insert the range into the list, merging overlapping ranges
    std::vector<CryptoRange> new_ranges;
    
    for (auto& existing : *crypto_ranges) {
        // Check if ranges overlap or are adjacent
        if (range.offset <= existing.offset + existing.length &&
            range.offset + range.length >= existing.offset) {
            // Merge ranges
            uint64_t merge_start = std::min(range.offset, existing.offset);
            uint64_t merge_end = std::max(range.offset + range.length, 
                                         existing.offset + existing.length);
            
            // Build merged data
            std::vector<uint8_t> merged_data;
            if (merge_start == range.offset) {
                merged_data = range.data;
                if (existing.offset + existing.length > range.offset + range.length) {
                    // Append tail from existing
                    size_t tail_start = range.offset + range.length - existing.offset;
                    merged_data.insert(merged_data.end(),
                                      existing.data.begin() + tail_start,
                                      existing.data.end());
                }
            } else {
                merged_data = existing.data;
                if (range.offset + range.length > existing.offset + existing.length) {
                    // Append tail from range
                    size_t tail_start = existing.offset + existing.length - range.offset;
                    merged_data.insert(merged_data.end(),
                                      range.data.begin() + tail_start,
                                      range.data.end());
                }
            }
            
            range.offset = merge_start;
            range.length = merge_end - merge_start;
            range.data = merged_data;
        } else {
            new_ranges.push_back(existing);
        }
    }
    new_ranges.push_back(range);
    *crypto_ranges = new_ranges;
    
    // Check if we can assemble contiguous data starting from next_offset
    // First, sort ranges by offset to handle out-of-order arrivals
    std::sort(crypto_ranges->begin(), crypto_ranges->end(),
              [](const CryptoRange& a, const CryptoRange& b) {
                  return a.offset < b.offset;
              });
    
    uint64_t current = *crypto_next_offset;
    for (auto& r : *crypto_ranges) {
        if (r.offset == current) {
            // This range fills the gap, extend buffer
            crypto_buffer->insert(crypto_buffer->end(), r.data.begin(), r.data.end());
            current = r.offset + r.length;
        } else if (r.offset > current) {
            // Gap exists
            break;
        }
    }
    *crypto_next_offset = current;
    
    // Remove assembled ranges
    uint64_t next_offset = *crypto_next_offset;
    crypto_ranges->erase(
        std::remove_if(crypto_ranges->begin(), crypto_ranges->end(),
                       [next_offset](const CryptoRange& r) {
                           return r.offset + r.length <= next_offset;
                       }),
        crypto_ranges->end()
    );
    
    offset += crypto_len;
    
    // Process TLS messages in crypto buffer if we have new contiguous data
    processTLSMessages();
}

// ============================================================================
// Frame Processing: STREAM Frame
// ============================================================================

void quic::processStreamFrame(const uint8_t* data, size_t len, size_t& offset) {
    if (offset >= len) return;
    
    // The frame type encodes flags in bits 0-2
    uint8_t frame_type = data[offset - 1];  // Already consumed type byte
    bool has_offset = (frame_type & 0x04) != 0;
    bool has_length = (frame_type & 0x02) != 0;
    bool has_fin = (frame_type & 0x01) != 0;
    
    // Stream ID
    size_t bytes;
    uint64_t stream_id = decodeVarInt(&data[offset], bytes);
    offset += bytes;
    
    // Offset (if present)
    uint64_t stream_offset = 0;
    if (has_offset) {
        stream_offset = decodeVarInt(&data[offset], bytes);
        offset += bytes;
    }
    
    // Length (if present)
    uint64_t stream_len;
    if (has_length) {
        stream_len = decodeVarInt(&data[offset], bytes);
        offset += bytes;
    } else {
        stream_len = len - offset;  // Rest of frame is data
    }
    QUIC_DBG("processStreamFrame: sid=%lu off=%lu len=%lu fin=%d ftype=0x%02x",
             (unsigned long)stream_id, (unsigned long)stream_offset,
             (unsigned long)stream_len, (int)has_fin, frame_type);
    
    
    if (offset + stream_len > len) return;
    
    // Get or create stream
    auto& stream = _streams[stream_id];
    stream.stream_id = stream_id;
    
    // Track peer-initiated stream count for MAX_STREAMS enforcement
    // Peer-initiated streams: client-initiated if we're server, server-initiated if we're client
    bool peer_initiated = (_is_server) ? ((stream_id & 0x01) == 0) : ((stream_id & 0x01) == 1);
    if (peer_initiated && stream.recv_buffer.empty() && stream.recv_offset == 0) {
        // First data on a new peer-initiated stream
        bool is_bidi = (stream_id % 4 == 0) || (stream_id % 4 == 1);
        if (is_bidi) {
            _peer_streams_bidi_opened++;
            if (_peer_streams_bidi_opened > _max_streams_bidi_local) {
                // STREAM_LIMIT_ERROR: peer exceeded our MAX_STREAMS
                std::vector<uint8_t> close_frame = buildConnectionCloseFrame(0x04, "stream limit exceeded");
                std::vector<uint8_t> packet = buildShortHeaderPacket(close_frame);
                const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
                applyHeaderProtection(packet, hp_key);
                sendPacket(packet.data(), packet.size());
                _conn_state.store(ConnectionState::Draining);
                offset += stream_len;
                return;
            }
            // Set per-stream limits for peer-initiated bidi streams
            stream.max_data_remote = _is_server
                ? _peer_initial_max_stream_data_bidi_local
                : _peer_initial_max_stream_data_bidi_remote;
        } else {
            _peer_streams_uni_opened++;
            if (_peer_streams_uni_opened > _max_streams_uni_local) {
                std::vector<uint8_t> close_frame = buildConnectionCloseFrame(0x04, "stream limit exceeded");
                std::vector<uint8_t> packet = buildShortHeaderPacket(close_frame);
                const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
                applyHeaderProtection(packet, hp_key);
                sendPacket(packet.data(), packet.size());
                _conn_state.store(ConnectionState::Draining);
                offset += stream_len;
                return;
            }
            stream.max_data_remote = _peer_initial_max_stream_data_uni;
        }
    }
    
    // Enforce receive-side flow control: peer must not exceed our advertised limits
    if (stream_len > 0) {
        uint64_t end_offset = stream_offset + stream_len;
        
        // Stream-level check
        if (end_offset > stream.max_data_local_committed) {
            // FLOW_CONTROL_ERROR (0x03): peer exceeded stream flow control limit
            std::vector<uint8_t> close_frame = buildConnectionCloseFrame(0x03, "stream flow control exceeded");
            std::vector<uint8_t> packet = buildShortHeaderPacket(close_frame);
            const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
            applyHeaderProtection(packet, hp_key);
            sendPacket(packet.data(), packet.size());
            _conn_state.store(ConnectionState::Draining);
            offset += stream_len;
            return;
        }
        
        // Connection-level check
        if (_data_recv + stream_len > _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);
            const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
            applyHeaderProtection(packet, hp_key);
            sendPacket(packet.data(), packet.size());
            _conn_state.store(ConnectionState::Draining);
            offset += stream_len;
            return;
        }
    }
    
    // Store data in stream buffer with proper out-of-order reassembly
    if (stream_len > 0) {
        uint64_t end_offset = stream_offset + stream_len;
        
        // Grow buffer if needed
        if (end_offset > stream.recv_buffer.size()) {
            stream.recv_buffer.resize(end_offset, 0);
        }
        
        // Copy data into the correct position (handles in-order,
        // out-of-order, and gap-fill cases)
        std::memcpy(&stream.recv_buffer[stream_offset],
                     &data[offset], stream_len);
        
        // Track received range — merge with adjacent/overlapping ranges
        auto new_range = std::make_pair(stream_offset, end_offset);
        auto it = stream.recv_ranges.insert(new_range).first;
        
        // Merge with predecessor(s) if overlapping/adjacent
        while (it != stream.recv_ranges.begin()) {
            auto prev = std::prev(it);
            if (prev->second >= it->first) {
                auto merged = std::make_pair(prev->first,
                                              std::max(prev->second, it->second));
                stream.recv_ranges.erase(prev);
                it = stream.recv_ranges.erase(it);
                it = stream.recv_ranges.insert(merged).first;
            } else {
                break;
            }
        }
        // Merge with successor(s) if overlapping/adjacent
        while (true) {
            auto next = std::next(it);
            if (next == stream.recv_ranges.end()) break;
            if (it->second >= next->first) {
                auto merged = std::make_pair(it->first,
                                              std::max(it->second, next->second));
                stream.recv_ranges.erase(next);
                it = stream.recv_ranges.erase(it);
                it = stream.recv_ranges.insert(merged).first;
            } else {
                break;
            }
        }
        
        // Update contiguous byte count
        if (!stream.recv_ranges.empty() && stream.recv_ranges.begin()->first == 0) {
            stream.recv_contiguous = stream.recv_ranges.begin()->second;
        }
    }
    
    offset += stream_len;
    
    // Update connection-level receive counter
    _data_recv += stream_len;
    
    // Flow control: defer MAX_STREAM_DATA when >50% of window consumed
    uint64_t consumed = stream.recv_buffer.size();
    if (consumed > stream.max_data_local_committed / 2) {
        uint64_t new_limit = consumed + stream.max_data_local;
        stream.max_data_local_committed = new_limit;
        _pending_max_stream_data[stream_id] = new_limit;
    }
    
    // Flow control: defer MAX_DATA when >50% of connection window consumed
    if (_data_recv > _max_data_local_committed / 2) {
        uint64_t new_limit = _data_recv + _max_data_local;
        _max_data_local_committed = new_limit;
        _pending_max_data_value = new_limit;
        _pending_max_data = true;
    }
    
    if (has_fin) {
        stream.recv_fin = true;
        stream.recv_fin_offset = stream_offset + stream_len;
    }
    
    // Check if stream is fully received: FIN seen + all data contiguous from 0
    if (stream.recv_fin && !stream.recv_complete &&
        stream.recv_contiguous >= stream.recv_fin_offset) {
        stream.recv_complete = true;
    }
    
    // Queue stream dispatch — will be invoked OUTSIDE _quic_mutex
    // so that long-running callbacks (e.g. import) don't block PING ACKs
    // and don't deadlock when the callback calls sendStreamData/flush_out.
    // ALL stream types are deferred via _pending_dispatches.
    if (_is_server && _stream_callback) {
        bool is_bidi_request = (stream_id % 4 == 0);
        if (is_bidi_request && stream.recv_complete && !stream.recv_dispatched) {
            stream.recv_dispatched = true;
            _pending_dispatches.push_back({stream_id,
                                           std::move(stream.recv_buffer),
                                           true});
            // Free memory — data has been moved to pending dispatch
            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)
            stream.recv_dispatched = true;
            _pending_dispatches.push_back({stream_id,
                                           std::vector<uint8_t>(stream.recv_buffer),
                                           true});
        }
    }
}

// ============================================================================
// Frame Processing: ACK Frame
// ============================================================================

void quic::processAckFrame(const uint8_t* data, size_t len, size_t& offset) {
    if (offset >= len) return;
    
    size_t bytes;
    
    // Largest Acknowledged
    uint64_t largest_ack = decodeVarInt(&data[offset], bytes);
    offset += bytes;
    
    // ACK Delay
    uint64_t ack_delay = decodeVarInt(&data[offset], bytes);
    offset += bytes;
    
    // ACK Range Count
    uint64_t ack_range_count = decodeVarInt(&data[offset], bytes);
    offset += bytes;
    
    // First ACK Range
    uint64_t first_ack_range = decodeVarInt(&data[offset], bytes);
    offset += bytes;

    // Collect all ACKed ranges: [lo, hi] pairs
    struct AckRange { uint64_t lo; uint64_t hi; };
    std::vector<AckRange> acked_ranges;

    // First range
    uint64_t hi = largest_ack;
    uint64_t lo = (largest_ack >= first_ack_range) ? largest_ack - first_ack_range : 0;
    acked_ranges.push_back({lo, hi});

    uint64_t prev_smallest = lo;

    // Additional ranges
    for (uint64_t i = 0; i < ack_range_count; ++i) {
        uint64_t gap = decodeVarInt(&data[offset], bytes);
        offset += bytes;
        uint64_t ack_range = decodeVarInt(&data[offset], bytes);
        offset += bytes;

        // RFC 9000: prev_smallest - gap - 2 is the top of the next range
        if (prev_smallest < gap + 2) break; // malformed
        hi = prev_smallest - gap - 2;
        lo = (hi >= ack_range) ? hi - ack_range : 0;
        acked_ranges.push_back({lo, hi});
        prev_smallest = lo;
    }

    auto now = std::chrono::steady_clock::now();

    // Remove acknowledged packets from _sent_packets and update RTT
    for (auto& ar : acked_ranges) {
        for (auto it = _sent_packets.lower_bound(ar.lo);
             it != _sent_packets.end() && it->first <= ar.hi; ) {
            // Update RTT from the largest newly-acked packet
            if (it->first == largest_ack && it->second.ack_eliciting) {
                double rtt_sample = std::chrono::duration<double>(now - it->second.sent_time).count();
                double adj_delay = static_cast<double>(ack_delay) / 1000000.0;
                if (rtt_sample > adj_delay) rtt_sample -= adj_delay;
                if (!_rtt_initialized) {
                    _srtt = rtt_sample;
                    _rttvar = rtt_sample / 2.0;
                    _rtt_initialized = true;
                } else {
                    _rttvar = 0.75 * _rttvar + 0.25 * std::abs(_srtt - rtt_sample);
                    _srtt = 0.875 * _srtt + 0.125 * rtt_sample;
                }
                _rto = _srtt + std::max(4.0 * _rttvar, 0.001);
                if (_rto < 0.1) _rto = 0.1;
                if (_rto > 10.0) _rto = 10.0;
            }
            it = _sent_packets.erase(it);
        }
    }

    if (_largest_acked_pn == UINT64_MAX || largest_ack > _largest_acked_pn) {
        _largest_acked_pn = largest_ack;
    }

    // Trigger loss detection after processing ACK
    checkLossAndRetransmit();
}

// ============================================================================
// Retransmission: Record a sent packet for tracking
// ============================================================================

void quic::recordSentPacket(uint64_t pn, uint64_t stream_id, uint64_t stream_offset,
                            const uint8_t* data, size_t len, bool fin) {
    SentPacket sp;
    sp.pn = pn;
    sp.sent_time = std::chrono::steady_clock::now();
    sp.stream_id = stream_id;
    sp.stream_offset = stream_offset;
    sp.data_length = len;
    sp.fin = fin;
    sp.ack_eliciting = true;
    sp.stream_data.assign(data, data + len);
    _sent_packets[pn] = std::move(sp);
}

// ============================================================================
// Retransmission: Loss detection and retransmit
// ============================================================================

void quic::checkLossAndRetransmit() {
    if (_sent_packets.empty() || _largest_acked_pn == UINT64_MAX) return;

    auto now = std::chrono::steady_clock::now();
    // Time-based loss threshold (RFC 9002 §6.1.2)
    double loss_delay = TIME_THRESHOLD * std::max(_srtt, _rttvar);
    if (loss_delay < 0.001) loss_delay = 0.001; // min 1ms
    auto loss_time = std::chrono::duration<double>(loss_delay);

    std::vector<SentPacket> lost;

    for (auto it = _sent_packets.begin(); it != _sent_packets.end(); ) {
        bool is_lost = false;

        // Packet-number-based detection: PN is PACKET_THRESHOLD below largest acked
        if (it->first + PACKET_THRESHOLD <= _largest_acked_pn) {
            is_lost = true;
        }

        // Time-based detection: packet was sent long enough ago
        if (!is_lost) {
            auto elapsed = std::chrono::duration<double>(now - it->second.sent_time);
            if (elapsed.count() >= loss_delay) {
                // Only declare lost if this pn < largest_acked (peer has received later packets)
                if (it->first < _largest_acked_pn) {
                    is_lost = true;
                }
            }
        }

        if (is_lost) {
            lost.push_back(std::move(it->second));
            it = _sent_packets.erase(it);
        } else {
            ++it;
        }
    }

    // Retransmit lost stream data with new packet numbers
    for (auto& sp : lost) {
        if (sp.data_length == 0 && !sp.fin) continue;

        // Build STREAM frame for the lost data
        uint8_t frame_type = 0x08 | 0x02; // LEN bit
        if (sp.stream_offset > 0) frame_type |= 0x04; // OFF bit
        if (sp.fin) frame_type |= 0x01;

        std::vector<uint8_t> frame;
        frame.reserve(1 + 8 + 8 + 8 + sp.data_length);
        frame.push_back(frame_type);

        uint8_t varint_buf[8];
        size_t bytes = encodeVarInt(sp.stream_id, varint_buf);
        frame.insert(frame.end(), varint_buf, varint_buf + bytes);

        if (sp.stream_offset > 0) {
            bytes = encodeVarInt(sp.stream_offset, varint_buf);
            frame.insert(frame.end(), varint_buf, varint_buf + bytes);
        }

        bytes = encodeVarInt(sp.data_length, varint_buf);
        frame.insert(frame.end(), varint_buf, varint_buf + bytes);

        frame.insert(frame.end(), sp.stream_data.begin(), sp.stream_data.end());

        // New packet number for retransmit
        uint64_t new_pn = _app_pn_send; // buildShortHeaderPacket will increment
        std::vector<uint8_t> packet = buildShortHeaderPacket(frame);
        const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
        applyHeaderProtection(packet, hp_key);

        ssize_t sent = sendPacket(packet.data(), packet.size());
        if (sent < 0) {
            // Single non-blocking retry — do not sleep with quic_mtx held.
            // If the socket is still busy the packet will be retransmitted
            // on the next loss-detection cycle.
            sent = sendPacket(packet.data(), packet.size());
        }

        if (sent > 0) {
            // Track retransmitted packet under new PN
            recordSentPacket(new_pn, sp.stream_id, sp.stream_offset,
                            sp.stream_data.data(), sp.stream_data.size(), sp.fin);
        }

        QUIC_DBG("RETRANSMIT: old_pn=%lu new_pn=%lu sid=%lu off=%lu len=%zu fin=%d",
                 (unsigned long)sp.pn, (unsigned long)new_pn,
                 (unsigned long)sp.stream_id, (unsigned long)sp.stream_offset,
                 sp.data_length, (int)sp.fin);
    }
}

// ============================================================================
// Stream Data Send (vector overload for application layer)
// ============================================================================

void quic::sendStreamData(uint64_t stream_id, const std::vector<uint8_t>& data, bool fin) {
    std::unique_lock<std::recursive_mutex> lock(quic_mtx());
    QUIC_DBG("sendStreamData: sid=%lu data_len=%zu fin=%d", (unsigned long)stream_id, data.size(), (int)fin);

    if (_conn_state.load() != ConnectionState::Connected && !_handshake_complete) {
        return;
    }

    if (data.empty() && !fin) {
        return;
    }

    // Maximum QUIC datagram size minus short header overhead:
    //   1 (flags) + dcid_len + 4 (PN) + 16 (GCM tag) + frame header (~11 max)
    const size_t header_overhead = 1 + _remote_cid.size() + 4 + 16;
    // STREAM frame header: 1 (type) + varint(stream_id) + varint(offset) + varint(length)
    // Conservative estimate for frame header (stream_id < 64 → 1 byte, etc.)
    const size_t frame_hdr_max = 1 + 8 + 8 + 8;  // worst case varints
    // Post-handshake packets can use larger datagrams.  The peer's
    // max_udp_payload_size (parsed from transport params) sets the
    // ceiling, clamped to 1472 (Ethernet MTU minus IP/UDP headers).
    // Before handshake, RFC 9000 requires the 1200-byte minimum.
    const size_t max_datagram = _handshake_complete
        ? static_cast<size_t>(_max_udp_payload)
        : 1200;
    // Usable payload per packet (conservative)
    const size_t max_stream_data = max_datagram - header_overhead - frame_hdr_max;

    // Get or create stream for offset tracking
    auto& stream = _streams[stream_id];
    stream.stream_id = stream_id;

    size_t buf_offset = 0;        // how much of 'data' we've sent so far
    int fc_stalls = 0;
    // 5s max stall — avoids blocking the mutex for too long while
    // waiting for client FC updates.  Previously 60s which could
    // starve all other QUIC connections sharing the same listener.
    static constexpr int MAX_FC_STALLS = 5000;
    bool blocked_sent = false;
    size_t pkt_counter = 0;       // packets sent since last pump/pace

    // Pacing: pump incoming + brief sleep every N packets to let the
    // client process data and send MAX_STREAM_DATA / MAX_DATA updates.
    // Without this, burst-sending hundreds of packets causes packet loss
    // and the client never increases the FC window.
    // Use a larger burst (64 packets) with a shorter sleep (50µs) to
    // improve throughput for large transfers while still yielding.
    static constexpr size_t PACE_INTERVAL = 64;

    while (buf_offset < data.size()) {
        // Bail out early if the connection was closed/draining while we waited
        if (_conn_state.load() == ConnectionState::Closed ||
            _conn_state.load() == ConnectionState::Draining) {
            break;
        }

        size_t remaining = data.size() - buf_offset;

        // Connection-level flow control
        if (_data_sent + remaining > _max_data_remote) {
            remaining = (_max_data_remote > _data_sent) ?
                        static_cast<size_t>(_max_data_remote - _data_sent) : 0;
        }

        // Stream-level flow control
        if (stream.send_offset + remaining > stream.max_data_remote) {
            size_t stream_allowed = (stream.max_data_remote > stream.send_offset) ?
                                    static_cast<size_t>(stream.max_data_remote - stream.send_offset) : 0;
            if (stream_allowed < remaining)
                remaining = stream_allowed;
        }

        if (remaining == 0) {
            // Flow control blocked — notify peer once per stall episode
            if (!blocked_sent) {
                QUIC_DBG("sendStreamData: FC BLOCKED sid=%lu buf_off=%zu/%zu "
                         "conn_sent=%lu conn_max=%lu stream_off=%lu stream_max=%lu",
                         (unsigned long)stream_id, buf_offset, data.size(),
                         (unsigned long)_data_sent, (unsigned long)_max_data_remote,
                         (unsigned long)stream.send_offset, (unsigned long)stream.max_data_remote);
                sendDataBlocked(_max_data_remote);
                sendStreamDataBlocked(stream_id, stream.max_data_remote);
                blocked_sent = true;
            }

            if (++fc_stalls > MAX_FC_STALLS) {
                break;
            }

            // Process incoming datagrams to receive MAX_DATA / MAX_STREAM_DATA
            pumpIncoming();

            // Release the mutex during sleep so other connections
            // sharing this listener are not starved.
            lock.unlock();
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
            lock.lock();

            // Re-check connection state after re-acquiring lock
            if (_conn_state.load() == ConnectionState::Closed ||
                _conn_state.load() == ConnectionState::Draining) {
                break;
            }
            continue;
        }

        fc_stalls = 0;
        blocked_sent = false;

        // Send chunks for the FC-allowed portion
        size_t chunk_offset = 0;
        while (chunk_offset < remaining) {
            // Pacing: every PACE_INTERVAL packets, drain incoming and
            // yield briefly so the client can process data and update FC.
            if (++pkt_counter % PACE_INTERVAL == 0) {
                pumpIncoming();

                // Release the mutex during sleep so other connections
                // sharing this listener are not starved.
                lock.unlock();
                std::this_thread::sleep_for(std::chrono::microseconds(50));
                lock.lock();

                // Re-check connection state after re-acquiring lock
                if (_conn_state.load() == ConnectionState::Closed ||
                    _conn_state.load() == ConnectionState::Draining) {
                    goto done;
                }

                // Re-check FC limits — pumpIncoming may have raised them,
                // allowing us to send more than the original 'remaining'.
                // We break out of the inner loop to re-evaluate at the
                // outer loop which recalculates FC limits.
            }

            size_t chunk_len = std::min(remaining - chunk_offset, max_stream_data);
            size_t abs_offset = buf_offset + chunk_offset;
            bool is_last_data = (abs_offset + chunk_len >= data.size());
            bool set_fin = fin && is_last_data;

            uint64_t stream_off = stream.send_offset;

            uint8_t frame_type = 0x08 | 0x02;
            if (stream_off > 0) frame_type |= 0x04;
            if (set_fin) frame_type |= 0x01;

            std::vector<uint8_t> frame;
            frame.reserve(frame_hdr_max + chunk_len);
            frame.push_back(frame_type);

            uint8_t varint_buf[8];
            size_t bytes = encodeVarInt(stream_id, varint_buf);
            frame.insert(frame.end(), varint_buf, varint_buf + bytes);

            if (stream_off > 0) {
                bytes = encodeVarInt(stream_off, varint_buf);
                frame.insert(frame.end(), varint_buf, varint_buf + bytes);
            }

            bytes = encodeVarInt(chunk_len, varint_buf);
            frame.insert(frame.end(), varint_buf, varint_buf + bytes);

            frame.insert(frame.end(),
                         data.begin() + abs_offset,
                         data.begin() + abs_offset + chunk_len);

            uint64_t pn_before = _app_pn_send; // capture before buildShortHeaderPacket increments
            std::vector<uint8_t> packet = buildShortHeaderPacket(frame);
            const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
            applyHeaderProtection(packet, hp_key);

            ssize_t sent = sendPacket(packet.data(), packet.size());
            if (sent < 0) {
                // Retry with brief pauses.  Release the mutex during
                // sleep to avoid blocking other QUIC connections.
                for (int retry = 0; retry < 5 && sent < 0; ++retry) {
                    lock.unlock();
                    std::this_thread::sleep_for(
                        std::chrono::microseconds(100 * (1 << retry)));
                    lock.lock();
                    if (_conn_state.load() == ConnectionState::Closed ||
                        _conn_state.load() == ConnectionState::Draining) {
                        goto done;
                    }
                    sent = sendPacket(packet.data(), packet.size());
                }
                if (sent < 0) goto done;
            }

            // Record for retransmission tracking
            recordSentPacket(pn_before, stream_id, stream_off,
                            data.data() + abs_offset, chunk_len, set_fin);

            stream.send_offset += chunk_len;
            _data_sent += chunk_len;
            chunk_offset += chunk_len;
        }

        buf_offset += remaining;
    }

done:
    if (fin) {
        stream.send_fin = true;
    }
}

// ============================================================================
// Flow Control: Send MAX_STREAM_DATA frame
// ============================================================================

void quic::sendMaxStreamData(uint64_t stream_id, uint64_t max_data) {
    std::vector<uint8_t> frame;
    frame.push_back(0x11);  // MAX_STREAM_DATA frame type
    uint8_t buf[8];
    size_t bytes = encodeVarInt(stream_id, buf);
    frame.insert(frame.end(), buf, buf + bytes);
    bytes = encodeVarInt(max_data, buf);
    frame.insert(frame.end(), buf, buf + bytes);

    std::vector<uint8_t> packet = buildShortHeaderPacket(frame);
    const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
    applyHeaderProtection(packet, hp_key);
    ssize_t sent = sendPacket(packet.data(), packet.size());
    if (sent < 0) {
        // Flow control frames are critical — immediate non-blocking retry.
        // Do not sleep here: caller typically holds quic_mtx().
        sendPacket(packet.data(), packet.size());
    }
}

// ============================================================================
// Flow Control: Send MAX_DATA frame
// ============================================================================

void quic::sendMaxData(uint64_t max_data) {
    std::vector<uint8_t> frame;
    frame.push_back(0x10);  // MAX_DATA frame type
    uint8_t buf[8];
    size_t bytes = encodeVarInt(max_data, buf);
    frame.insert(frame.end(), buf, buf + bytes);

    std::vector<uint8_t> packet = buildShortHeaderPacket(frame);
    const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
    applyHeaderProtection(packet, hp_key);
    ssize_t sent = sendPacket(packet.data(), packet.size());
    if (sent < 0) {
        // Flow control frames are critical — immediate non-blocking retry.
        // Do not sleep here: caller typically holds quic_mtx().
        sendPacket(packet.data(), packet.size());
    }
}

// ============================================================================
// Flow Control: Send DATA_BLOCKED frame (RFC 9000 §19.12)
// ============================================================================

void quic::sendDataBlocked(uint64_t limit) {
    std::vector<uint8_t> frame;
    frame.push_back(0x14);  // DATA_BLOCKED frame type
    uint8_t buf[8];
    size_t bytes = encodeVarInt(limit, buf);
    frame.insert(frame.end(), buf, buf + bytes);

    std::vector<uint8_t> packet = buildShortHeaderPacket(frame);
    const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
    applyHeaderProtection(packet, hp_key);
    sendPacket(packet.data(), packet.size());
}

// ============================================================================
// Flow Control: Send STREAM_DATA_BLOCKED frame (RFC 9000 §19.13)
// ============================================================================

void quic::sendStreamDataBlocked(uint64_t stream_id, uint64_t limit) {
    std::vector<uint8_t> frame;
    frame.push_back(0x15);  // STREAM_DATA_BLOCKED frame type
    uint8_t buf[8];
    size_t bytes = encodeVarInt(stream_id, buf);
    frame.insert(frame.end(), buf, buf + bytes);
    bytes = encodeVarInt(limit, buf);
    frame.insert(frame.end(), buf, buf + bytes);

    std::vector<uint8_t> packet = buildShortHeaderPacket(frame);
    const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
    applyHeaderProtection(packet, hp_key);
    sendPacket(packet.data(), packet.size());
}

// ============================================================================
// ACK: Send ACK frame for 1-RTT application packets
// ============================================================================

void quic::sendAppAck() {
    std::vector<uint8_t> frame = buildAckFrame(_app_pn_recv, 0, _app_pn_recv_ranges);

    std::vector<uint8_t> packet = buildShortHeaderPacket(frame);
    const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
    applyHeaderProtection(packet, hp_key);
    sendPacket(packet.data(), packet.size());
}

// ============================================================================
// TLS Message Processing
// ============================================================================

void quic::processTLSMessages() {
    // Select the correct crypto buffer based on encryption level
    std::vector<uint8_t>* crypto_buffer = nullptr;
    switch (_current_enc_level) {
        case EncryptionLevel::Initial:
            crypto_buffer = &_crypto_recv_initial;
            break;
        case EncryptionLevel::Handshake:
            crypto_buffer = &_crypto_recv_handshake;
            break;
        case EncryptionLevel::Application:
            crypto_buffer = &_crypto_recv_app;
            break;
    }
    
    if (!crypto_buffer) return;
    
    // === HANDSHAKE TIMEOUT CHECK (EVENT-DRIVEN) ===
    // Check for incomplete ClientHello (missing fragments due to packet loss)
    // RFC 9001: ClientHello is typically 1528+ bytes
    // Only trigger timeout if we've received enough time (2+ seconds) with incomplete data and no new fragments
    if (_is_server && _current_enc_level == EncryptionLevel::Initial && crypto_buffer->size() > 0) {
        // Check if we have a complete ClientHello
        // ClientHello format: type(1) + length(3) + [body 35+ bytes]
        if (crypto_buffer->size() >= 4) {
            uint8_t msg_type = (*crypto_buffer)[0];
            uint32_t msg_len = (static_cast<uint32_t>((*crypto_buffer)[1]) << 16) |
                              (static_cast<uint32_t>((*crypto_buffer)[2]) << 8) |
                              static_cast<uint32_t>((*crypto_buffer)[3]);
            
            size_t required_bytes = 4 + msg_len;  // type + length + body
            size_t available_bytes = crypto_buffer->size();
            
            // Detect incomplete message: need significant gap (>200 bytes missing is likely packet loss)
            // Don't timeout on small gaps since fragments might still be arriving
            if (msg_type == 0x01 && available_bytes < required_bytes && 
                (required_bytes - available_bytes) > 200) {
                
                if (!_incomplete_ch_detected) {
                    _incomplete_ch_detected = true;
                    _handshake_start_time = std::chrono::steady_clock::now();
                }
                
                // Check if handshake timeout has been exceeded (2 seconds for fragmented messages)
                auto now = std::chrono::steady_clock::now();
                auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
                    now - _handshake_start_time).count();
                
                if (elapsed_ms > 2000 && !_handshake_timeout_triggered) {  // 2 second timeout for fragments
                    _handshake_timeout_triggered = true;
                    _conn_state.store(ConnectionState::Closed);
                    return;  // Exit without processing
                }
                
                // Still waiting for more fragments, don't process yet
                return;
            }
        }
    }
    
    // Process TLS handshake messages from crypto buffer
    
    while (crypto_buffer->size() >= 4) {
        uint8_t msg_type = (*crypto_buffer)[0];
        uint32_t msg_len = (static_cast<uint32_t>((*crypto_buffer)[1]) << 16) |
                          (static_cast<uint32_t>((*crypto_buffer)[2]) << 8) |
                          static_cast<uint32_t>((*crypto_buffer)[3]);
        
        
        if (crypto_buffer->size() < 4 + msg_len) {
            break;  // Wait for more data
        }
        
        // Extract complete message
        std::vector<uint8_t> msg(crypto_buffer->begin(),
                                  crypto_buffer->begin() + 4 + msg_len);
        crypto_buffer->erase(crypto_buffer->begin(),
                              crypto_buffer->begin() + 4 + msg_len);
        
        switch (msg_type) {
            case 0x02:  // ServerHello
                // Add to transcript (client receiving from server)
                _tls_transcript.insert(_tls_transcript.end(), msg.begin(), msg.end());
                processServerHello(msg);
                break;
            case 0x08:  // EncryptedExtensions
                // Add to transcript (client receiving from server)
                _tls_transcript.insert(_tls_transcript.end(), msg.begin(), msg.end());
                // Parse transport parameters (flow control limits, etc.)
                processEncryptedExtensions(msg);
                break;
            case 0x0b:  // Certificate
                // Add to transcript (client receiving from server)
                _tls_transcript.insert(_tls_transcript.end(), msg.begin(), msg.end());
                // Verify server certificate (simplified)
                break;
            case 0x0f:  // CertificateVerify
                // Add to transcript (client receiving from server)
                _tls_transcript.insert(_tls_transcript.end(), msg.begin(), msg.end());
                // Verify signature (simplified)
                break;
            case 0x14:  // Finished
                processFinished(msg);
                // Clear transcript after Finished is processed
                // Transcript is no longer needed after handshake completion
                _tls_transcript.clear();
                break;
            case 0x01:  // ClientHello
                if (_is_server) {
                    // Add to transcript (server receiving from client)
                    _tls_transcript.insert(_tls_transcript.end(), msg.begin(), msg.end());
                    processClientHello(msg);
                }
                break;
            default:
                break;
        }
    }
}

// ============================================================================
// TLS: Process ServerHello
// ============================================================================

void quic::processServerHello(const std::vector<uint8_t>& msg) {
    if (msg.size() < 39) return;  // Minimum ServerHello size
    
    // Note: Message is ALREADY added to transcript in processTLSMessages()
    // Only process the message content here, not the raw bytes
    
    // Skip to extensions
    size_t offset = 4;  // Skip type + length
    offset += 2;  // Legacy version
    
    // Server random
    _server_random.assign(msg.begin() + offset, msg.begin() + offset + 32);
    offset += 32;
    
    // Session ID length + session ID
    uint8_t session_id_len = msg[offset++];
    offset += session_id_len;
    
    // Cipher suite - parse and store the selected cipher
    if (offset + 2 > msg.size()) return;
    _selected_cipher = (static_cast<uint16_t>(msg[offset]) << 8) | msg[offset + 1];
    offset += 2;
    
    // Compression method
    offset += 1;
    
    // Extensions length
    if (offset + 2 > msg.size()) return;
    uint16_t ext_len = (static_cast<uint16_t>(msg[offset]) << 8) | msg[offset + 1];
    offset += 2;
    
    // Parse extensions to find key_share
    size_t ext_end = offset + ext_len;
    while (offset + 4 <= ext_end && offset + 4 <= msg.size()) {
        uint16_t ext_type = (static_cast<uint16_t>(msg[offset]) << 8) | msg[offset + 1];
        uint16_t ext_data_len = (static_cast<uint16_t>(msg[offset + 2]) << 8) | msg[offset + 3];
        offset += 4;
        
        if (ext_type == 0x0033 && ext_data_len >= 36) {  // key_share
            // Named group (2 bytes)
            offset += 2;
            // Key length (2 bytes)
            offset += 2;
            // Client public key (32 bytes for x25519)
            if (offset + 32 <= msg.size()) {
                std::vector<uint8_t> client_pub(msg.begin() + offset, msg.begin() + offset + 32);
                
                
                
                // Compute shared secret
                _ecdhe_shared.resize(32);
                std::vector<uint8_t> priv_vec(_ecdhe_private.begin(), _ecdhe_private.end());
                scalarmult_curve25519(_ecdhe_shared, priv_vec, client_pub);
                
                
                // Derive handshake keys
                deriveHandshakeKeys(_ecdhe_shared);
            }
            break;
        }
        
        offset += ext_data_len;
    }
}

// ============================================================================
// TLS: Parse QUIC Transport Parameters (RFC 9000 §18)
// ============================================================================

void quic::parseTransportParameters(const uint8_t* data, size_t len) {
    size_t offset = 0;
    while (offset < len) {
        if (offset + 1 > len) break;
        size_t bytes;
        uint64_t param_id = decodeVarInt(&data[offset], bytes);
        offset += bytes;
        if (offset + 1 > len) break;
        uint64_t param_len = decodeVarInt(&data[offset], bytes);
        offset += bytes;
        if (offset + param_len > len) break;
        
        switch (param_id) {
            case 0x04: {  // initial_max_data
                size_t vb;
                _max_data_remote = decodeVarInt(&data[offset], vb);
                break;
            }
            case 0x05: {  // initial_max_stream_data_bidi_local
                size_t vb;
                _peer_initial_max_stream_data_bidi_local = decodeVarInt(&data[offset], vb);
                break;
            }
            case 0x06: {  // initial_max_stream_data_bidi_remote
                size_t vb;
                _peer_initial_max_stream_data_bidi_remote = decodeVarInt(&data[offset], vb);
                break;
            }
            case 0x07: {  // initial_max_stream_data_uni
                size_t vb;
                _peer_initial_max_stream_data_uni = decodeVarInt(&data[offset], vb);
                break;
            }
            case 0x08: {  // initial_max_streams_bidi
                size_t vb;
                _max_streams_bidi_remote = decodeVarInt(&data[offset], vb);
                break;
            }
            case 0x09: {  // initial_max_streams_uni
                size_t vb;
                _max_streams_uni_remote = decodeVarInt(&data[offset], vb);
                break;
            }
            case 0x01: {  // max_idle_timeout
                size_t vb;
                uint64_t peer_timeout = decodeVarInt(&data[offset], vb);
                // Use the smaller of our timeout and peer's
                if (peer_timeout > 0 && peer_timeout < _max_idle_timeout) {
                    _max_idle_timeout = peer_timeout;
                }
                break;
            }
            case 0x03: {  // max_udp_payload_size
                size_t vb;
                uint64_t peer_max_payload = decodeVarInt(&data[offset], vb);
                // Clamp to safe Ethernet MTU ceiling (1472 = 1500 - 20 IP - 8 UDP)
                if (peer_max_payload > 1472)
                    peer_max_payload = 1472;
                if (peer_max_payload > _max_udp_payload)
                    _max_udp_payload = peer_max_payload;
                break;
            }
            default:
                // Skip unknown parameters
                break;
        }
        offset += param_len;
    }
}

// ============================================================================
// TLS: Process EncryptedExtensions (Client side - contains transport params)
// ============================================================================

void quic::processEncryptedExtensions(const std::vector<uint8_t>& msg) {
    if (msg.size() < 6) return;  // Type(1) + Length(3) + ExtLen(2) minimum
    
    size_t offset = 4;  // Skip type(1) + length(3)
    
    // Extensions length (2 bytes)
    if (offset + 2 > msg.size()) return;
    uint16_t ext_total_len = (static_cast<uint16_t>(msg[offset]) << 8) | msg[offset + 1];
    offset += 2;
    
    size_t ext_end = offset + ext_total_len;
    if (ext_end > msg.size()) ext_end = msg.size();
    
    while (offset + 4 <= ext_end) {
        uint16_t ext_type = (static_cast<uint16_t>(msg[offset]) << 8) | msg[offset + 1];
        uint16_t ext_data_len = (static_cast<uint16_t>(msg[offset + 2]) << 8) | msg[offset + 3];
        offset += 4;
        
        if (offset + ext_data_len > ext_end) break;
        
        if (ext_type == 0x0039) {  // quic_transport_parameters
            parseTransportParameters(&msg[offset], ext_data_len);
        }
        
        offset += ext_data_len;
    }
}

// ============================================================================
// TLS: Process ClientHello (Server side)
// ============================================================================

void quic::processClientHello(const std::vector<uint8_t>& msg) {
    if (!_is_server) return;
    if (msg.size() < 39) return;  // Minimum ClientHello size
    QUIC_DBG("processClientHello: msg_size=%zu", msg.size());
    
    
    // Note: Message is ALREADY added to transcript in processTLSMessages()
    // Only process the message content here, not the raw bytes
    
    // Parse ClientHello
    size_t offset = 4;  // Skip type + length
    offset += 2;  // Legacy version
    
    // Client random
    _client_random.assign(msg.begin() + offset, msg.begin() + offset + 32);
    offset += 32;
    
    // Session ID - save for ServerHello echo
    uint8_t session_id_len = msg[offset++];
    if (session_id_len > 0 && offset + session_id_len <= msg.size()) {
        _client_session_id.assign(msg.begin() + offset, msg.begin() + offset + session_id_len);
    } else {
        _client_session_id.clear();
    }
    offset += session_id_len;
    
    // Cipher suites - parse and select best supported
    if (offset + 2 > msg.size()) return;
    uint16_t cipher_suites_len = (static_cast<uint16_t>(msg[offset]) << 8) | msg[offset + 1];
    offset += 2;
    
    // Parse cipher suites and select preferred (prefer AES-256-GCM with SHA-384)
    bool found_aes256 = false;
    bool found_aes128 = false;
    for (size_t i = 0; i < cipher_suites_len && offset + i + 1 < msg.size(); i += 2) {
        uint16_t cs = (static_cast<uint16_t>(msg[offset + i]) << 8) | msg[offset + i + 1];
        if (cs == 0x1302) found_aes256 = true;  // TLS_AES_256_GCM_SHA384
        if (cs == 0x1301) found_aes128 = true;  // TLS_AES_128_GCM_SHA256
    }
    
    // Select cipher suite - prefer AES-256 with SHA-384
    if (found_aes256) {
        _selected_cipher = 0x1302;  // TLS_AES_256_GCM_SHA384
    } else if (found_aes128) {
        _selected_cipher = 0x1301;  // TLS_AES_128_GCM_SHA256
    } else {
        return;
    }
    
    offset += cipher_suites_len;
    
    // Compression methods
    if (offset >= msg.size()) return;
    uint8_t comp_len = msg[offset++];
    offset += comp_len;
    
    // Extensions length
    if (offset + 2 > msg.size()) return;
    uint16_t ext_len = (static_cast<uint16_t>(msg[offset]) << 8) | msg[offset + 1];
    offset += 2;
    
    // Parse extensions to find key_share and SNI
    size_t ext_end = offset + ext_len;
    std::vector<uint8_t> client_key_share;
    
    while (offset + 4 <= ext_end && offset + 4 <= msg.size()) {
        uint16_t ext_type = (static_cast<uint16_t>(msg[offset]) << 8) | msg[offset + 1];
        uint16_t ext_data_len = (static_cast<uint16_t>(msg[offset + 2]) << 8) | msg[offset + 3];
        offset += 4;
        
        if (offset + ext_data_len > msg.size()) break;
        
        if (ext_type == 0x0010 && ext_data_len > 2) {  // ALPN
            // Parse ALPN list
            size_t alpn_off = offset;
            uint16_t alpn_list_len = (static_cast<uint16_t>(msg[alpn_off]) << 8) | msg[alpn_off + 1];
            alpn_off += 2;
            
            // Parse first protocol in list (we'll use h3 if available)
            size_t alpn_end = alpn_off + alpn_list_len;
            while (alpn_off + 1 <= alpn_end && alpn_off + 1 <= offset + ext_data_len) {
                uint8_t proto_len = msg[alpn_off++];
                if (alpn_off + proto_len <= alpn_end && alpn_off + proto_len <= offset + ext_data_len) {
                    std::string proto(msg.begin() + alpn_off, msg.begin() + alpn_off + proto_len);
                    if (proto == "h3" || proto == "h3-29") {
                        _selected_alpn = proto;
                        break;  // Use first h3 protocol found
                    }
                    alpn_off += proto_len;
                }
            }
        } else if (ext_type == 0x0000 && ext_data_len > 5) {  // SNI
            // Parse SNI to select certificate
            size_t sni_off = offset + 2;  // Skip list length
            if (sni_off + 3 <= offset + ext_data_len) {
                uint8_t name_type = msg[sni_off++];
                uint16_t name_len = (static_cast<uint16_t>(msg[sni_off]) << 8) | msg[sni_off + 1];
                sni_off += 2;
                
                if (name_type == 0x00 && sni_off + name_len <= offset + ext_data_len) {
                    std::string hostname(msg.begin() + sni_off, msg.begin() + sni_off + name_len);
                    
                    // Store the requested hostname for SNI support
                    _requested_hostname = hostname;
                    
                    // Select certificate based on hostname
                    auto it = _cert_map.find(hostname);
                    if (it != _cert_map.end()) {
                        _selected_cert = &it->second;
                    } else if (!_cert_map.empty()) {
                        // Use first certificate as default
                        _selected_cert = &_cert_map.begin()->second;
                    }
                }
            }
        } else if (ext_type == 0x000d && ext_data_len >= 2) {  // signature_algorithms
            size_t sig_off = offset;
            uint16_t sig_list_len = (static_cast<uint16_t>(msg[sig_off]) << 8) | msg[sig_off + 1];
            sig_off += 2;
            size_t sig_end = std::min(sig_off + sig_list_len, offset + ext_data_len);

            _sigalg_rsa_pss_sha256 = false;
            _sigalg_rsa_pss_sha384 = false;

            while (sig_off + 1 < sig_end && sig_off + 1 < msg.size()) {
                uint16_t sig = (static_cast<uint16_t>(msg[sig_off]) << 8) | msg[sig_off + 1];
                if (sig == 0x0804) _sigalg_rsa_pss_sha256 = true;  // rsa_pss_rsae_sha256
                if (sig == 0x0805) _sigalg_rsa_pss_sha384 = true;  // rsa_pss_rsae_sha384
                sig_off += 2;
            }
        } else if (ext_type == 0x0033) {  // key_share
            // Parse client key shares
            if (ext_data_len >= 4) {
                size_t ks_off = offset;
                uint16_t ks_len = (static_cast<uint16_t>(msg[ks_off]) << 8) | msg[ks_off + 1];
                ks_off += 2;
                
                // Parse key share entries
                size_t ks_end = ks_off + ks_len;
                while (ks_off + 4 <= ks_end && ks_off + 4 <= msg.size()) {
                    uint16_t group = (static_cast<uint16_t>(msg[ks_off]) << 8) | msg[ks_off + 1];
                    uint16_t key_len = (static_cast<uint16_t>(msg[ks_off + 2]) << 8) | msg[ks_off + 3];
                    ks_off += 4;
                    
                    if (group == 0x001d && key_len == 32) {  // x25519
                        client_key_share.assign(msg.begin() + ks_off, msg.begin() + ks_off + 32);
                        break;
                    }
                    
                    ks_off += key_len;
                }
            }
        } else if (ext_type == 0x0039) {  // quic_transport_parameters
            parseTransportParameters(&msg[offset], ext_data_len);
        }
        
        offset += ext_data_len;
    }
    
    // Generate server ECDHE key pair
    generateECDHEKeyPair();
    
    // Compute shared secret
    if (!client_key_share.empty()) {
        computeSharedSecret(client_key_share);
    }
    
    // Select default certificate if none selected via SNI
    if (_selected_cert == nullptr && !_cert_map.empty()) {
        _selected_cert = &_cert_map.begin()->second;
    }
    
    // Send ServerHello and continue handshake
    sendServerHello();
}

// ============================================================================
// TLS: Process Finished
// ============================================================================

void quic::processFinished(const std::vector<uint8_t>& msg) {
    // Add to transcript before processing
    _tls_transcript.insert(_tls_transcript.end(), msg.begin(), msg.end());
    QUIC_DBG("processFinished: is_server=%d", (int)_is_server);
    
    // Verify Finished message
    // In a full implementation, compute expected verify_data and compare
    
    if (!_is_server) {
        // Client received server's Finished
        // Derive application keys and send our Finished
        deriveApplicationKeys();
        
        // Send client Finished
        sendClientFinished();
        
        _handshake_complete = true;
        _conn_state.store(ConnectionState::Connected);
    } else {
        // Server received client's Finished
        _handshake_complete = true;
        _conn_state.store(ConnectionState::Connected);
        
        // Handshake complete - no need to retransmit anymore
        _hs_flight_packets.clear();
        
        // Send HANDSHAKE_DONE frame in 1-RTT packet
        sendHandshakeDone();
    }
}

// ============================================================================
// TLS: Send Client Finished
// ============================================================================

void quic::sendClientFinished() {
    // Build Finished message
    std::vector<uint8_t> finished;
    finished.push_back(0x14);  // Finished type
    
    // Compute verify_data using cipher-appropriate hash:
    // finished_key = HKDF-Expand-Label(client_handshake_traffic_secret, "finished", "", hash_len)
    // verify_data = HMAC(finished_key, Transcript-Hash)
    const bool use_sha384 = (_selected_cipher == 0x1302);  // TLS_AES_256_GCM_SHA384
    const size_t hash_len = use_sha384 ? 48 : 32;
    const uint16_t cipher_suite = use_sha384 ? 0x1302 : 0x1301;
    
    // Use TLS class HKDF method
    std::vector<uint8_t> finished_key = netplus::tls::hkdf_expand_label(
        _c_hs_traffic_secret, "finished", {}, hash_len, cipher_suite);
    
    // Compute transcript hash and verify_data
    std::vector<uint8_t> transcript_hash = use_sha384 
        ? sha384_hash(_tls_transcript)
        : sha256_hash(_tls_transcript);
    std::vector<uint8_t> verify_data = use_sha384
        ? hmac_sha384(finished_key, transcript_hash)
        : hmac_sha256(finished_key, transcript_hash);
    
    // Length (3 bytes) - 48 bytes for SHA-384, 32 bytes for SHA-256
    finished.push_back(0x00);
    finished.push_back(0x00);
    finished.push_back(static_cast<uint8_t>(hash_len));
    
    // Add verify_data
    finished.insert(finished.end(), verify_data.begin(), verify_data.end());
    
    // Add to transcript BEFORE sending
    _tls_transcript.insert(_tls_transcript.end(), finished.begin(), finished.end());
    
    // Send in Handshake packet
    std::vector<uint8_t> crypto_frame = buildCryptoFrame(finished, _crypto_send_handshake.size());
    _crypto_send_handshake.insert(_crypto_send_handshake.end(), finished.begin(), finished.end());
    
    std::vector<uint8_t> packet = buildHandshakePacket(crypto_frame);
    applyHeaderProtection(packet, _hs_hp_client);
    
    if (_Socket >= 0) {
        sendPacket(packet.data(), packet.size());
    }
}

// ============================================================================
// QUIC: Send HANDSHAKE_DONE frame (Server only)
// ============================================================================

void quic::sendHandshakeDone() {
    if (!_is_server) return;
    QUIC_DBG("sendHandshakeDone: sending HANDSHAKE_DONE");
    
    
    // HANDSHAKE_DONE frame is a single byte: 0x1e
    std::vector<uint8_t> frame;
    frame.push_back(0x1e);  // HANDSHAKE_DONE frame type
    
    // Build and send 1-RTT (short header) packet
    std::vector<uint8_t> packet = buildShortHeaderPacket(frame);
    
    // Apply header protection using server's application HP key
    applyHeaderProtection(packet, _app_hp_server);
    
    
    if (_Socket >= 0) {
        sendPacket(packet.data(), packet.size());
    }
    
    // If ALPN is HTTP/3, send the mandatory control streams (RFC 9114 §6.2.1)
    if (_selected_alpn == "h3" || _selected_alpn == "h3-29") {
        sendHttp3ControlStreams();
    }
}

// ============================================================================
// HTTP/3: Send mandatory control streams (RFC 9114 §6.2.1)
// ============================================================================

void quic::sendHttp3ControlStreams() {
    if (_h3_ctrl_sent) return;
    _h3_ctrl_sent = true;
    QUIC_DBG("sendHttp3ControlStreams: sending control, QPACK encoder, QPACK decoder streams");
    
    // 1. Control stream (stream type 0x00) with SETTINGS frame
    //    RFC 9114 §6.2.1: Each side MUST initiate a single control stream
    //    and send its SETTINGS frame as the first frame on this stream.
    uint64_t ctrl_id = openStream(false);  // server-initiated unidirectional
    std::vector<uint8_t> ctrl_data;
    ctrl_data.push_back(0x00);  // Stream type: Control
    // SETTINGS frame (type 0x04) with empty payload (use defaults)
    ctrl_data.push_back(0x04);  // Frame type: SETTINGS
    ctrl_data.push_back(0x00);  // Frame length: 0
    sendStreamData(ctrl_id, ctrl_data, false);
    
    // 2. QPACK encoder stream (stream type 0x02)
    //    RFC 9204 §4.2: required for QPACK header compression
    uint64_t enc_id = openStream(false);
    std::vector<uint8_t> enc_data;
    enc_data.push_back(0x02);  // Stream type: QPACK Encoder
    sendStreamData(enc_id, enc_data, false);
    
    // 3. QPACK decoder stream (stream type 0x03)
    uint64_t dec_id = openStream(false);
    std::vector<uint8_t> dec_data;
    dec_data.push_back(0x03);  // Stream type: QPACK Decoder
    sendStreamData(dec_id, dec_data, false);
}

// ============================================================================
// Socket Interface: sendData (Send application data)
// ============================================================================

size_t quic::sendData(buffer& data, int flags) {
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    
    // Check connection state
    if (_conn_state.load() == ConnectionState::Closed) {
        NetException e;
        e[NetException::Error] << "quic::sendData: connection closed";
        throw e;
    }
    
    if (_conn_state.load() != ConnectionState::Connected && !_handshake_complete) {
        NetException e;
        e[NetException::Error] << "quic::sendData: connection not established";
        throw e;
    }
    
    // Get data pointer
#ifdef Windows
    const char* send_ptr = data.data.buf;
#else
    const char* send_ptr = data.ptr ? data.data.ptr : data.data.buf;
#endif
    size_t send_len = data.size;
    
    if (send_len == 0) {
        return 0;
    }
    
    // Check flow control
    if (_data_sent + send_len > _max_data_remote) {
        // Would exceed peer's max_data limit
        send_len = (_max_data_remote > _data_sent) ? 
                   (_max_data_remote - _data_sent) : 0;
        
        if (send_len == 0) {
            NetException e;
            e[NetException::Note] << "quic::sendData: flow control blocked";
            throw e;
        }
    }
    
    // Use default stream (client-initiated bidirectional stream 0)
    uint64_t stream_id = _is_server ? 1 : 0;
    
    // Get or create stream
    auto& stream = _streams[stream_id];
    stream.stream_id = stream_id;
    
    // Check stream-level flow control
    if (stream.send_offset + send_len > stream.max_data_remote) {
        send_len = (stream.max_data_remote > stream.send_offset) ?
                   (stream.max_data_remote - stream.send_offset) : 0;
        
        if (send_len == 0) {
            NetException e;
            e[NetException::Note] << "quic::sendData: stream flow control blocked";
            throw e;
        }
    }
    
    // Build STREAM frames in QUIC datagram chunks
    const size_t header_overhead = 1 + _remote_cid.size() + 4 + 16;
    const size_t frame_hdr_max = 1 + 8 + 8 + 8;
    const size_t max_datagram = _handshake_complete
        ? static_cast<size_t>(_max_udp_payload)
        : 1200;
    const size_t max_chunk = max_datagram - header_overhead - frame_hdr_max;

    size_t sent_total = 0;
    while (sent_total < send_len) {
        size_t chunk = std::min(send_len - sent_total, max_chunk);

        std::vector<uint8_t> chunk_data(send_ptr + sent_total, send_ptr + sent_total + chunk);
        std::vector<uint8_t> stream_frame = buildStreamFrame(
            stream_id, chunk_data, stream.send_offset, false);

        // Build 1-RTT packet
        uint64_t pn_before = _app_pn_send;
        std::vector<uint8_t> packet = buildShortHeaderPacket(stream_frame);

        // Apply header protection
        const uint8_t* hp_key = _is_server ? _app_hp_server : _app_hp_client;
        applyHeaderProtection(packet, hp_key);

        // Send packet
        ssize_t ret = sendPacket(packet.data(), packet.size());
        if (ret < 0) {
            // Non-blocking retry — do not sleep with quic_mtx held.
            // Lost packets will be retransmitted by the loss detection.
            ret = sendPacket(packet.data(), packet.size());
            if (ret < 0) break;
        }

        // Record for retransmission tracking
        recordSentPacket(pn_before, stream_id, stream.send_offset,
                        reinterpret_cast<const uint8_t*>(send_ptr + sent_total), chunk, false);

        stream.send_offset += chunk;
        _data_sent += chunk;
        sent_total += chunk;
    }
    
    return sent_total;
}

// ============================================================================
// Stream Data Send (for specific stream)
// ============================================================================

size_t quic::sendStreamData(uint64_t stream_id, const uint8_t* data, size_t len, bool fin) {
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    
    if (_conn_state.load() != ConnectionState::Connected && !_handshake_complete) {
        return 0;
    }
    
    if (len == 0 && !fin) {
        return 0;
    }
    
    // Get or create stream
    auto& stream = _streams[stream_id];
    stream.stream_id = stream_id;
    
    // Check flow control
    size_t send_len = len;
    if (_data_sent + send_len > _max_data_remote) {
        send_len = (_max_data_remote > _data_sent) ?
                   (_max_data_remote - _data_sent) : 0;
    }
    
    if (stream.send_offset + send_len > stream.max_data_remote) {
        send_len = (stream.max_data_remote > stream.send_offset) ?
                   (stream.max_data_remote - stream.send_offset) : 0;
    }
    
    if (send_len == 0 && !fin) {
        return 0;
    }
    
    // Chunk into QUIC datagrams for proper MTU handling
    const size_t header_overhead = 1 + _remote_cid.size() + 4 + 16;
    const size_t frame_hdr_max = 1 + 8 + 8 + 8;
    const size_t max_datagram = _handshake_complete
        ? static_cast<size_t>(_max_udp_payload)
        : 1200;
    const size_t max_chunk = max_datagram - header_overhead - frame_hdr_max;

    size_t sent_total = 0;
    while (sent_total < send_len) {
        size_t chunk = std::min(send_len - sent_total, max_chunk);
        bool is_last = (sent_total + chunk >= send_len);
        bool set_fin = fin && is_last;

        std::vector<uint8_t> chunk_data(data + sent_total, data + sent_total + chunk);
        std::vector<uint8_t> stream_frame = buildStreamFrame(
            stream_id, chunk_data, stream.send_offset, set_fin);

        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);

        ssize_t ret = sendPacket(packet.data(), packet.size());
        if (ret < 0) {
            // Non-blocking retry — do not sleep with quic_mtx held.
            // Lost packets will be retransmitted by the loss detection.
            ret = sendPacket(packet.data(), packet.size());
            if (ret < 0) break;
        }

        // Record for retransmission tracking
        recordSentPacket(pn_before, stream_id, stream.send_offset,
                        data + sent_total, chunk, set_fin);

        stream.send_offset += chunk;
        _data_sent += chunk;
        sent_total += chunk;
    }

    if (fin) {
        stream.send_fin = true;
    }
    
    return sent_total;
}

// ============================================================================
// Frame Building: STREAM Frame
// ============================================================================

std::vector<uint8_t> quic::buildStreamFrame(uint64_t stream_id, 
                                             const std::vector<uint8_t>& data,
                                             uint64_t offset, bool fin) {
    std::vector<uint8_t> frame;
    frame.reserve(1 + 8 + 8 + 8 + data.size());
    
    // Frame type: STREAM (0x08-0x0f)
    // Bits: OFF (0x04) | LEN (0x02) | FIN (0x01)
    uint8_t frame_type = 0x08;
    if (offset > 0) frame_type |= 0x04;  // OFF bit
    frame_type |= 0x02;  // LEN bit (always include length for clarity)
    if (fin) frame_type |= 0x01;  // FIN bit
    frame.push_back(frame_type);
    
    // Stream ID (variable-length integer)
    uint8_t sid_buf[8];
    size_t sid_len = encodeVarInt(stream_id, sid_buf);
    frame.insert(frame.end(), sid_buf, sid_buf + sid_len);
    
    // Offset (if present)
    if (offset > 0) {
        uint8_t off_buf[8];
        size_t off_len = encodeVarInt(offset, off_buf);
        frame.insert(frame.end(), off_buf, off_buf + off_len);
    }
    
    // Length (variable-length integer)
    uint8_t len_buf[8];
    size_t len_len = encodeVarInt(data.size(), len_buf);
    frame.insert(frame.end(), len_buf, len_buf + len_len);
    
    // Data
    frame.insert(frame.end(), data.begin(), data.end());
    
    return frame;
}

// ============================================================================
// Frame Building: ACK Frame
// ============================================================================

void quic::trackRecvPN(uint64_t pn, std::set<std::pair<uint64_t,uint64_t>>& ranges) {
    auto r = std::make_pair(pn, pn);
    auto it = ranges.insert(r).first;
    // Merge with predecessor(s)
    while (it != ranges.begin()) {
        auto prev = std::prev(it);
        if (prev->second + 1 >= it->first) {
            auto merged = std::make_pair(prev->first,
                                          std::max(prev->second, it->second));
            ranges.erase(prev);
            it = ranges.erase(it);
            it = ranges.insert(merged).first;
        } else {
            break;
        }
    }
    // Merge with successor(s)
    while (true) {
        auto next = std::next(it);
        if (next == ranges.end()) break;
        if (it->second + 1 >= next->first) {
            auto merged = std::make_pair(it->first,
                                          std::max(it->second, next->second));
            ranges.erase(next);
            it = ranges.erase(it);
            it = ranges.insert(merged).first;
        } else {
            break;
        }
    }
}

std::vector<uint8_t> quic::buildAckFrame(uint64_t largest_ack, uint64_t ack_delay,
                                          const std::set<std::pair<uint64_t,uint64_t>>& recv_ranges) {
    std::vector<uint8_t> frame;
    uint8_t vbuf[8];
    size_t vlen;

    QUIC_DBG("buildAckFrame: largest_ack=%lu ack_delay=%lu ranges_size=%zu",
             (unsigned long)largest_ack, (unsigned long)ack_delay, recv_ranges.size());

    // Frame type: ACK (0x02)
    frame.push_back(0x02);

    if (!recv_ranges.empty()) {
        // Ranges are sorted ascending. Walk from the highest range down.
        auto it = std::prev(recv_ranges.end());

        // Largest Acknowledged
        uint64_t la = it->second;
        vlen = encodeVarInt(la, vbuf);
        frame.insert(frame.end(), vbuf, vbuf + vlen);

        // ACK Delay
        vlen = encodeVarInt(ack_delay, vbuf);
        frame.insert(frame.end(), vbuf, vbuf + vlen);

        // Count additional ranges (all except the first/highest)
        uint64_t range_count = recv_ranges.size() - 1;
        vlen = encodeVarInt(range_count, vbuf);
        frame.insert(frame.end(), vbuf, vbuf + vlen);

        // First ACK Range: la - lo of the highest range
        uint64_t first_range = it->second - it->first;
        vlen = encodeVarInt(first_range, vbuf);
        frame.insert(frame.end(), vbuf, vbuf + vlen);

        uint64_t prev_smallest = it->first;

        // Encode remaining ranges from high to low
        while (it != recv_ranges.begin()) {
            --it;
            uint64_t gap = prev_smallest - it->second - 2;
            vlen = encodeVarInt(gap, vbuf);
            frame.insert(frame.end(), vbuf, vbuf + vlen);

            uint64_t ack_range = it->second - it->first;
            vlen = encodeVarInt(ack_range, vbuf);
            frame.insert(frame.end(), vbuf, vbuf + vlen);

            prev_smallest = it->first;
        }
    } else {
        // No ranges tracked — single-packet ACK
        vlen = encodeVarInt(largest_ack, vbuf);
        frame.insert(frame.end(), vbuf, vbuf + vlen);

        vlen = encodeVarInt(ack_delay, vbuf);
        frame.insert(frame.end(), vbuf, vbuf + vlen);

        // Range count = 0
        frame.push_back(0x00);

        // First ACK Range = 0 (only the largest_ack packet itself)
        frame.push_back(0x00);
    }

    // Hex dump the ACK frame for debugging
    {
        std::string hex;
        for (size_t i = 0; i < frame.size() && i < 32; ++i) {
            char buf[4];
            snprintf(buf, sizeof(buf), "%02x ", frame[i]);
            hex += buf;
        }
        QUIC_DBG("buildAckFrame result (%zu bytes): %s", frame.size(), hex.c_str());
    }

    return frame;
}

// ============================================================================
// pumpIncoming: Drain incoming UDP datagrams and process them
//
// Called from long-running stream callbacks (e.g. H3 streaming) to keep
// QUIC flow control alive.  Reads datagrams from the parent's (or own)
// UDP socket and processes ACKs, MAX_DATA, MAX_STREAM_DATA, etc.
// ============================================================================

void quic::pumpIncoming() {
    quic* parent = _parent ? _parent : this;
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());

    static constexpr int MAX_PUMP = 64;
    std::vector<uint8_t> dgram(65535);
    for (int i = 0; i < MAX_PUMP; ++i) {
        sockaddr_storage peer_addr{};
        socklen_t peer_len = sizeof(peer_addr);

        ssize_t recv_len = ::recvfrom(parent->_Socket,
                                       reinterpret_cast<char*>(dgram.data()),
                                       static_cast<int>(dgram.size()),
                                       MSG_DONTWAIT,
                                       reinterpret_cast<sockaddr*>(&peer_addr),
                                       &peer_len);
        if (recv_len <= 0) break;

        // Short header packets — route via DCID for server (child connections),
        // or process directly for client connections (no _child_connections).
        if (!(dgram[0] & 0x80)) {
            if (!_parent && parent->_child_connections.empty()) {
                // Client-side: process packet directly on this connection
                processIncomingPacket(dgram.data(), static_cast<size_t>(recv_len));
            } else {
                static constexpr size_t CID_LEN = 8;
                if (static_cast<size_t>(recv_len) < 1 + CID_LEN) continue;
                std::vector<uint8_t> dcid(dgram.begin() + 1,
                                           dgram.begin() + 1 + CID_LEN);
                auto it = parent->_child_connections.find(dcid);
                if (it != parent->_child_connections.end() && it->second) {
                    it->second->processIncomingPacket(dgram.data(), static_cast<size_t>(recv_len));

                    // Flush child's pending flow control frames
                    if (it->second->_handshake_complete &&
                        it->second->_conn_state.load() == ConnectionState::Connected) {
                        for (auto& [sid, limit] : it->second->_pending_max_stream_data) {
                            it->second->sendMaxStreamData(sid, limit);
                        }
                        it->second->_pending_max_stream_data.clear();
                        if (it->second->_pending_max_data) {
                            it->second->sendMaxData(it->second->_pending_max_data_value);
                            it->second->_pending_max_data = false;
                        }
                    }
                }
            }
        } else {
            // Long header packet (Initial, Handshake, etc.) — route to
            // existing child connection if possible.  Must not silently
            // drop these, as they may carry handshake continuations.
            if (static_cast<size_t>(recv_len) >= 7) {
                size_t dcid_len = static_cast<size_t>(dgram[5]);
                if (6 + dcid_len <= static_cast<size_t>(recv_len)) {
                    std::vector<uint8_t> dcid(dgram.begin() + 6,
                                               dgram.begin() + 6 + dcid_len);
                    auto it = parent->_child_connections.find(dcid);
                    if (it != parent->_child_connections.end() && it->second) {
                        it->second->processIncomingPacket(dgram.data(), static_cast<size_t>(recv_len));
                    }
                    // If no match, the datagram is for a NEW connection
                    // and will be picked up by the next accept() call.
                    // We cannot create connections here, so buffer it.
                }
            }
        }
    }

    // Flush own pending flow control frames
    if (_handshake_complete && _conn_state.load() == ConnectionState::Connected) {
        for (auto& [sid, limit] : _pending_max_stream_data) {
            sendMaxStreamData(sid, limit);
        }
        _pending_max_stream_data.clear();
        if (_pending_max_data) {
            sendMaxData(_pending_max_data_value);
            _pending_max_data = false;
        }
    }

    // After processing incoming packets (which may contain ACKs),
    // check for lost packets and retransmit.
    checkLossAndRetransmit();
}

// ============================================================================
// Socket Interface: flush_out (Send queued data)
// ============================================================================

void quic::flush_out() {
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    
    // Send any queued packets
    while (!_send_queue.empty()) {
        auto& packet = _send_queue.front();
        
        try {
            buffer send_buf(reinterpret_cast<const char*>(packet.data()), packet.size());
            udp::sendData(send_buf, MSG_DONTWAIT);
        } catch (...) {
            // Socket buffer full or error - stop trying
            break;
        }
        
        _send_queue.pop_front();
    }
    
    // Flush batched flow control frames (only when handshake is complete)
    if (_handshake_complete && _conn_state.load() == ConnectionState::Connected) {
        // Send pending MAX_STREAM_DATA frames (coalesced: only latest value per stream)
        for (auto& [sid, limit] : _pending_max_stream_data) {
            sendMaxStreamData(sid, limit);
        }
        _pending_max_stream_data.clear();
        
        // Send pending MAX_DATA frame (coalesced: only latest value)
        if (_pending_max_data) {
            sendMaxData(_pending_max_data_value);
            _pending_max_data = false;
        }
    }
}

// ============================================================================
// Stream Management: Open Stream
// ============================================================================

uint64_t quic::openStream(bool bidirectional) {
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    
    // Check MAX_STREAMS limit before opening
    if (bidirectional) {
        if (_streams_bidi_opened >= _max_streams_bidi_remote) {
            NetException e;
            e[NetException::Error] << "quic::openStream: bidi stream limit reached ("
                                   << _max_streams_bidi_remote << ")";
            throw e;
        }
    } else {
        if (_streams_uni_opened >= _max_streams_uni_remote) {
            NetException e;
            e[NetException::Error] << "quic::openStream: uni stream limit reached ("
                                   << _max_streams_uni_remote << ")";
            throw e;
        }
    }
    
    uint64_t stream_id;
    
    if (bidirectional) {
        // Client-initiated bidi: 0, 4, 8, ...
        // Server-initiated bidi: 1, 5, 9, ...
        stream_id = _next_stream_id_bidi;
        _next_stream_id_bidi += 4;
        _streams_bidi_opened++;
    } else {
        // Client-initiated uni: 2, 6, 10, ...
        // Server-initiated uni: 3, 7, 11, ...
        stream_id = _next_stream_id_uni;
        _next_stream_id_uni += 4;
        _streams_uni_opened++;
    }
    
    // Adjust for client/server
    if (_is_server) {
        stream_id |= 0x01;  // Server-initiated streams have LSB = 1
    }
    
    // Create stream with peer's advertised per-stream limits
    Stream& stream = _streams[stream_id];
    stream.stream_id = stream_id;
    stream.max_data_local = 16777216;
    if (bidirectional) {
        // For streams we initiate, the remote limit is the peer's bidi_remote param
        stream.max_data_remote = _is_server
            ? _peer_initial_max_stream_data_bidi_remote
            : _peer_initial_max_stream_data_bidi_local;
    } else {
        stream.max_data_remote = _peer_initial_max_stream_data_uni;
    }
    stream.max_data_local_committed = stream.max_data_local;
    
    return stream_id;
}

// ============================================================================
// Stream Management: Close Stream
// ============================================================================

void quic::closeStream(uint64_t stream_id) {
    std::lock_guard<std::recursive_mutex> lock(quic_mtx());
    
    auto it = _streams.find(stream_id);
    if (it == _streams.end()) {
        return;
    }
    
    Stream& stream = it->second;
    
    // Send FIN if we haven't already
    if (!stream.send_fin) {
        // Send empty STREAM frame with FIN bit
        std::vector<uint8_t> stream_frame = buildStreamFrame(
            stream_id, {}, stream.send_offset, true);
        
        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);
        
        if (_Socket >= 0) {
            sendPacket(packet.data(), packet.size());
        }
        
        stream.send_fin = true;
    }
    
    // If both sides have sent FIN, remove stream
    if (stream.recv_fin && stream.send_fin) {
        _streams.erase(it);
    }
}

// ============================================================================
// TLS 1.3 Handshake: sendClientHello (wrapper for connect flow)
// ============================================================================

void quic::sendClientHello() {
    // Generate ECDHE key pair if not already done
    if (_ecdhe_public.empty()) {
        generateECDHEKeyPair();
    }
    
    // Build ClientHello TLS message
    std::vector<uint8_t> client_hello = buildClientHello();
    
    // Build CRYPTO frame containing ClientHello
    std::vector<uint8_t> crypto_frame = buildCryptoFrame(client_hello, 0);
    
    // Pad Initial packet to at least 1200 bytes (RFC 9000 requirement)
    size_t min_payload = 1200 - 100;  // Account for header overhead
    while (crypto_frame.size() < min_payload) {
        crypto_frame.push_back(0x00);  // PADDING frame
    }
    
    // Build Initial packet
    std::vector<uint8_t> packet = buildInitialPacket(crypto_frame);
    
    // Apply header protection
    applyHeaderProtection(packet, _initial_hp_client, true);
    
    // Send Initial packet
    if (_Socket >= 0) {
        sendPacket(packet.data(), packet.size());
    }
}

// ============================================================================
// TLS 1.3 Handshake: sendServerHello (Server response to ClientHello)
// ============================================================================

void quic::sendServerHello() {
    if (!_is_server) return;
    QUIC_DBG("sendServerHello: cipher=0x%04x alpn=%s", _selected_cipher, _selected_alpn.c_str());
    
    
    // Generate server ECDHE key pair
    if (_ecdhe_public.empty()) {
        generateECDHEKeyPair();
    }
    
    
    // Build ServerHello message
    std::vector<uint8_t> server_hello;
    
    // Handshake type: ServerHello (0x02)
    server_hello.push_back(0x02);
    
    // Length placeholder (3 bytes)
    size_t length_pos = server_hello.size();
    server_hello.push_back(0x00);
    server_hello.push_back(0x00);
    server_hello.push_back(0x00);
    
    // Legacy version: TLS 1.2 (0x0303)
    server_hello.push_back(0x03);
    server_hello.push_back(0x03);
    
    // Server random (32 bytes)
    _server_random.resize(32);
    fillRandomBytes(_server_random.data(), 32);
    server_hello.insert(server_hello.end(), _server_random.begin(), _server_random.end());
    
    // Legacy session ID echo - must echo the client's session_id
    server_hello.push_back(static_cast<uint8_t>(_client_session_id.size()));
    if (!_client_session_id.empty()) {
        server_hello.insert(server_hello.end(), _client_session_id.begin(), _client_session_id.end());
    }
    
    // Cipher suite: Use the negotiated cipher
    server_hello.push_back(static_cast<uint8_t>((_selected_cipher >> 8) & 0xFF));
    server_hello.push_back(static_cast<uint8_t>(_selected_cipher & 0xFF));
    
    // Legacy compression method
    server_hello.push_back(0x00);
    
    // Extensions
    std::vector<uint8_t> extensions;
    
    // Extension: supported_versions (0x002b) - TLS 1.3
    extensions.push_back(0x00);
    extensions.push_back(0x2b);
    extensions.push_back(0x00);
    extensions.push_back(0x02);  // Length
    extensions.push_back(0x03);
    extensions.push_back(0x04);  // TLS 1.3
    
    // Extension: key_share (0x0033)
    extensions.push_back(0x00);
    extensions.push_back(0x33);
    extensions.push_back(0x00);
    extensions.push_back(0x24);  // Length: 36 bytes
    extensions.push_back(0x00);
    extensions.push_back(0x1d);  // x25519
    extensions.push_back(0x00);
    extensions.push_back(0x20);  // 32 bytes
    extensions.insert(extensions.end(), _ecdhe_public.begin(), _ecdhe_public.end());
    
    // Extensions length
    server_hello.push_back(static_cast<uint8_t>(extensions.size() >> 8));
    server_hello.push_back(static_cast<uint8_t>(extensions.size()));
    server_hello.insert(server_hello.end(), extensions.begin(), extensions.end());
    
    // Fill in length
    size_t msg_len = server_hello.size() - 4;
    server_hello[length_pos] = static_cast<uint8_t>(msg_len >> 16);
    server_hello[length_pos + 1] = static_cast<uint8_t>(msg_len >> 8);
    server_hello[length_pos + 2] = static_cast<uint8_t>(msg_len);
    
    // Add to transcript
    _tls_transcript.insert(_tls_transcript.end(), server_hello.begin(), server_hello.end());
    
    // Build ACK frame for client's Initial packets
    std::vector<uint8_t> ack_frame = buildAckFrame(_initial_pn_recv, 0, _initial_pn_recv_ranges);
    
    // Build CRYPTO frame
    std::vector<uint8_t> crypto_frame = buildCryptoFrame(server_hello, 0);
    
    // Combine ACK + CRYPTO frames
    std::vector<uint8_t> payload;
    payload.insert(payload.end(), ack_frame.begin(), ack_frame.end());
    payload.insert(payload.end(), crypto_frame.begin(), crypto_frame.end());

    // Pad Initial packet to minimum 1200 bytes (RFC 9000)
    // Use the current CID sizes to compute header length and total packet size.
    const size_t pn_len = 4;
    const size_t token_len = 0;
    size_t padding_guard = 0;
    while (padding_guard++ < 4) {
        size_t total_len = pn_len + payload.size() + 16;
        size_t len_varint = varIntSize(total_len);
        size_t header_len = 1 + 4 + 1 + _remote_cid.size() + 1 + _local_cid.size() +
                            varIntSize(token_len) + len_varint + pn_len;
        size_t packet_len = header_len + payload.size() + 16;
        if (packet_len >= 1200) {
            break;
        }
        size_t pad_len = 1200 - packet_len;
        payload.insert(payload.end(), pad_len, 0x00);
    }
    
    // Build Initial packet for ServerHello (with ACK + CRYPTO)
    std::vector<uint8_t> packet = buildInitialPacket(payload);
    
    applyHeaderProtection(packet, _initial_hp_server, true);
    
    // Send Initial packet with ServerHello
    if (_Socket >= 0) {
        ssize_t sent = sendPacket(packet.data(), packet.size());
        std::cerr << "[QUIC] ServerHello sent=" << sent << " bytes (packet=" << packet.size() << ")" << std::endl;
        if (sent < 0) {
            std::cerr << "[QUIC] ServerHello send failed: errno=" << errno << std::endl;
        }
    }
    
    
    // Derive handshake keys from shared secret
    deriveHandshakeKeys(_ecdhe_shared);
    QUIC_DBG("sendServerHello: handshake keys derived, sending flight...");
    
    
    // Send EncryptedExtensions, Certificate, CertificateVerify, Finished
    sendServerHandshakeFlight();
}

// ============================================================================
// TLS 1.3 Server Handshake: Send Encrypted Flight
// ============================================================================

void quic::sendServerHandshakeFlight() {
    QUIC_DBG("sendServerHandshakeFlight: starting");
    std::vector<uint8_t> flight;
    
    // EncryptedExtensions (0x08)
    std::vector<uint8_t> enc_ext;
    enc_ext.push_back(0x08);  // Type
    
    // Build extensions data
    std::vector<uint8_t> ext_data;
    
    // Extension 1: ALPN (0x0010) - negotiate h3 for HTTP/3
    if (!_selected_alpn.empty()) {
        ext_data.push_back(0x00);
        ext_data.push_back(0x10);  // ALPN extension type
        // Extension length: 2 (list len) + 1 (proto len) + proto.size()
        size_t alpn_ext_len = 2 + 1 + _selected_alpn.size();
        ext_data.push_back(static_cast<uint8_t>(alpn_ext_len >> 8));
        ext_data.push_back(static_cast<uint8_t>(alpn_ext_len));
        // ALPN list length
        size_t alpn_list_len = 1 + _selected_alpn.size();
        ext_data.push_back(static_cast<uint8_t>(alpn_list_len >> 8));
        ext_data.push_back(static_cast<uint8_t>(alpn_list_len));
        // Protocol length and value
        ext_data.push_back(static_cast<uint8_t>(_selected_alpn.size()));
        ext_data.insert(ext_data.end(), _selected_alpn.begin(), _selected_alpn.end());
        
    }
    
    // Extension 2: quic_transport_parameters (0x0039)
    std::vector<uint8_t> transport_params = buildTransportParameters();
    ext_data.push_back(0x00);
    ext_data.push_back(0x39);
    ext_data.push_back(static_cast<uint8_t>(transport_params.size() >> 8));
    ext_data.push_back(static_cast<uint8_t>(transport_params.size()));
    ext_data.insert(ext_data.end(), transport_params.begin(), transport_params.end());
    
    // EncryptedExtensions message: Type(1) + Length(3) + Extensions_length(2) + Extensions_data
    // Message length = 2 (extensions length field) + extensions data size
    size_t ee_msg_len = 2 + ext_data.size();
    enc_ext.push_back(static_cast<uint8_t>(ee_msg_len >> 16));
    enc_ext.push_back(static_cast<uint8_t>(ee_msg_len >> 8));
    enc_ext.push_back(static_cast<uint8_t>(ee_msg_len));
    
    // Extensions length field (2 bytes, big-endian)
    enc_ext.push_back(static_cast<uint8_t>(ext_data.size() >> 8));
    enc_ext.push_back(static_cast<uint8_t>(ext_data.size()));
    enc_ext.insert(enc_ext.end(), ext_data.begin(), ext_data.end());
    
    flight.insert(flight.end(), enc_ext.begin(), enc_ext.end());
    _tls_transcript.insert(_tls_transcript.end(), enc_ext.begin(), enc_ext.end());
    
    // Certificate (0x0b) - if we have a certificate
    if (_selected_cert != nullptr) {
        std::vector<uint8_t> cert_msg;
        cert_msg.push_back(0x0b);  // Type
        
        // Certificate request context (empty for server)
        std::vector<uint8_t> cert_body;
        cert_body.push_back(0x00);  // Context length = 0
        
        // Build certificate entries: leaf cert + chain certs
        std::vector<uint8_t> cert_entries;
        
        // Leaf certificate entry
        const auto& cert_der = _selected_cert->cert.derData();
        cert_entries.push_back(static_cast<uint8_t>(cert_der.size() >> 16));
        cert_entries.push_back(static_cast<uint8_t>(cert_der.size() >> 8));
        cert_entries.push_back(static_cast<uint8_t>(cert_der.size()));
        cert_entries.insert(cert_entries.end(), cert_der.begin(), cert_der.end());
        cert_entries.push_back(0x00);  // Extensions length (2 bytes)
        cert_entries.push_back(0x00);
        
        // Chain certificates (CA certs from selected bundle)
        for (const auto& chain_cert : _selected_cert->chain) {
            cert_entries.push_back(static_cast<uint8_t>(chain_cert.size() >> 16));
            cert_entries.push_back(static_cast<uint8_t>(chain_cert.size() >> 8));
            cert_entries.push_back(static_cast<uint8_t>(chain_cert.size()));
            cert_entries.insert(cert_entries.end(), chain_cert.begin(), chain_cert.end());
            cert_entries.push_back(0x00);  // Extensions length (2 bytes)
            cert_entries.push_back(0x00);
        }
        
        // Certificate list length (3 bytes) - covers all entries
        cert_body.push_back(static_cast<uint8_t>(cert_entries.size() >> 16));
        cert_body.push_back(static_cast<uint8_t>(cert_entries.size() >> 8));
        cert_body.push_back(static_cast<uint8_t>(cert_entries.size()));
        cert_body.insert(cert_body.end(), cert_entries.begin(), cert_entries.end());
        
        // Certificate message length
        cert_msg.push_back(static_cast<uint8_t>(cert_body.size() >> 16));
        cert_msg.push_back(static_cast<uint8_t>(cert_body.size() >> 8));
        cert_msg.push_back(static_cast<uint8_t>(cert_body.size()));
        cert_msg.insert(cert_msg.end(), cert_body.begin(), cert_body.end());
        
        
        flight.insert(flight.end(), cert_msg.begin(), cert_msg.end());
        _tls_transcript.insert(_tls_transcript.end(), cert_msg.begin(), cert_msg.end());
        
        // CertificateVerify (0x0f)
        std::vector<uint8_t> cert_verify;
        cert_verify.push_back(0x0f);
        
        // Signature algorithm selection based on cipher suite
        const bool prefer_sha384 = (_selected_cipher == 0x1302);
        const bool use_sha384_cv = prefer_sha384 && _sigalg_rsa_pss_sha384;
        std::vector<uint8_t> cv_body;
        
        if (use_sha384_cv) {
            cv_body.push_back(0x08);
            cv_body.push_back(0x05);  // rsa_pss_rsae_sha384
        } else {
            cv_body.push_back(0x08);
            cv_body.push_back(0x04);  // rsa_pss_rsae_sha256
        }
        
        // Build content to sign: 64 spaces + context + 0x00 + transcript hash
        // The transcript hash MUST use the cipher suite's hash function
        std::vector<uint8_t> sign_content;
        sign_content.reserve(64 + 34 + 1 + 64);  // 64 spaces + context + 0x00 + max hash
        sign_content.resize(64, 0x20);  // 64 spaces
        const char* context = "TLS 1.3, server CertificateVerify";
        sign_content.insert(sign_content.end(), context, context + strlen(context));
        sign_content.push_back(0x00);
        
        // Use cipher-appropriate hash for transcript
        std::vector<uint8_t> cv_transcript_hash = use_sha384_cv ? 
            sha384_hash(_tls_transcript) : sha256_hash(_tls_transcript);
        sign_content.insert(sign_content.end(), cv_transcript_hash.begin(), cv_transcript_hash.end());
        
        // Use appropriate RSA-PSS signing based on cipher suite
        std::vector<uint8_t> signature;
        if (use_sha384_cv) {
            signature = rsa_pss_sha384::sign(
                _selected_cert->rsa_key, sign_content);
        } else {
            signature = rsa_pss_sha256::sign(
                _selected_cert->rsa_key, sign_content);
        }
        
        // Signature length and data
        cv_body.push_back(static_cast<uint8_t>(signature.size() >> 8));
        cv_body.push_back(static_cast<uint8_t>(signature.size()));
        cv_body.insert(cv_body.end(), signature.begin(), signature.end());
        
        // CertificateVerify length
        cert_verify.push_back(static_cast<uint8_t>(cv_body.size() >> 16));
        cert_verify.push_back(static_cast<uint8_t>(cv_body.size() >> 8));
        cert_verify.push_back(static_cast<uint8_t>(cv_body.size()));
        cert_verify.insert(cert_verify.end(), cv_body.begin(), cv_body.end());
        
        flight.insert(flight.end(), cert_verify.begin(), cert_verify.end());
        _tls_transcript.insert(_tls_transcript.end(), cert_verify.begin(), cert_verify.end());
    }
    
    // Finished (0x14)
    std::vector<uint8_t> finished;
    finished.push_back(0x14);
    
    // Compute verify_data using cipher-appropriate hash:
    // finished_key = HKDF-Expand-Label(server_handshake_traffic_secret, "finished", "", hash_len)
    // verify_data = HMAC(finished_key, Transcript-Hash)
    const bool use_sha384 = (_selected_cipher == 0x1302);  // TLS_AES_256_GCM_SHA384
    const size_t hash_len = use_sha384 ? 48 : 32;
    const uint16_t cipher_suite = use_sha384 ? 0x1302 : 0x1301;
    
    // Use TLS class HKDF method
    std::vector<uint8_t> finished_key = netplus::tls::hkdf_expand_label(
        _s_hs_traffic_secret, "finished", {}, hash_len, cipher_suite);
    
    // Compute transcript hash and verify_data
    std::vector<uint8_t> transcript_hash = use_sha384 
        ? sha384_hash(_tls_transcript)
        : sha256_hash(_tls_transcript);
    std::vector<uint8_t> verify_data = use_sha384
        ? hmac_sha384(finished_key, transcript_hash)
        : hmac_sha256(finished_key, transcript_hash);
    
    // Length (3 bytes) - 48 bytes for SHA-384, 32 bytes for SHA-256
    finished.push_back(0x00);
    finished.push_back(0x00);
    finished.push_back(static_cast<uint8_t>(hash_len));
    
    // Add verify_data
    finished.insert(finished.end(), verify_data.begin(), verify_data.end());
    
    flight.insert(flight.end(), finished.begin(), finished.end());
    _tls_transcript.insert(_tls_transcript.end(), finished.begin(), finished.end());
    
    // Fragment the flight into multiple Handshake packets, each fitting within
    // 1200-byte UDP datagrams (RFC 9000 §14 - max datagram size before PMTUD).
    // Handshake header overhead: 1 (first byte) + 4 (version) + 1 (dcid len) +
    //   dcid + 1 (scid len) + scid + 2 (length varint, ≤1200) + 4 (PN) = ~13+CIDs
    // CRYPTO frame overhead: 1 (type) + varint(offset) + varint(len) ≈ 3-7 bytes
    // AEAD tag: 16 bytes
    const size_t MAX_DGRAM = 1200;
    const size_t hs_header_len = 1 + 4 + 1 + _remote_cid.size() + 1 + _local_cid.size() + 2 + 4;
    const size_t aead_tag_len = 16;
    // Conservative max CRYPTO frame overhead: type(1) + offset(4) + length(2) = 7
    const size_t crypto_overhead = 7;
    const size_t max_crypto_data = MAX_DGRAM - hs_header_len - aead_tag_len - crypto_overhead;

    size_t flight_offset = 0;
    size_t fragment_num = 0;
    
    // Clear any previous flight storage and prepare for retransmission
    _hs_flight_packets.clear();
    
    while (flight_offset < flight.size()) {
        size_t chunk_size = std::min(max_crypto_data, flight.size() - flight_offset);
        
        std::vector<uint8_t> chunk(flight.begin() + flight_offset,
                                    flight.begin() + flight_offset + chunk_size);
        std::vector<uint8_t> crypto_frame = buildCryptoFrame(chunk, flight_offset);
        std::vector<uint8_t> packet = buildHandshakePacket(crypto_frame);
        
        applyHeaderProtection(packet, _hs_hp_server);
        
        // Store for retransmission before sending
        _hs_flight_packets.push_back(packet);
        
        if (_Socket >= 0) {
            sendPacket(packet.data(), packet.size());
        }
        
        flight_offset += chunk_size;
        fragment_num++;
    }
    
    // Record send time for retransmission timer
    _hs_last_send = std::chrono::steady_clock::now();
    _hs_retransmit_count = 0;
    QUIC_DBG("sendServerHandshakeFlight: sent %zu fragments, flight=%zu bytes\", deriving app keys...",
             fragment_num, flight.size());
    
    // Derive application keys
    deriveApplicationKeys();
    QUIC_DBG("sendServerHandshakeFlight: app keys derived, done");
}

// ============================================================================
// TLS 1.3 Handshake: Compute Shared Secret from Client Key Share
// ============================================================================

void quic::computeSharedSecret(const std::vector<uint8_t>& peer_public_key) {
    if (peer_public_key.size() != 32) return;
    
    _ecdhe_shared.resize(32);
    std::vector<uint8_t> priv_vec(_ecdhe_private.begin(), _ecdhe_private.end());
    scalarmult_curve25519(_ecdhe_shared, priv_vec, peer_public_key);
}

#ifdef Windows
// ============================================================================
// Windows-specific: AcceptEx-based accept (not used for QUIC/UDP)
// ============================================================================

void quic::accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& csock) {
    // QUIC runs over UDP and does not use AcceptEx
    (void)lpfnAcceptEx;
    (void)csock;
}

void quic::prime_read() {
    DWORD bytesRecv = 0;
    DWORD dwFlags = 0;

    if (!_ReadBuffer)
        _ReadBuffer.reset(new buffer);

    std::memset(&_ReadBuffer->overlapped, 0, sizeof(WSAOVERLAPPED));
    _ReadBuffer->operation = OP_READ;

    int ret = ::WSARecv(_Socket, &_ReadBuffer->data, 1, &bytesRecv, &dwFlags,
        &_ReadBuffer->overlapped, nullptr);

    if (ret == SOCKET_ERROR) {
        int err = ::WSAGetLastError();
        if (err != WSA_IO_PENDING) {
            NetException e;
            e[NetException::Error] << "quic::prime_read failed: " << err;
            throw e;
        }
    }
}
#endif

} // namespace netplus
