/*******************************************************************************
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.
*******************************************************************************/

// Shared checksum/random-payload helpers and request handler for the
// HTTP/1.1, HTTP/2 and HTTP/3 checksum integrity tests
// (http1_checksum_test.cpp, http2_checksum_test.cpp, http3_checksum_test.cpp).
//
// The three protocol tests each drive a real client<->server exchange over
// their own wire format, but they all exercise the SAME server-side
// RequestEvent, because HttpEvent::RequestEvent() gets the request body in
// curreq.RecvData and lets a response header be attached via
// setHeaderData() identically regardless of protocol -- HTTP/2 and HTTP/3
// forward extra HttpResponse headers to the wire via the library's internal
// ":res-*" pseudo-header convention (see HttpResponse::_storeResponseInfo /
// httpd.cpp's _finishH2Dispatch), so a single handler genuinely covers all
// three protocols.

#pragma once

#include <cstdint>
#include <iostream>
#include <string>
#include <thread>
#include <vector>

#include <netplus/crypto/sha.h>
#include <netplus/random.h>

#include "http.h"
#include "httpd.h"

namespace checksumtest {

inline std::string sha256Hex(const char *data, size_t len) {
    std::vector<uint8_t> buf(reinterpret_cast<const uint8_t *>(data),
                              reinterpret_cast<const uint8_t *>(data) + len);
    std::vector<uint8_t> digest = netplus::sha256_hash(buf);
    static const char *hexd = "0123456789abcdef";
    std::string out;
    out.reserve(digest.size() * 2);
    for (uint8_t b : digest) {
        out.push_back(hexd[b >> 4]);
        out.push_back(hexd[b & 0x0f]);
    }
    return out;
}

inline std::string sha256Hex(const std::vector<char> &data) {
    return sha256Hex(data.data(), data.size());
}

inline std::vector<char> randomBuffer(size_t len) {
    std::vector<char> buf(len);
    if (len)
        netplus::fillRandomBytes(reinterpret_cast<uint8_t *>(buf.data()), len);
    return buf;
}

// Small-to-large sweep: an empty body, a single byte, a few KB, one that
// spans several TCP/TLS records or H2/H3 frames (64KB) and one that is
// guaranteed to exhaust the HTTP/2 64KB-ish default flow-control window and
// force multiple round trips (1MB).
inline const std::vector<size_t> &sweepSizes() {
    static const std::vector<size_t> sizes = {0, 1, 4096, 65536, 1048576};
    return sizes;
}

// GET  /gen/<n>  -> <n> random bytes, with X-Sha256 holding their checksum.
// POST /echo     -> echoes the request body back, with X-Sha256 holding the
//                   checksum of what the server actually received (so
//                   upload integrity is verified independently of what the
//                   client remembers sending, and download integrity is
//                   verified by the client comparing the echoed bytes to
//                   its own copy).
class ChecksumEvent : public libhttppp::HttpEvent {
public:
    explicit ChecksumEvent(std::vector<netplus::socket *> serversocket)
        : libhttppp::HttpEvent(serversocket) {}

    void RequestEvent(libhttppp::HttpRequest &req, const int, ULONG_PTR) override {
        try {
            const std::string &url = req.getRequestURL();

            if (url.rfind("/gen/", 0) == 0) {
                size_t len = 0;
                try { len = static_cast<size_t>(std::stoull(url.substr(5))); }
                catch (...) { len = 0; }

                std::vector<char> body = randomBuffer(len);
                std::string hash = sha256Hex(body);

                libhttppp::HttpResponse res;
                res.setContentType("application/octet-stream");
                res.setHeaderData("x-sha256")->push_back(hash);
                res.send(req, body, static_cast<int>(body.size()));
                return;
            }

            if (url == "/echo") {
                std::vector<char> body(req.RecvData.data(),
                                        req.RecvData.data() + req.RecvData.size());
                std::string hash = sha256Hex(body);

                libhttppp::HttpResponse res;
                res.setContentType("application/octet-stream");
                res.setHeaderData("x-sha256")->push_back(hash);
                res.send(req, body, static_cast<int>(body.size()));
                return;
            }

            libhttppp::HttpResponse res;
            res.setState("404 Not Found");
            res.send(req, std::string("not found"), 9);
        } catch (libhttppp::HTTPException &e) {
            std::cerr << "[ChecksumEvent] " << e.what() << std::endl;
        }
    }
};

// RAII stop/join for a server thread running HttpEvent::runEventloop().
// Letting such a thread outlive its netplus::event/socket objects (e.g. via
// detach()) races epoll teardown (netplus::poll's CloseEventHandler)
// against object/process teardown and can segfault; joining it -- on every
// exit path, including exceptions -- avoids that race. Declare this AFTER
// the socket/event objects it targets so it destructs (and joins) BEFORE
// them, in a scope that has already started the thread.
struct ServerThreadGuard {
    std::thread *t;
    ~ServerThreadGuard() {
        if (t && t->joinable()) {
            netplus::event::Running = false;
            t->join();
        }
    }
};

} // namespace checksumtest
