/*
    SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "peer.h"

#include "packetreader.h"
#include "peerdownloader.h"
#include "peermanager.h"
#include "peeruploader.h"
#include "utmetadata.h"
#include "utpex.h"
#include <bcodec/bdecoder.h>
#include <bcodec/bencoder.h>
#include <bcodec/bnode.h>
#include <cmath>
#include <diskio/chunk.h>
#include <download/piece.h>
#include <download/request.h>
#include <mse/encryptedpacketsocket.h>
#include <net/address.h>
#include <net/reverseresolver.h>
#include <torrent/server.h>
#include <torrent/torrent.h>
#include <util/functions.h>
#include <util/log.h>
#include <version.h>

using namespace net;

namespace bt
{
static const int MAX_METADATA_SIZE = 100 * 1024 * 1024; // maximum allowed metadata_size (up to 100 MiB)
static const unsigned MAX_PENDING_UPLOAD_BLOCKS = 512; // allow up to 8 MiB of 16KiB blocks
static const Uint32 MAX_PENDING_UPLOAD_BYTES = MAX_PENDING_UPLOAD_BLOCKS * (13 + 16384);
static Uint32 peer_id_counter = 1;
bool Peer::resolve_hostname = true;

Peer::Peer(std::unique_ptr<mse::EncryptedPacketSocket> sock,
           const PeerID &peer_id,
           Uint32 num_chunks,
           Uint32 chunk_size,
           Uint32 support,
           bool local,
           std::unique_ptr<ConnectionLimit::Token> token,
           PeerManager *pman)
    : PeerInterface(peer_id, num_chunks)
    , sock(std::move(sock))
    , token(std::move(token))
    , id(peer_id_counter)
    , pman(pman)
{
    peer_id_counter++;

    Uint32 max_packet_len = 9 + MAX_PIECE_LEN; // size of piece packet
    const Uint32 bitfield_length = num_chunks / 8 + 1 + (num_chunks % 8 == 0 ? 0 : 1);
    if (bitfield_length > max_packet_len) { // bitfield can be longer
        max_packet_len = bitfield_length;
    }

    // to be future proof use 10 times the max packet length,
    // in case some extension message comes along which is larger
    preader = new PacketReader(10 * max_packet_len);
    downloader = new PeerDownloader(this, chunk_size);
    uploader = new PeerUploader(this);

    stalled_timer.update();

    connect_time = QTime::currentTime();
    stats.client = peer_id.identifyClient();
    stats.ip_address = getIPAddresss();
    stats.dht_support = support & DHT_SUPPORT;
    stats.fast_extensions = support & FAST_EXT_SUPPORT;
    stats.extension_protocol = support & EXT_PROT_SUPPORT;
    stats.encrypted = this->sock->encrypted();
    stats.local = local;
    stats.transport_protocol = this->sock->socketDevice()->transportProtocol();

    if (stats.ip_address == QStringLiteral("0.0.0.0")) {
        Out(SYS_CON | LOG_DEBUG) << "No more 0.0.0.0" << endl;
        kill();
    } else {
        this->sock->startMonitoring(preader);
    }

    pex_allowed = stats.extension_protocol;
    extensions.setAutoDelete(true);

    if (resolve_hostname) {
        net::ReverseResolver *res = new net::ReverseResolver();
        connect(res, &net::ReverseResolver::resolved, this, &Peer::resolved, Qt::QueuedConnection);
        res->resolveAsync(this->sock->getRemoteAddress());
    }
}

Peer::~Peer()
{
    // Seeing that we are going to delete the preader object,
    // call stopMonitoring, in some situations it is possible that the socket
    // is only deleted later because the authenticate object still has a reference to it
    sock->stopMonitoring();
    delete uploader;
    delete downloader;
    delete preader;
}

void Peer::closeConnection()
{
    sock->close();
}

void Peer::kill()
{
    sock->close();
    killed = true;
    token.reset();
}

void Peer::handleChoke(Uint32 len)
{
    if (len != 1) {
        kill();
        return;
    }

    if (!stats.choked) {
        stats.time_choked = CurrentTime();
    }
    stats.choked = true;
    downloader->choked();
}

void Peer::handleUnchoke(Uint32 len)
{
    if (len != 1) {
        Out(SYS_CON | LOG_DEBUG) << "len err UNCHOKE" << endl;
        kill();
        return;
    }

    if (stats.choked) {
        stats.time_unchoked = CurrentTime();
        bytes_downloaded_since_unchoke = 0;
    }

    stats.choked = false;
}

void Peer::handleInterested(Uint32 len)
{
    if (len != 1) {
        Out(SYS_CON | LOG_DEBUG) << "len err INTERESTED" << endl;
        kill();
        return;
    }

    if (!stats.interested) {
        stats.interested = true;
        pman->rerunChoker();
    }
}

void Peer::handleNotInterested(Uint32 len)
{
    if (len != 1) {
        kill();
        return;
    }

    if (stats.interested) {
        stats.interested = false;
        pman->rerunChoker();
    }
}

void Peer::handleHave(const bt::Uint8 *packet, Uint32 len)
{
    if (len != 5) {
        kill();
    } else {
        const Uint32 ch = ReadUint32(packet, 1);
        if (ch < pieces.getNumBits()) {
            pman->have(this, ch);
            pieces.set(ch, true);
        } else if (pman->getTorrent().isLoaded()) {
            Out(SYS_CON | LOG_NOTICE) << "Received invalid have value, kicking peer" << endl;
            kill();
        }
    }
}

void Peer::handleHaveAll(Uint32 len)
{
    if (len != 1) {
        kill();
    } else {
        pieces.setAll(true);
        pman->bitSetReceived(this, pieces);
    }
}

void Peer::handleHaveNone(Uint32 len)
{
    if (len != 1) {
        kill();
    } else {
        pieces.setAll(false);
        pman->bitSetReceived(this, pieces);
    }
}

void Peer::handleBitField(const bt::Uint8 *packet, Uint32 len)
{
    if (len != 1 + pieces.getNumBytes()) {
        if (pman->getTorrent().isLoaded()) {
            kill();
        }
    } else {
        pieces = BitSet(packet + 1, pieces.getNumBits());
        pman->bitSetReceived(this, pieces);
    }
}

void Peer::handleRequest(const bt::Uint8 *packet, Uint32 len)
{
    if (len != 13) {
        kill();
        return;
    }

    const Request r(ReadUint32(packet, 1), ReadUint32(packet, 5), ReadUint32(packet, 9), downloader);

    if (stats.has_upload_slot) {
        uploader->addRequest(r);
    } else if (stats.fast_extensions) {
        sendReject(r);
    }
}

void Peer::handlePiece(const bt::Uint8 *packet, Uint32 len)
{
    if (paused) {
        return;
    }

    if (len < 9) {
        kill();
        return;
    }

    snub_timer.update();

    stats.bytes_downloaded += (len - 9);
    bytes_downloaded_since_unchoke += (len - 9);
    const Piece p(ReadUint32(packet, 1), ReadUint32(packet, 5), len - 9, downloader, packet + 9);
    downloader->piece(p);
    pman->pieceReceived(p);
    downloader->update();
}

void Peer::handleCancel(const bt::Uint8 *packet, Uint32 len)
{
    if (len != 13) {
        kill();
    } else {
        const Request r(ReadUint32(packet, 1), ReadUint32(packet, 5), ReadUint32(packet, 9), downloader);
        uploader->removeRequest(r);
        sock->doNotSendPiece(r, stats.fast_extensions);
    }
}

void Peer::handleReject(const bt::Uint8 *packet, Uint32 len)
{
    if (len != 13) {
        kill();
    } else {
        const Request r(ReadUint32(packet, 1), ReadUint32(packet, 5), ReadUint32(packet, 9), downloader);
        downloader->onRejected(r);
    }
}

void Peer::handlePort(const bt::Uint8 *packet, Uint32 len)
{
    if (len != 3) {
        kill();
    } else {
        const Uint16 port = ReadUint16(packet, 1);
        //  Out(SYS_CON|LOG_DEBUG) << "Got PORT packet : " << port << endl;
        pman->portPacketReceived(getIPAddresss(), port);
    }
}

void Peer::handlePacket(const bt::Uint8 *packet, Uint32 size)
{
    if (killed || size == 0) {
        return;
    }

    switch (packet[0]) {
    case CHOKE:
        handleChoke(size);
        break;
    case UNCHOKE:
        handleUnchoke(size);
        break;
    case INTERESTED:
        handleInterested(size);
        break;
    case NOT_INTERESTED:
        handleNotInterested(size);
        break;
    case HAVE:
        handleHave(packet, size);
        break;
    case BITFIELD:
        handleBitField(packet, size);
        break;
    case REQUEST:
        handleRequest(packet, size);
        break;
    case PIECE:
        handlePiece(packet, size);
        break;
    case CANCEL:
        handleCancel(packet, size);
        break;
    case REJECT_REQUEST:
        handleReject(packet, size);
        break;
    case PORT:
        handlePort(packet, size);
        break;
    case HAVE_ALL:
        handleHaveAll(size);
        break;
    case HAVE_NONE:
        handleHaveNone(size);
        break;
    case SUGGEST_PIECE:
        // ignore suggestions for the moment
        break;
    case ALLOWED_FAST:
        // we no longer support this, so do nothing
        break;
    case EXTENDED:
        handleExtendedPacket(packet, size);
        break;
    }
}

void Peer::pause()
{
    if (paused) {
        return;
    }

    downloader->cancelAll();
    // choke the peer and tell it we are not interested
    choke();
    sendNotInterested();
    paused = true;
}

void Peer::unpause()
{
    paused = false;
}

void Peer::handleExtendedPacket(const Uint8 *packet, Uint32 size)
{
    if (size <= 2) {
        return;
    }

    PeerProtocolExtension *ext = extensions.find(packet[1]);
    if (ext) {
        ext->handlePacket(packet, size);
    } else if (packet[1] == 0) {
        handleExtendedHandshake(packet, size);
    }
}

void Peer::handleExtendedHandshake(const Uint8 *packet, Uint32 size)
{
    if (size <= 2) {
        return;
    }

    const auto tmp = QByteArrayView{packet, size}.sliced(2);
    try {
        BDecoder dec(tmp, false);
        const std::unique_ptr<BDictNode> dict = dec.decodeDict();
        if (!dict) {
            return;
        }

        BDictNode *mdict = dict->getDict("m");
        if (!mdict) {
            return;
        }

        const BValueNode *val = nullptr;

        if ((val = mdict->getValue("ut_pex")) && UTPex::isEnabled()) {
            // ut_pex packet
            ut_pex_id = val->data().toInt();
            if (ut_pex_id == 0) {
                extensions.erase(UT_PEX_ID);
            } else {
                PeerProtocolExtension *ext = extensions.find(UT_PEX_ID);
                if (ext) {
                    ext->changeID(ut_pex_id);
                } else if (pex_allowed) {
                    extensions.insert(UT_PEX_ID, new UTPex(this, ut_pex_id));
                }
            }
        }

        if ((val = mdict->getValue("ut_metadata"))) {
            // meta data
            const Uint32 ut_metadata_id = val->data().toInt();
            if (ut_metadata_id == 0) { // disabled by other side
                extensions.erase(UT_METADATA_ID);
            } else {
                PeerProtocolExtension *ext = extensions.find(UT_METADATA_ID);
                if (ext) {
                    ext->changeID(ut_metadata_id);
                } else {
                    int metadata_size = 0;
                    if (dict->getValue("metadata_size")) {
                        metadata_size = dict->getInt("metadata_size");
                    }

                    if (metadata_size > MAX_METADATA_SIZE) { // check for wrong metadata_size values
                        Out(SYS_GEN | LOG_NOTICE) << "Wrong or too high metadata size: " << metadata_size << ". Killing peer... " << endl;
                        Out(SYS_GEN | LOG_NOTICE) << "Maximum supported metadata (torrent file) size: " << MAX_METADATA_SIZE << endl;
                        kill();
                        return;
                    }

                    UTMetaData *md = new UTMetaData(pman->getTorrent(), ut_metadata_id, this);
                    md->setReportedMetadataSize(metadata_size);
                    extensions.insert(UT_METADATA_ID, md);
                }
            }
        }

        if ((val = dict->getValue("reqq"))) {
            stats.max_request_queue = val->data().toInt();
        }

        if ((val = dict->getValue("upload_only"))) {
            stats.partial_seed = val->data().toInt() == 1;
        }
    } catch (...) {
        // just ignore invalid packets
        Out(SYS_CON | LOG_DEBUG) << "Invalid extended packet" << endl;
    }
}

Uint32 Peer::sendData(const Uint8 *data, Uint32 len)
{
    if (killed) {
        return 0;
    }

    const Uint32 ret = sock->sendData(data, len);
    if (!sock->ok()) {
        kill();
    }

    return ret;
}

Uint32 Peer::readData(Uint8 *buf, Uint32 len)
{
    if (killed) {
        return 0;
    }

    const Uint32 ret = sock->readData(buf, len);

    if (!sock->ok()) {
        kill();
    }

    return ret;
}

Uint32 Peer::bytesAvailable() const
{
    return sock->bytesAvailable();
}

Uint32 Peer::getUploadRate() const
{
    if (sock) {
        return sock->getUploadRate();
    } else {
        return 0;
    }
}

Uint32 Peer::getDownloadRate() const
{
    if (sock) {
        return sock->getDownloadRate();
    } else {
        return 0;
    }
}

void Peer::update()
{
    if (killed) {
        return;
    }

    if (!sock->ok() || !preader->ok()) {
        Out(SYS_CON | LOG_DEBUG) << "Connection closed" << endl;
        kill();
        return;
    }

    sock->updateSpeeds(bt::CurrentTime());
    preader->update(*this);

    const Uint32 data_bytes = sock->dataBytesUploaded();

    if (data_bytes > 0) {
        stats.bytes_uploaded += data_bytes;
        uploader->addUploadedBytes(data_bytes);
    }

    if (!paused) {
        PtrMap<Uint32, PeerProtocolExtension>::iterator i = extensions.begin();
        while (i != extensions.end()) {
            if (i->second->needsUpdate()) {
                i->second->update();
            }
            ++i;
        }
    }

    // if no data is being sent or received, and there are pending requests
    // increment the connection stalled timer
    if (getUploadRate() > 100 || getDownloadRate() > 100
        || (uploader->getNumRequests() == 0 && sock->numPendingPieceUploads() == 0 && downloader->getNumRequests() == 0)) {
        stalled_timer.update();
    }

    stats.download_rate = this->getDownloadRate();
    stats.upload_rate = this->getUploadRate();
    stats.perc_of_file = this->percentAvailable();
    stats.snubbed = this->isSnubbed();
    stats.num_up_requests = uploader->getNumRequests() + sock->numPendingPieceUploads();
    stats.num_down_requests = downloader->getNumRequests();
}

bool Peer::isStalled() const
{
    return stalled_timer.getElapsedSinceUpdate() >= 2 * 60 * 1000;
}

bool Peer::isSnubbed() const
{
    // 4 minutes
    return snub_timer.getElapsedSinceUpdate() >= 2 * 60 * 1000 && stats.num_down_requests > 0;
}

QString Peer::getIPAddresss() const
{
    if (sock) {
        return sock->getRemoteIPAddress();
    } else {
        return QString();
    }
}

Uint16 Peer::getPort() const
{
    if (!sock) {
        return 0;
    } else {
        return sock->getRemotePort();
    }
}

net::Address Peer::getAddress() const
{
    if (!sock) {
        return net::Address();
    } else {
        return sock->getRemoteAddress();
    }
}

Uint32 Peer::getTimeSinceLastPiece() const
{
    return snub_timer.getElapsedSinceUpdate();
}

float Peer::percentAvailable() const
{
    // calculation needs to use bytes, instead of chunks, because
    // the last chunk can have a different size
    const Torrent &tor = pman->getTorrent();
    Uint64 bytes = 0;
    if (pieces.get(tor.getNumChunks() - 1)) {
        bytes = tor.getChunkSize() * (pieces.numOnBits() - 1) + tor.getLastChunkSize();
    } else {
        bytes = tor.getChunkSize() * pieces.numOnBits();
    }

    const Uint64 tbytes = tor.getChunkSize() * (pieces.getNumBits() - 1) + tor.getLastChunkSize();
    return (float)bytes / (float)tbytes * 100.0;
}

void Peer::setACAScore(double s)
{
    stats.aca_score = s;
}

void Peer::choke()
{
    if (!stats.has_upload_slot) {
        return;
    }

    sendChoke();
    uploader->clearAllRequests();
}

void Peer::emitPortPacket()
{
    pman->portPacketReceived(sock->getRemoteIPAddress(), sock->getRemotePort());
}

void Peer::emitPex(const QByteArray &data, int ip_version)
{
    pman->pex(data, ip_version);
}

void Peer::setPexEnabled(bool on)
{
    if (!stats.extension_protocol) {
        return;
    }

    const PeerProtocolExtension *ext = extensions.find(UT_PEX_ID);
    if (ext && (!on || !UTPex::isEnabled())) {
        extensions.erase(UT_PEX_ID);
    } else if (!ext && on && ut_pex_id > 0 && UTPex::isEnabled()) {
        // if the other side has enabled it to, create a new UTPex object
        extensions.insert(UT_PEX_ID, new UTPex(this, ut_pex_id));
    }

    pex_allowed = on;
}

void Peer::sendExtProtHandshake(Uint16 port, Uint32 metadata_size, bool partial_seed)
{
    if (!stats.extension_protocol) {
        return;
    }

    QByteArray arr;
    BEncoder enc(std::make_unique<BEncoderBufferOutput>(arr));
    enc.beginDict();
    enc.write("m");
    // supported messages
    enc.beginDict();
    enc.write("ut_pex", pex_allowed ? UT_PEX_ID : 0);
    enc.write("ut_metadata", UT_METADATA_ID);
    enc.end();
    if (port > 0) {
        enc.write("p", (Uint32)port);
    }
    enc.write("reqq", (bt::Uint32)250);
    if (metadata_size) {
        enc.write("metadata_size", metadata_size);
    }

    enc.write("upload_only", partial_seed);
    enc.write("v", bt::GetVersionString().toLatin1());
    enc.end();
    sendExtProtMsg(0, arr);
}

void Peer::setGroupIDs(Uint32 up_gid, Uint32 down_gid)
{
    sock->setGroupID(up_gid, true);
    sock->setGroupID(down_gid, false);
}

void Peer::resolved(const QString &hinfo)
{
    stats.hostname = hinfo;
}

void Peer::setResolveHostnames(bool on)
{
    resolve_hostname = on;
}

void Peer::emitMetadataDownloaded(const QByteArray &data)
{
    Q_EMIT metadataDownloaded(data);
}

bool Peer::hasWantedChunks(const bt::BitSet &wanted_chunks) const
{
    BitSet bs = pieces;
    bs.andBitSet(wanted_chunks);
    return bs.numOnBits() > 0;
}

Uint32 Peer::averageDownloadSpeed() const
{
    if (stats.choked) {
        return 0;
    }

    const TimeStamp now = CurrentTime();
    if (now >= getUnchokeTime()) {
        return 0;
    } else {
        return bytes_downloaded_since_unchoke / (getUnchokeTime() - now);
    }
}

void Peer::sendChoke()
{
    if (!stats.has_upload_slot) {
        return;
    }

    sock->addPacket(Packet::create(CHOKE));
    stats.has_upload_slot = false;
}

void Peer::sendUnchoke()
{
    if (stats.has_upload_slot) {
        return;
    }

    sock->addPacket(Packet::create(UNCHOKE));
    stats.has_upload_slot = true;
}

void Peer::sendEvilUnchoke()
{
    sock->addPacket(Packet::create(UNCHOKE));
    stats.has_upload_slot = false;
}

void Peer::sendInterested()
{
    if (stats.am_interested == true) {
        return;
    }

    sock->addPacket(Packet::create(INTERESTED));
    stats.am_interested = true;
}

void Peer::sendNotInterested()
{
    if (stats.am_interested == false) {
        return;
    }

    sock->addPacket(Packet::create(NOT_INTERESTED));
    stats.am_interested = false;
}

void Peer::sendRequest(const Request &r)
{
    sock->addPacket(Packet::create(r, bt::REQUEST));
}

void Peer::sendCancel(const Request &r)
{
    sock->addPacket(Packet::create(r, bt::CANCEL));
}

void Peer::sendReject(const Request &r)
{
    sock->addPacket(Packet::create(r, bt::REJECT_REQUEST));
}

void Peer::sendHave(Uint32 index)
{
    sock->addPacket(Packet::create(index, bt::HAVE));
}

void Peer::sendPort(Uint16 port)
{
    sock->addPacket(Packet::create(port));
}

void Peer::sendBitSet(const BitSet &bs)
{
    sock->addPacket(Packet::create(bs));
}

void Peer::sendHaveAll()
{
    sock->addPacket(Packet::create(bt::HAVE_ALL));
}

void Peer::sendHaveNone()
{
    sock->addPacket(Packet::create(bt::HAVE_NONE));
}

void Peer::sendSuggestPiece(Uint32 index)
{
    sock->addPacket(Packet::create(index, bt::SUGGEST_PIECE));
}

void Peer::sendAllowedFast(Uint32 index)
{
    sock->addPacket(Packet::create(index, bt::ALLOWED_FAST));
}

bool Peer::sendChunk(Uint32 index, Uint32 begin, Uint32 len, Chunk *ch)
{
    //      Out() << "sendChunk " << index << " " << begin << " " << len << endl;

#ifndef NDEBUG
    Q_ASSERT(ch);
    if (!ch) {
        Out(SYS_CON | LOG_NOTICE) << "Warning : attempted to upload an invalid chunk" << endl;
        return false;
    }
#endif
    if (begin >= ch->getSize() || begin + len > ch->getSize()) {
        Out(SYS_CON | LOG_NOTICE) << "Warning : Illegal piece request" << endl;
        Out(SYS_CON | LOG_NOTICE) << "\tChunk : index " << index << " size = " << ch->getSize() << endl;
        Out(SYS_CON | LOG_NOTICE) << "\tPiece : begin = " << begin << " len = " << len << endl;
        return false;
    }
    if (sock->numPendingPieceUploads() >= MAX_PENDING_UPLOAD_BLOCKS || sock->numPendingPieceUploadBytes() + 13 + len > MAX_PENDING_UPLOAD_BYTES) {
        Out(SYS_CON | LOG_NOTICE) << "Warning : rejecting piece request due to limit on pending uploads" << endl;
        return false;
    }
    /*      Out(SYS_CON|LOG_DEBUG) << QString("Uploading %1 %2 %3 %4 %5")
     *          .arg(index).arg(begin).arg(len).arg((quint64)ch,0,16).arg((quint64)ch->getData(),0,16)
     *          << endl;;
     */
    sock->addPacket(Packet::create(index, begin, len, ch));
    return true;
}

void Peer::sendExtProtMsg(Uint8 id, const QByteArray &data)
{
    sock->addPacket(Packet::create(id, data));
}

void Peer::clearPendingPieceUploads()
{
    sock->clearPieces(stats.fast_extensions);
}

void Peer::chunkAllowed(Uint32 chunk)
{
    sendHave(chunk);
}

}

#include "moc_peer.cpp"
