//
// Created by WolverinDEV on 04/05/2020.
//

#include <event.h>
#include <cassert>
#include <netinet/tcp.h>
#include <log/LogUtils.h>
#include <misc/net.h>
#include "./LocalFileProvider.h"
#include "./duration_utils.h"

#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
    #define TCP_NOPUSH TCP_CORK
#endif

using namespace ts::server::file;
using namespace ts::server::file::transfer;

#define MAX_HTTP_HEADER_SIZE (4096)

inline void add_network_event(FileClient& transfer, event* ev, bool& ev_throttle_readd_flag, bool ignore_bandwidth) {
    timeval tv{0, 1}, *ptv{nullptr};
    {
        auto timeout = transfer.networking.disconnect_timeout;
        if(timeout.time_since_epoch().count() > 0) {
            auto now = std::chrono::system_clock::now();
            if(now < timeout) {
                auto duration = timeout - now;

                auto seconds = std::chrono::duration_cast<std::chrono::seconds>(timeout - now);
                duration -= seconds;

                auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(timeout - now);

                tv.tv_sec = seconds.count();
                tv.tv_usec = microseconds.count();
            }
            ptv = &tv;
        }
    }

    if(!ignore_bandwidth) {
        if(ev_throttle_readd_flag) return; /* we're already throttled */

        timeval ttv{};
        if(transfer.networking.throttle.should_throttle(ttv)) {
            if(transfer.networking.event_throttle)
                event_add(transfer.networking.event_throttle, &ttv);
            ev_throttle_readd_flag = true;
            return;
        }
    }

    event_add(ev, ptv);
}

void FileClient::add_network_read_event(bool ignore_bandwidth) {
    std::shared_lock slock{this->state_mutex};

    switch (this->state) {
        case STATE_DISCONNECTING:
        case STATE_DISCONNECTED:
            return;

        case STATE_AWAITING_KEY:
        case STATE_TRANSFERRING:
            break;

        default:
            assert(false);
            return;
    }
    if(this->state != STATE_AWAITING_KEY && this->state != STATE_TRANSFERRING)
        return;

    add_network_event(*this, this->networking.event_read, this->networking.add_event_read, ignore_bandwidth);
}

void FileClient::add_network_write_event(bool ignore_bandwidth) {
    std::shared_lock slock{this->state_mutex};
    this->add_network_write_event_nolock(ignore_bandwidth);
}

void FileClient::add_network_write_event_nolock(bool ignore_bandwidth) {
    switch (this->state) {
        case STATE_DISCONNECTED:
            return;

        case STATE_DISCONNECTING:
            if(this->transfer && this->transfer->direction == Transfer::DIRECTION_UPLOAD)
                return;
            /* flush our write buffer */
            break;

        case STATE_AWAITING_KEY:
            if(this->networking.protocol != FileClient::PROTOCOL_HTTPS) {
                assert(false);
                return;
            }
            break;

        case STATE_TRANSFERRING:
            break;

        default:
            assert(false);
            break;
    }

    add_network_event(*this, this->networking.event_write, this->networking.add_event_write, ignore_bandwidth);
}

bool FileClient::send_file_bytes(const void *snd_buffer, size_t size) {
    if(this->networking.protocol == FileClient::PROTOCOL_TS_V1) {
        return this->enqueue_buffer_bytes(snd_buffer, size);
    } else if(this->networking.protocol == FileClient::PROTOCOL_HTTPS) {
        this->networking.pipe_ssl.send(pipes::buffer_view{snd_buffer, size});
        return this->buffer.bytes > TRANSFER_MAX_CACHED_BYTES;
    } else {
        return false;
    }
}

bool FileClient::enqueue_buffer_bytes(const void *snd_buffer, size_t size) {
    auto tbuffer = allocate_buffer(size);
    tbuffer->length = size;
    tbuffer->offset = 0;
    memcpy(tbuffer->data, snd_buffer, size);

    size_t buffer_size;
    {
        std::lock_guard block{this->buffer.mutex};
        *this->buffer.buffer_tail = tbuffer;
        this->buffer.buffer_tail = &tbuffer->next;

        buffer_size = (this->buffer.bytes += size);
    }

    this->add_network_write_event(false);
    return buffer_size > TRANSFER_MAX_CACHED_BYTES;
}

NetworkingStartResult LocalFileTransfer::start_networking() {
    assert(!this->network.active);

    this->network.active = true;
    this->network.event_base = event_base_new();
    if(!this->network.event_base) return NetworkingStartResult::OUT_OF_MEMORY;

    bool bound{false};
    for(auto& binding : this->network.bindings) {
        binding->file_descriptor = socket(binding->address.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
        if(!binding->file_descriptor) {
            logWarning(LOG_FT, "Failed to allocate socket for {}: {}/{}", binding->hostname, errno, strerror(errno));
            continue;
        }


        int enable = 1, disabled = 0;

        if (setsockopt(binding->file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
            logWarning(LOG_FT, "Failed to activate SO_REUSEADDR for binding {} ({} | {})", binding->hostname, errno, strerror(errno));

        if(setsockopt(binding->file_descriptor, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0)
            logWarning(LOG_FT, "Failed to deactivate TCP_NOPUSH for binding {} ({} | {})", binding->hostname, errno, strerror(errno));

        if(binding->address.ss_family == AF_INET6) {
            if(setsockopt(binding->file_descriptor, IPPROTO_IPV6, IPV6_V6ONLY, &enable, sizeof(int)) < 0)
                logWarning(LOG_FT, "Failed to activate IPV6_V6ONLY for IPv6 binding {} ({} | {})", binding->hostname, errno, strerror(errno));
        }
        if(fcntl(binding->file_descriptor, F_SETFD, FD_CLOEXEC) < 0)
            logWarning(LOG_FT, "Failed to set flag FD_CLOEXEC for binding {} ({} | {})", binding->hostname, errno, strerror(errno));


        if (bind(binding->file_descriptor, (struct sockaddr *) &binding->address, sizeof(binding->address)) < 0) {
            logError(LOG_FT, "Failed to bind server to {}. (Failed to bind socket: {} | {})", binding->hostname, errno, strerror(errno));
            goto reset_binding;
        }

        if (listen(binding->file_descriptor, 8) < 0) {
            logError(LOG_FT, "Failed to bind server to {}. (Failed to listen: {} | {})", binding->hostname, errno, strerror(errno));
            goto reset_binding;
        }

        binding->handle = this;
        binding->accept_event = event_new(this->network.event_base, binding->file_descriptor, (unsigned) EV_READ | (unsigned) EV_PERSIST, &LocalFileTransfer::callback_transfer_network_accept, &*binding);
        if(!binding->accept_event)
            goto reset_binding;

        event_add(binding->accept_event, nullptr);
        logMessage(LOG_FT, "Started to listen on {}:{}", binding->hostname, net::port(binding->address));

        bound = true;
        continue;

        reset_binding:
        if(binding->accept_event) {
            event_free(binding->accept_event);
            binding->accept_event = nullptr;
        }

        if(binding->file_descriptor > 0)
            ::close(binding->file_descriptor);
        binding->file_descriptor = 0;

        binding->handle = nullptr;
    }
    if(!bound) {
        event_base_free(std::exchange(this->network.event_base, nullptr));
        return NetworkingStartResult::NO_BINDINGS;
    }

    this->network.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_network, this);
    return NetworkingStartResult::SUCCESS;
}

void LocalFileTransfer::shutdown_networking() {
    if(!this->network.active) return;
    this->network.active = false;

    for(auto& binding : this->network.bindings) {
        if(binding->accept_event) {
            event_del_block(binding->accept_event);
            event_free(binding->accept_event);
            binding->accept_event = nullptr;
        }

        if(binding->file_descriptor > 0)
            ::close(binding->file_descriptor);
        binding->file_descriptor = 0;

        binding->handle = nullptr;
    }

    {
        std::unique_lock tlock{this->transfers_mutex};
        auto transfers = this->transfers_;
        tlock.unlock();
        for(const auto& transfer : transfers) {
            std::unique_lock slock{transfer->state_mutex};
            this->disconnect_client(transfer, slock, false);
        }
    }

    auto ev_base = std::exchange(this->network.event_base, nullptr);
    if(ev_base)
        event_base_loopbreak(ev_base);

    if(this->network.dispatch_thread.joinable())
        this->network.dispatch_thread.join();

    if(ev_base)
        event_base_free(ev_base);
}

void LocalFileTransfer::dispatch_loop_network(void *provider_ptr) {
    auto provider = reinterpret_cast<LocalFileTransfer*>(provider_ptr);

    while(provider->network.active) {
        assert(provider->network.event_base);
        event_base_loop(provider->network.event_base, EVLOOP_NO_EXIT_ON_EMPTY);
    }
}

NetworkInitializeResult LocalFileTransfer::initialize_networking(const std::shared_ptr<FileClient> &client, int file_descriptor) {
    client->networking.file_descriptor = file_descriptor;

    client->networking.event_read = event_new(this->network.event_base, file_descriptor, EV_READ, &LocalFileTransfer::callback_transfer_network_read, &*client);
    client->networking.event_write = event_new(this->network.event_base, file_descriptor, EV_WRITE, &LocalFileTransfer::callback_transfer_network_write, &*client);
    client->networking.event_throttle = evtimer_new(this->network.event_base, &LocalFileTransfer::callback_transfer_network_throttle, &*client);

    if(!client->networking.event_read || !client->networking.event_write || !client->networking.event_throttle)
        goto oom_exit;

    client->add_network_read_event(true);

    client->timings.connected = std::chrono::system_clock::now();
    client->timings.last_write = client->timings.connected;
    client->timings.last_read = client->timings.connected;

    return NetworkInitializeResult::SUCCESS;

    oom_exit:
    if(auto event{std::exchange(client->networking.event_read, nullptr)}; event)
        event_free(event);
    if(auto event{std::exchange(client->networking.event_write, nullptr)}; event)
        event_free(event);
    if(auto event{std::exchange(client->networking.event_throttle, nullptr)}; event)
        event_free(event);

    return NetworkInitializeResult::OUT_OF_MEMORY;
}

void LocalFileTransfer::finalize_networking(const std::shared_ptr<FileClient> &client, std::unique_lock<std::shared_mutex>& state_lock) {
    assert(state_lock.owns_lock());

    auto ev_read = std::exchange(client->networking.event_read, nullptr);
    auto ev_write = std::exchange(client->networking.event_write, nullptr);
    auto ev_throttle = std::exchange(client->networking.event_throttle, nullptr);

    state_lock.unlock();
    if (ev_read) {
        event_del_block(ev_read);
        event_free(ev_read);
    }
    if (ev_write) {
        event_del_block(ev_write);
        event_free(ev_write);
    }
    if (ev_throttle) {
        event_del_block(ev_throttle);
        event_free(ev_throttle);
    }
    state_lock.lock();

    if (client->networking.file_descriptor > 0) {
        ::shutdown(client->networking.file_descriptor, SHUT_RDWR);
        ::close(client->networking.file_descriptor);
    }
    client->networking.file_descriptor = 0;
}

#if 0
void dp_log(void* ptr, pipes::Logger::LogLevel level, const std::string& name, const std::string& message, ...) {
    auto max_length = 1024 * 8;
    char buffer[max_length];

    va_list args;
    va_start(args, message);
    max_length = vsnprintf(buffer, max_length, message.c_str(), args);
    va_end(args);

    debugMessage(LOG_GENERAL, "[{}][{}] {}", level, name, std::string{buffer});
}
#endif


bool LocalFileTransfer::initialize_client_ssl(const std::shared_ptr<FileClient> &client) {
    std::string error;

    auto& ssl_pipe = client->networking.pipe_ssl;

    if(!ssl_pipe.initialize(this->ssl_options_, error)) {
        logWarning(0, "{} Failed to initialize client SSL pipe ({}). Disconnecting client.", client->log_prefix(), error);

        std::unique_lock slock{client->state_mutex};
        client->handle->disconnect_client(client->shared_from_this(), slock, false);
        return false;
    }

#if 0
    auto logger = std::make_shared<pipes::Logger>();
    logger->callback_log = dp_log;
    ssl_pipe.logger(logger);
#endif

    ssl_pipe.direct_process(pipes::PROCESS_DIRECTION_IN, true);
    ssl_pipe.direct_process(pipes::PROCESS_DIRECTION_OUT, true);
    ssl_pipe.callback_initialized = [client] {
        logTrace(LOG_FT, "{} SSL layer has been initialized", client->log_prefix());
    };

    ssl_pipe.callback_data([&, client](const pipes::buffer_view& message) {
        client->handle->handle_transfer_read(client, message.data_ptr<char>(), message.length());
    });

    ssl_pipe.callback_error([client](int error, const std::string & error_detail) {
        logMessage(LOG_FT, "{} Received SSL error ({}/{}). Closing connection.", client->log_prefix(), error, error_detail);

        std::unique_lock slock{client->state_mutex};
        client->handle->disconnect_client(client->shared_from_this(), slock, false);
    });

    ssl_pipe.callback_write([client](const pipes::buffer_view& buffer) {
        client->enqueue_buffer_bytes(buffer.data_ptr(), buffer.length());
        client->add_network_write_event(false);
    });

    return true;
}

void LocalFileTransfer::finalize_client_ssl(const std::shared_ptr<FileClient> &client) {
    auto& ssl_pipe = client->networking.pipe_ssl;

    ssl_pipe.callback_initialized = []{};
    ssl_pipe.callback_write([](const pipes::buffer_view&){});
    ssl_pipe.callback_error([](auto, const auto&){});
    ssl_pipe.callback_data([](const auto&){});
}

void LocalFileTransfer::callback_transfer_network_accept(int fd, short, void *ptr_binding) {
    auto binding = reinterpret_cast<NetworkBinding*>(ptr_binding);
    auto transfer = binding->handle;

    sockaddr_storage address{};
    socklen_t address_length{sizeof(address)};
    auto client_fd = ::accept4(fd, reinterpret_cast<sockaddr*>(&address), &address_length, SOCK_NONBLOCK);
    if(client_fd <= 0) {
        /* TODO: Reserve one file descriptor in case of out of file descriptors (see current implementation) */
        logError(LOG_FT, "Failed to accept new client: {}/{}", errno, strerror(errno));
        return;
    }

    auto client = std::make_shared<FileClient>(transfer);
    memcpy(&client->networking.address, &address, sizeof(sockaddr_storage));

    logMessage(LOG_FT, "{} Connection received.", client->log_prefix());
    auto ninit = transfer->initialize_networking(client, client_fd);
    switch(ninit) {
        case NetworkInitializeResult::SUCCESS: {
            std::lock_guard tlock{transfer->transfers_mutex};
            transfer->transfers_.push_back(std::move(client));
            return;
        }
        case NetworkInitializeResult::OUT_OF_MEMORY:
            client->state = FileClient::STATE_DISCONNECTED; /* required else the deallocate assert will fail */
            logError(LOG_FT, "{} Failed to initialize transfer client because we ran out of memory. Closing connection.",  client->log_prefix());
            ::close(client_fd);
            return;

        default:
            client->state = FileClient::STATE_DISCONNECTED; /* required else the deallocate assert will fail */
            logError(LOG_FT, "{} Failed to initialize transfer client. Closing connection.", client->log_prefix());
            ::close(client_fd);
            return;
    }
}

void LocalFileTransfer::callback_transfer_network_throttle(int, short, void *ptr_transfer) {
    auto transfer = reinterpret_cast<FileClient*>(ptr_transfer);

    if(std::exchange(transfer->networking.add_event_write, false))
        transfer->add_network_write_event(true);

    if(std::exchange(transfer->networking.add_event_read, false))
        transfer->add_network_read_event(true);
}

void LocalFileTransfer::callback_transfer_network_read(int fd, short events, void *ptr_transfer) {
    auto transfer = reinterpret_cast<FileClient*>(ptr_transfer);

    if((unsigned) events & (unsigned) EV_TIMEOUT) {
        /* should never happen, receive timeouts are done via the client tick */
    }

    if((unsigned) events & (unsigned) EV_READ) {
        constexpr size_t buffer_size{4096};
        char buffer[buffer_size];

        while(true) {
            const auto max_read_buffer = transfer->networking.throttle.bytes_left();
            if(!max_read_buffer) break; /* network throttle */

            auto read = ::recv(fd, buffer, std::min(buffer_size, std::min(max_read_buffer, transfer->networking.max_read_buffer_size)), MSG_NOSIGNAL | MSG_DONTWAIT);
            //logTrace(0, "Read {}, max {} | {}", read, std::min(buffer_size, std::min(max_read_buffer, transfer->networking.max_read_buffer_size)), max_read_buffer);
            if(read <= 0) {
                if(read == 0) {
                    std::unique_lock slock{transfer->state_mutex};
                    auto original_state = transfer->state;
                    transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false);
                    slock.unlock();

                    switch(original_state) {
                        case FileClient::STATE_AWAITING_KEY:
                            logMessage(LOG_FT, "{} Disconnected without sending any key or initializing a transfer.", transfer->log_prefix());
                            break;
                        case FileClient::STATE_TRANSFERRING: {
                            assert(transfer->transfer);

                            /* since we're counting the bytes directly on read, a disconnect would mean that not all data has been transferred */
                            if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
                                logMessage(LOG_FT, "{} Disconnected while receiving file. Received {} out of {} bytes",
                                        transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset);
                            } else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
                                logMessage(LOG_FT, "{} Disconnected while sending file. Send {} out of {} bytes",
                                        transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset);
                            }

                            transfer->handle->report_transfer_statistics(transfer->shared_from_this());
                            if(auto callback{transfer->handle->callback_transfer_aborted}; callback)
                                callback(transfer->transfer, { TransferError::UNEXPECTED_CLIENT_DISCONNECT, "" });
                            break;
                        }
                        default:
                            break;
                    }
                } else {
                    if(errno == EAGAIN) {
                        transfer->add_network_read_event(false);
                        return;
                    }

                    std::unique_lock slock{transfer->state_mutex};
                    auto original_state = transfer->state;
                    transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false);
                    slock.unlock();

                    switch(original_state) {
                        case FileClient::STATE_AWAITING_KEY:
                            logMessage(LOG_FT, "{} Received a read error for an unauthenticated client: {}/{}. Closing connection.", transfer->log_prefix(), errno, strerror(errno));
                            break;
                        case FileClient::STATE_TRANSFERRING:
                            assert(transfer->transfer);

                            /* since we're counting the bytes directly on read, a disconnect would mean that not all data has been transferred */
                            if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
                                logMessage(LOG_FT, "{} Received read error while receiving file. Received {} out of {} bytes: {}/{}",
                                        transfer->log_prefix(),  transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset, errno, strerror(errno));
                            } else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
                                logMessage(LOG_FT, "{} Received read error while sending file. Send {} out of {} bytes: {}/{}",
                                        transfer->log_prefix(),  transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset, errno, strerror(errno));
                            }

                            transfer->handle->report_transfer_statistics(transfer->shared_from_this());
                            if(auto callback{transfer->handle->callback_transfer_aborted}; callback)
                                callback(transfer->transfer, { TransferError::NETWORK_IO_ERROR, strerror(errno) });
                            break;
                        default:
                            break;
                    }
                    return;
                }
                return;
            } else {
                transfer->timings.last_read = std::chrono::system_clock::now();
                transfer->statistics.network_bytes_received += read;
                bool throttle_read = transfer->networking.throttle.increase_bytes(read);

                if(transfer->state != FileClient::STATE_AWAITING_KEY && !(transfer->state == FileClient::STATE_TRANSFERRING && transfer->transfer->direction == Transfer::DIRECTION_UPLOAD)) {
                    debugMessage(LOG_FT, "{} Received {} bytes without any need. Dropping them.", transfer->log_prefix(), read);
                    return;
                }

                size_t bytes_buffered{0};
                if(transfer->state == FileClient::STATE_AWAITING_KEY) {
                    bytes_buffered = transfer->handle->handle_transfer_read_raw(transfer->shared_from_this(), buffer, read);
                } else if(transfer->state == FileClient::STATE_TRANSFERRING) {
                    if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
                        bytes_buffered = transfer->handle->handle_transfer_read_raw(transfer->shared_from_this(), buffer, read);
                    } else {
                        debugMessage(LOG_FT, "{} Received {} bytes without any need. Dropping them.", transfer->log_prefix(), read);
                    }
                }

                if(transfer->state == FileClient::STATE_DISCONNECTING || transfer->state == FileClient::STATE_DISCONNECTED)
                    break;

                if(bytes_buffered > TRANSFER_MAX_CACHED_BYTES) {
                    transfer->buffer.buffering_stopped = true;
                    debugMessage(LOG_FT, "{} Stopping network read, temp buffer full.", transfer->log_prefix());
                    return; /* no read event readd, buffer filled */
                }

                if(throttle_read)
                    break;
            }
        }

        transfer->add_network_read_event(false); /* read event is not persistent */
    }
}

void LocalFileTransfer::callback_transfer_network_write(int fd, short events, void *ptr_transfer) {
    auto transfer = reinterpret_cast<FileClient*>(ptr_transfer);

    if((unsigned) events & (unsigned) EV_TIMEOUT) {
        if(transfer->state == FileClient::STATE_DISCONNECTING) {
            {
                std::unique_lock slock{transfer->state_mutex};
                transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false);
            }

            logMessage(LOG_FT, "{} Flush timeout. Force closing connection.", transfer->log_prefix());
            return;
        }
    }

    if((unsigned) events & (unsigned) EV_WRITE) {
        if(transfer->state != FileClient::STATE_DISCONNECTING && transfer->state != FileClient::STATE_TRANSFERRING) {
            if(!(transfer->state == FileClient::STATE_AWAITING_KEY && transfer->networking.protocol == FileClient::PROTOCOL_HTTPS)) {
                debugMessage(LOG_FT, "{} Tried to write data to send only stream. Dropping buffers.", transfer->log_prefix());

                std::unique_lock block{transfer->buffer.mutex};
                auto head = std::exchange(transfer->buffer.buffer_head, nullptr);
                transfer->buffer.buffer_tail = &transfer->buffer.buffer_head;

                while(head) {
                    auto next = head->next;
                    free_buffer(head);
                    head = next;
                }
                return;
            }
        }

        Buffer* buffer{nullptr};
        size_t buffer_left_size{0};

        while(true) {
            {
                std::lock_guard block{transfer->buffer.mutex};
                buffer = transfer->buffer.buffer_head;
                buffer_left_size = transfer->buffer.bytes;
            }
            if(!buffer) {
                break;
            }

            const auto max_write_bytes = transfer->networking.throttle.bytes_left();
            if(!max_write_bytes) break; /* network throttle */

            assert(buffer->offset < buffer->length);
            auto written = ::send(fd, buffer->data + buffer->offset, std::min(buffer->length - buffer->offset, max_write_bytes), MSG_DONTWAIT | MSG_NOSIGNAL);
            if(written <= 0) {
                if(written == 0) {
                    /* EOF, how the hell is this event possible?! (Read should already catch it) */
                    logError(LOG_FT, "{} Client disconnected unexpectedly on write. Send {} bytes out of {}.",
                            transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset);

                    transfer->handle->report_transfer_statistics(transfer->shared_from_this());
                    if(auto callback{transfer->handle->callback_transfer_aborted}; callback)
                        callback(transfer->transfer, { TransferError::UNEXPECTED_CLIENT_DISCONNECT, strerror(errno) });

                    {
                        std::unique_lock slock{transfer->state_mutex};
                        transfer->handle->disconnect_client(transfer->shared_from_this(), slock, true);
                    }
                } else {
                    if(errno == EAGAIN) {
                        transfer->add_network_write_event(false);
                        break;
                    }

                    logError(LOG_FT, "{} Received network write error. Send {} bytes out of {}. Closing transfer.",
                            transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset);

                    transfer->handle->report_transfer_statistics(transfer->shared_from_this());
                    if(auto callback{transfer->handle->callback_transfer_aborted}; callback)
                        callback(transfer->transfer, { TransferError::NETWORK_IO_ERROR, strerror(errno) });

                    {
                        std::unique_lock slock{transfer->state_mutex};
                        transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false);
                    }
                }
                return;
            } else {
                buffer->offset += written;
                assert(buffer->offset <= buffer->length);
                if(buffer->length == buffer->offset) {
                    {
                        std::lock_guard block{transfer->buffer.mutex};
                        transfer->buffer.buffer_head = buffer->next;
                        if(!buffer->next)
                            transfer->buffer.buffer_tail = &transfer->buffer.buffer_head;
                        assert(transfer->buffer.bytes >= written);
                        transfer->buffer.bytes -= written;
                        buffer_left_size = transfer->buffer.bytes;
                    }

                    free_buffer(buffer);
                } else {
                    std::lock_guard block{transfer->buffer.mutex};
                    assert(transfer->buffer.bytes >= written);
                    transfer->buffer.bytes -= written;
                    buffer_left_size = transfer->buffer.bytes;
                }

                transfer->timings.last_write = std::chrono::system_clock::now();
                transfer->statistics.network_bytes_send += written;

                if(transfer->networking.throttle.increase_bytes(written))
                    break; /* we've to slow down */
            }
        }

        if(buffer_left_size > 0)
            transfer->add_network_write_event(false);
        else if(transfer->state == FileClient::STATE_DISCONNECTING) {
            if(transfer->transfer && transfer->statistics.file_bytes_transferred + transfer->transfer->file_offset == transfer->transfer->expected_file_size) {
                logMessage(LOG_FT, "{} Finished file transfer within {}. Closing connection.", transfer->log_prefix(), duration_to_string(std::chrono::system_clock::now() - transfer->timings.key_received));
                transfer->handle->report_transfer_statistics(transfer->shared_from_this());
                if(auto callback{transfer->handle->callback_transfer_finished}; callback)
                    callback(transfer->transfer);
            } else {
                debugMessage(LOG_FT, "{} Flushed output buffer.", transfer->log_prefix());
            }
            std::unique_lock slock{transfer->state_mutex};
            transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false);
            return;
        }
        transfer->handle->enqueue_disk_io(transfer->shared_from_this());
    }
}

inline std::string transfer_key_to_string(char key[TRANSFER_KEY_LENGTH]) {
    std::string result{};
    result.resize(TRANSFER_KEY_LENGTH);

    for(size_t index{0}; index < TRANSFER_KEY_LENGTH; index++)
        result[index] = isprint(key[index]) ? key[index] : '.';

    return result;
}

size_t LocalFileTransfer::handle_transfer_read_raw(const std::shared_ptr<FileClient> &client, const char *buffer, size_t length) {
    if(client->networking.protocol == FileClient::PROTOCOL_TS_V1) {
        return this->handle_transfer_read(client, buffer, length);
    } else if(client->networking.protocol == FileClient::PROTOCOL_HTTPS) {
        client->networking.pipe_ssl.process_incoming_data(pipes::buffer_view{buffer, length});
        return client->buffer.bytes;
    } else if(client->networking.protocol != FileClient::PROTOCOL_UNKNOWN) {
        assert(false);
        logWarning(LOG_FT, "{} Read bytes with unknown protocol. Closing connection.", client->log_prefix());

        std::unique_lock slock{client->state_mutex};
        client->handle->disconnect_client(client->shared_from_this(), slock, false);
        return (size_t) -1;
    }

    if(client->state != FileClient::STATE_AWAITING_KEY) {
        logWarning(LOG_FT, "{} Read bytes with unknown protocol but having not awaiting key state. Closing connection.", client->log_prefix());

        std::unique_lock slock{client->state_mutex};
        client->handle->disconnect_client(client->shared_from_this(), slock, false);
        return (size_t) -1;
    }

    /* lets read the key bytes (16) and then decide */
    if(client->transfer_key.provided_bytes < TRANSFER_KEY_LENGTH) {
        const auto bytes_write = std::min(TRANSFER_KEY_LENGTH - client->transfer_key.provided_bytes, length);
        memcpy(client->transfer_key.key + client->transfer_key.provided_bytes, buffer, bytes_write);
        length -= bytes_write;
        buffer += bytes_write;
        client->transfer_key.provided_bytes += bytes_write;
    }

    if(client->transfer_key.provided_bytes < TRANSFER_KEY_LENGTH)
        return 0; /* we need more data */

    if(pipes::SSL::is_ssl((uint8_t*) client->transfer_key.key, client->transfer_key.provided_bytes)) {
        client->networking.protocol = FileClient::PROTOCOL_HTTPS;
        client->networking.http_header_buffer.reset(allocate_buffer(MAX_HTTP_HEADER_SIZE)); /* max 8k header */
        client->networking.max_read_buffer_size = (size_t) MAX_HTTP_HEADER_SIZE; /* HTTP-Header are sometimes a bit bigger. Dont cap max bandwidth here. */
        debugMessage(LOG_FT, "{} Using protocol HTTPS for file transfer.", client->log_prefix());

        char first_bytes[TRANSFER_KEY_LENGTH];
        memcpy(first_bytes, client->transfer_key.key, TRANSFER_KEY_LENGTH);
        client->transfer_key.provided_bytes = 0;

        if(!this->initialize_client_ssl(client))
            return (size_t) -1;

        client->networking.pipe_ssl.process_incoming_data(pipes::buffer_view{first_bytes, TRANSFER_KEY_LENGTH});
        if(length > 0)
            client->networking.pipe_ssl.process_incoming_data(pipes::buffer_view{buffer, length});
        return client->buffer.bytes;
    } else {
        client->networking.protocol = FileClient::PROTOCOL_TS_V1;
        debugMessage(LOG_FT, "{} Using protocol RAWv1 for file transfer.", client->log_prefix());

        std::string error_detail{};
        auto key_result = this->handle_transfer_key_provided(client, error_detail);
        switch(key_result) {
            case TransferKeyApplyResult::SUCCESS:
                if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD)
                    this->enqueue_disk_io(client); /* we've to take initiative */

                return length ? this->handle_transfer_read(client, buffer, length) : 0;

            case TransferKeyApplyResult::UNKNOWN_KEY:
                logMessage(LOG_FT, "{} Disconnecting client because we don't recognise his key ({}).", client->log_prefix(), transfer_key_to_string(client->transfer_key.key));
                break;

            case TransferKeyApplyResult::FILE_ERROR:
                assert(client->transfer);

                this->report_transfer_statistics(client);
                if(auto callback{this->callback_transfer_aborted}; callback)
                    callback(client->transfer, { TransferError::DISK_INITIALIZE_ERROR, error_detail });

                logMessage(LOG_FT, "{} Disconnecting client because we failed to open the target file.", client->log_prefix());
                break;

            default:
                this->report_transfer_statistics(client);
                if(auto callback{this->callback_transfer_aborted}; client->transfer && callback)
                    callback(client->transfer, { TransferError::UNKNOWN, error_detail });

                logMessage(LOG_FT, "{} Disconnecting client because of an unknown key initialize error ({}).", client->log_prefix(), (int) key_result);
                break;
        }


        std::unique_lock slock{client->state_mutex};
        client->handle->disconnect_client(client->shared_from_this(), slock, false);
        return (size_t) -1;
    }

    return 0;
}

size_t LocalFileTransfer::handle_transfer_read(const std::shared_ptr<FileClient> &client, const char *buffer, size_t length) {
    if(client->state == FileClient::STATE_AWAITING_KEY) {
        if(client->networking.protocol == FileClient::PROTOCOL_HTTPS) {
            assert(client->networking.http_header_buffer);
            auto header_buffer = &*client->networking.http_header_buffer;

            http::HttpResponse response{};
            size_t overhead_length{0};
            char* overhead_data_ptr{nullptr};

            if(header_buffer->offset + length > header_buffer->capacity) {
                logMessage(LOG_FT, "{} Closing connection due to an too long HTTP(S) header (over {} bytes)", client->log_prefix(), header_buffer->capacity);
                response.code = http::code::code(413, "Entity Too Large");
                response.setHeader("x-error-message", { "header exceeds max size of " + std::to_string(header_buffer->capacity) });
                goto send_response_exit;
            }

            {
                http::HttpRequest request{};

                const auto old_offset = header_buffer->offset;
                memcpy(header_buffer->data + header_buffer->offset, buffer, length);
                header_buffer->offset += length;

                constexpr static std::string_view header_end_token{"\r\n\r\n"};
                auto header_view = std::string_view{header_buffer->data, header_buffer->offset};
                auto header_end = header_view.find(header_end_token, old_offset > 3 ? old_offset - 3 : 0);
                if(header_end == std::string::npos) return 0;

                debugMessage(LOG_FT, "{} Received clients HTTP header.", client->log_prefix());
                if(!http::parse_request(std::string{header_view.data(), header_end}, request)) {
                    logError(LOG_FT, "{} Failed to parse HTTP request. Disconnecting client.", client->log_prefix());

                    std::unique_lock slock{client->state_mutex};
                    client->handle->disconnect_client(client->shared_from_this(), slock, true);
                    return (size_t) -1;
                }

                const auto transfer_key_header = request.findHeader("transfer-key");
                if(!transfer_key_header || transfer_key_header.values.empty()) {
                    logMessage(0, "{} Missing transfer key header. Disconnecting client.", client->log_prefix());
                    response.code = http::code::code(510, "Not Extended");
                    response.setHeader("x-error-message", { "missing transfer key" });
                    goto send_response_exit;
                }

                const auto& transfer_key = transfer_key_header.values[0];
                if(transfer_key.length() != TRANSFER_KEY_LENGTH) {
                    logMessage(0, "{} Received too short/long transfer key. Expected {} but received {}. Disconnecting client.", client->log_prefix(), TRANSFER_KEY_LENGTH, transfer_key.length());
                    response.code = http::code::code(510, "Not Extended");
                    response.setHeader("x-error-message", { "key too short/long" });
                    goto send_response_exit;
                }
                client->transfer_key.provided_bytes = TRANSFER_KEY_LENGTH;
                memcpy(client->transfer_key.key, transfer_key.data(), TRANSFER_KEY_LENGTH);

                std::string error_detail{};
                auto key_result = this->handle_transfer_key_provided(client, error_detail);
                switch(key_result) {
                    case TransferKeyApplyResult::SUCCESS:
                        break;

                    case TransferKeyApplyResult::FILE_ERROR:
                        assert(client->transfer);

                        this->report_transfer_statistics(client);
                        if(auto callback{this->callback_transfer_aborted}; callback)
                            callback(client->transfer, { TransferError::DISK_INITIALIZE_ERROR, error_detail });

                        logMessage(LOG_FT, "{} Disconnecting client because we failed to open the target file.", client->log_prefix());
                        response.code = http::code::code(500, "Internal Server Error");
                        response.setHeader("x-error-message", { error_detail });
                        goto send_response_exit;

                    case TransferKeyApplyResult::UNKNOWN_KEY:
                        logMessage(LOG_FT, "{} Disconnecting client because we don't recognise his key ({}).", client->log_prefix(), transfer_key_to_string(client->transfer_key.key));
                        response.code = http::code::code(406, "Not Acceptable");
                        response.setHeader("x-error-message", { "unknown key" });
                        goto send_response_exit;

                    default:
                        this->report_transfer_statistics(client);
                        if(auto callback{this->callback_transfer_aborted}; client->transfer && callback)
                            callback(client->transfer, { TransferError::UNKNOWN, error_detail });

                        logMessage(LOG_FT, "{} Disconnecting client because of an unknown key initialize error ({}).", client->log_prefix(), (int) key_result);
                        response.code = http::code::code(500, "Internal Server Error");
                        response.setHeader("x-error-message", { error_detail.empty() ? "failed to initialize transfer" : error_detail });
                        goto send_response_exit;
                }

                response.code = http::code::_200;
                if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
                    const auto download_name = request.findHeader("download-name");
                    response.setHeader("Content-Length", { std::to_string(client->transfer->expected_file_size - client->transfer->file_offset) });

                    response.setHeader("Content-type", {"application/octet-stream; "});
                    response.setHeader("Content-Transfer-Encoding", {"binary"});
                    response.setHeader("Content-Disposition", {
                        "attachment; filename=\"" + http::encode_url(download_name && !download_name.values.empty() ? download_name.values[0] : "TeaWeb Download") + "\""
                    });

                    /* TODO: X-media-bytes */
#if 0
                    if(this->pendingKey->size > 1) {
                            char header_buffer[64];
                            auto read = fstream->readsome(header_buffer, 64);
                            if(read > 0)
                                response.setHeader("X-media-bytes", {base64::encode(header_buffer, read)});
                            fstream->seekg(this->pendingKey->offset, std::ios::beg);
                        }
#endif
                    client->networking.http_state = FileClient::HTTP_STATE_DOWNLOADING;
                    goto send_response_exit;
                } else {
                    response.setHeader("Content-Length", { std::to_string(client->transfer->expected_file_size) });
                    client->networking.http_state = FileClient::HTTP_STATE_AWAITING_BOUNDARY;
                }

                overhead_length = header_buffer->offset - header_end - header_end_token.length();
                overhead_data_ptr = header_buffer->data + header_end + header_end_token.length();
            }

            send_response_exit:
            this->send_http_response(client, response);
            if(response.code->code != 200) {
                std::unique_lock slock{client->state_mutex};
                client->handle->disconnect_client(client->shared_from_this(), slock, true);
                return (size_t) -1;
            }

            if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD)
                this->enqueue_disk_io(client); /* we've to take initiative */

            header_buffer->offset = 0;
            return overhead_length == 0 ? 0 : this->handle_transfer_read(client, overhead_data_ptr, overhead_length);
        } else {
            logError(LOG_FT, "{} Protocol variable contains invalid protocol for awaiting key state. Disconnecting client.", client->log_prefix());

            std::unique_lock slock{client->state_mutex};
            client->handle->disconnect_client(client->shared_from_this(), slock, true);
            return (size_t) -1;
        }
    } else if(client->state == FileClient::STATE_TRANSFERRING) {
        assert(client->transfer);
        if(client->transfer->direction != Transfer::DIRECTION_UPLOAD) {
            debugMessage(LOG_FT, "{} Read {} bytes from client even though we're only sending a file. Ignoring it.", client->log_prefix(), length);
            return 0;
        }

        if(client->networking.protocol == FileClient::PROTOCOL_HTTPS) {
            std::string error_message{};
            const auto upload_result = client->handle->handle_transfer_upload_http(client, buffer, length);
            switch(upload_result) {
                case TransferUploadHTTPResult::FINISH: {this->report_transfer_statistics(client);
                    if(auto callback{this->callback_transfer_finished}; callback)
                        callback(client->transfer);

                    std::unique_lock slock{client->state_mutex};
                    client->handle->disconnect_client(client->shared_from_this(), slock, true);
                    return client->buffer.bytes; /* a bit unexact but the best we could get away with it */
                }

                case TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE:
                    return client->buffer.bytes; /* a bit unexact but the best we could get away with it */

                case TransferUploadHTTPResult::MISSING_CONTENT_TYPE:
                    logMessage(LOG_FT, "{} Missing boundary content type. Disconnecting client.", client->log_prefix());
                    error_message = "invalid boundary content type";
                    break;

                case TransferUploadHTTPResult::INVALID_CONTENT_TYPE:
                    logMessage(LOG_FT, "{} Invalid boundary content type. Disconnecting client.", client->log_prefix());
                    error_message = "missing boundary content type";
                    break;

                case TransferUploadHTTPResult::BOUNDARY_MISSING:
                    logMessage(LOG_FT, "{} Missing boundary token. Disconnecting client.", client->log_prefix());
                    error_message = "missing boundary token";
                    break;

                case TransferUploadHTTPResult::BOUNDARY_INVALID:
                    logMessage(LOG_FT, "{} Invalid boundary. Disconnecting client.", client->log_prefix());
                    error_message = "invalid boundary";
                    break;

                case TransferUploadHTTPResult::BOUNDARY_TOKEN_INVALID:
                    logMessage(LOG_FT, "{} Invalid boundary token. Disconnecting client.", client->log_prefix());
                    error_message = "invalid boundary token";
                    break;
            }

            http::HttpResponse response{};

            response.code = http::code::code(510, "Not Extended");
            response.setHeader("x-error-message", { error_message });
            client->handle->send_http_response(client, response);

            std::unique_lock slock{client->state_mutex};
            client->handle->disconnect_client(client->shared_from_this(), slock, true);

            return (size_t) -1;
        } else if(client->networking.protocol == FileClient::PROTOCOL_TS_V1) {
            auto result = this->handle_transfer_upload_raw(client, buffer, length);

            switch (result) {
                case TransferUploadRawResult::FINISH: {this->report_transfer_statistics(client);
                    if(auto callback{this->callback_transfer_finished}; callback)
                        callback(client->transfer);

                    std::unique_lock slock{client->state_mutex};
                    client->handle->disconnect_client(client->shared_from_this(), slock, true);
                    return (size_t) -1;
                }

                case TransferUploadRawResult::MORE_DATA_TO_RECEIVE:
                    return client->buffer.bytes; /* a bit unexact but the best we could get away with it */
            }
        } else {
            logWarning(LOG_FT, "{} Read message for client with unknown protocol. Dropping {} bytes.", client->log_prefix(), length);
            return 0;
        }
    } else {
        logWarning(LOG_FT, "{} Read message at invalid client state ({}). Dropping message.", client->log_prefix(), client->state);
    }
    return 0;
}

TransferKeyApplyResult LocalFileTransfer::handle_transfer_key_provided(const std::shared_ptr<FileClient> &client, std::string& error_detail) {
    {
        std::lock_guard tlock{this->transfers_mutex};
        for(auto it = this->pending_transfers.begin(); it != this->pending_transfers.end(); it++) {
            if(memcmp((*it)->transfer_key, client->transfer_key.key, TRANSFER_KEY_LENGTH) == 0) {
                client->transfer = *it;
                this->pending_transfers.erase(it);
                break;
            }
        }
    }

    if(!client->transfer)
        return TransferKeyApplyResult::UNKNOWN_KEY;

    {
        std::string absolute_path{};
        auto transfer = client->transfer;
        switch (transfer->target_type) {
            case Transfer::TARGET_TYPE_AVATAR:
                absolute_path = this->file_system_.absolute_avatar_path(transfer->server_id, transfer->target_file_path);
                logMessage(LOG_FT, "{} Initialized avatar transfer for avatar \"{}\" ({} bytes, transferring {} bytes).",
                        client->log_prefix(), transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
                break;
            case Transfer::TARGET_TYPE_ICON:
                absolute_path = this->file_system_.absolute_icon_path(transfer->server_id, transfer->target_file_path);
                logMessage(LOG_FT, "{} Initialized icon transfer for icon \"{}\" ({} bytes, transferring {} bytes).",
                        client->log_prefix(), transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
                break;
            case Transfer::TARGET_TYPE_CHANNEL_FILE:
                absolute_path = this->file_system_.absolute_channel_path(transfer->server_id, transfer->channel_id, transfer->target_file_path);
                logMessage(LOG_FT, "{} Initialized channel transfer for file \"{}/{}\" ({} bytes, transferring {} bytes).",
                        client->log_prefix(), transfer->channel_id, transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
                break;
            default:
                logError(LOG_FT, "{} Tried to initialize client with unknown file target type ({}). Dropping transfer.", client->log_prefix(), (int) transfer->target_type);
                error_detail = "invalid transfer target type";
                return TransferKeyApplyResult::INTERNAL_ERROR;
        }
        debugMessage(LOG_FT, "{} Absolute file path: {}", client->log_prefix(), absolute_path);
        client->file.absolute_path = absolute_path;
    }

    if(client->transfer->max_bandwidth > 0) {
        debugMessage(LOG_FT, "{} Limit network bandwidth to {} bytes/second", client->log_prefix(), client->transfer->max_bandwidth);
        client->networking.throttle.set_max_bandwidth(client->transfer->max_bandwidth);
    }
    client->networking.max_read_buffer_size = (size_t) -1; /* limit max bandwidth via throttle */

    auto io_init_result = this->initialize_file_io(client);
    if(io_init_result != FileInitializeResult::SUCCESS) {
        logMessage(LOG_FT, "{} Failed to initialize file {}: {}/{}. Disconnecting client.",
                client->log_prefix(), client->transfer->direction == Transfer::DIRECTION_UPLOAD ? "writer" : "reader", (int) io_init_result, kFileInitializeResultMessages[(int) io_init_result]);
        error_detail = std::to_string((int) io_init_result) + "/" + std::string{kFileInitializeResultMessages[(int) io_init_result]};
        return TransferKeyApplyResult::FILE_ERROR;
    }

    {
        std::unique_lock slock{client->state_mutex};
        if(client->state != FileClient::STATE_AWAITING_KEY)
            return TransferKeyApplyResult::SUCCESS; /* something disconnected the client */

        client->state = FileClient::STATE_TRANSFERRING;
    }

    if(auto callback{this->callback_transfer_started}; callback)
        callback(client->transfer);

    client->timings.key_received = std::chrono::system_clock::now();
    return TransferKeyApplyResult::SUCCESS;
}

TransferUploadRawResult LocalFileTransfer::handle_transfer_upload_raw(const std::shared_ptr<FileClient> &client, const char *buffer, size_t length) {
    client->statistics.file_bytes_transferred += length;

    bool transfer_finished{false};
    auto writte_offset = client->statistics.file_bytes_transferred + client->transfer->file_offset;
    if(writte_offset > client->transfer->expected_file_size) {
        logMessage(LOG_FT, "{} Client send {} too many bytes (Transfer length was {}). Dropping them, flushing the disk IO and closing the transfer.", client->log_prefix(), writte_offset - client->transfer->expected_file_size, duration_to_string(std::chrono::system_clock::now() - client->timings.key_received));
        length -= writte_offset - client->transfer->expected_file_size;

        transfer_finished = true;
    } else if(writte_offset == client->transfer->expected_file_size) {
        logMessage(LOG_FT, "{} File upload has been completed in {}. Flushing disk IO and closing the connection.", client->log_prefix(), duration_to_string(std::chrono::system_clock::now() - client->timings.key_received));
        transfer_finished = true;
    }

    auto tbuffer = allocate_buffer(length);
    tbuffer->offset = 0;
    tbuffer->length = length;
    memcpy(tbuffer->data, buffer, length);

    {
        std::lock_guard block{client->buffer.mutex};
        *client->buffer.buffer_tail = tbuffer;
        client->buffer.buffer_tail = &tbuffer->next;
        client->buffer.bytes += length;
    }

    this->enqueue_disk_io(client);

    return transfer_finished ? TransferUploadRawResult::FINISH : TransferUploadRawResult::MORE_DATA_TO_RECEIVE;
}

//Example boundary:
//------WebKitFormBoundaryaWP8XAzMBnMOJznv\r\nContent-Disposition: form-data; name="file"; filename="blob"\r\nContent-Type: application/octet-stream\r\n\r\n
TransferUploadHTTPResult LocalFileTransfer::handle_transfer_upload_http(const std::shared_ptr<FileClient> &client,
                                                                        const char *buffer, size_t length) {
    constexpr static std::string_view boundary_end_token{"\r\n\r\n"};
    constexpr static std::string_view boundary_token_end_token{"\r\n"};

    if(client->networking.http_state == FileClient::HTTP_STATE_AWAITING_BOUNDARY) {
        assert(client->networking.http_header_buffer);

        /* Notice: The buffer ptr might be some data within our header buffer! But since its somewhere in the back its okey */
        auto boundary_buffer = &*client->networking.http_header_buffer;
        if(boundary_buffer->offset + length > boundary_buffer->capacity)
            return TransferUploadHTTPResult::BOUNDARY_MISSING;

        const auto old_offset = boundary_buffer->offset;
        memcpy(boundary_buffer->data + boundary_buffer->offset, buffer, length);
        boundary_buffer->offset += length;

        auto boundary_view = std::string_view{boundary_buffer->data, boundary_buffer->offset};
        auto boundary_end = boundary_view.find(boundary_end_token, old_offset > 3 ? old_offset - 3 : 0);
        if(boundary_end == std::string::npos)
            return TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE;

        auto boundary_token_end = boundary_view.find(boundary_token_end_token);
        if(boundary_token_end >= boundary_end)
            return TransferUploadHTTPResult::BOUNDARY_TOKEN_INVALID;

        const auto boundary_token = boundary_view.substr(0, boundary_token_end);
        debugMessage(LOG_FT, "{} Received clients HTTP file boundary ({}).", client->log_prefix(), boundary_token);

        const auto boundary_payload = boundary_view.substr(boundary_token_end + boundary_token_end_token.size());

        http::HttpRequest boundary{};
        if(!http::parse_request(std::string{boundary_payload}, boundary))
            return TransferUploadHTTPResult::BOUNDARY_INVALID;

        const auto content_type = boundary.findHeader("Content-Type");
        if(!content_type || content_type.values.empty())
            return TransferUploadHTTPResult::MISSING_CONTENT_TYPE;
        else if(content_type.values[0] != "application/octet-stream")
            return TransferUploadHTTPResult::INVALID_CONTENT_TYPE;

        const auto overhead_length = boundary_buffer->offset - boundary_end - boundary_end_token.length();
        const auto overhead_data_ptr = boundary_buffer->data + boundary_end + boundary_end_token.length();

        client->networking.http_state = FileClient::HTTP_STATE_UPLOADING;
        boundary_buffer->offset = 0;
        return overhead_length == 0 ? TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE : this->handle_transfer_upload_http(client, overhead_data_ptr, overhead_length);
    } else if(client->networking.http_state == FileClient::HTTP_STATE_UPLOADING) {
        auto result = this->handle_transfer_upload_raw(client, buffer, length);
        switch(result) {
            case TransferUploadRawResult::MORE_DATA_TO_RECEIVE:
                return TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE;

            case TransferUploadRawResult::FINISH:
                /* TODO: Try to read the end boundary! */
                return TransferUploadHTTPResult::FINISH;

            default:
                assert(false);
                return TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE;
        }
    } else {
        logWarning(0, "{} Received HTTP(S) data, for an invalid HTTP state ({}).", client->log_prefix(), (int) client->networking.http_state);
        return TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE;
    }
}

inline void apply_cors_and_connection_headers(http::HttpResponse &response) {
    response.setHeader("Connection", {"close"}); /* close the connection instance, we dont want multiple requests */
    response.setHeader("Access-Control-Allow-Methods", {"GET, POST"});
    response.setHeader("Access-Control-Allow-Origin", {"*"});
    response.setHeader("Access-Control-Allow-Headers", response.findHeader("Access-Control-Request-Headers").values); //access-control-allow-headers
    response.setHeader("Access-Control-Max-Age", {"86400"});
}

void LocalFileTransfer::send_http_response(const std::shared_ptr<FileClient> &client, http::HttpResponse &response) {
    apply_cors_and_connection_headers(response);
    response.setHeader("Access-Control-Expose-Headers", {"*, x-error-message, Content-Length, X-media-bytes, Content-Disposition"});

    const auto payload = response.build();
    client->send_file_bytes(payload.data(), payload.length());
}