// See the file "COPYING" in the main distribution directory for copyright.

// This code contributed by Nadi Sarrar.

#include "zeek/analyzer/protocol/bittorrent/BitTorrent.h"

#include "zeek/analyzer/protocol/bittorrent/events.bif.h"
#include "zeek/analyzer/protocol/tcp/TCP_Reassembler.h"

namespace zeek::analyzer::bittorrent {

BitTorrent_Analyzer::BitTorrent_Analyzer(Connection* c) : analyzer::tcp::TCP_ApplicationAnalyzer("BITTORRENT", c) {
    interp = new binpac::BitTorrent::BitTorrent_Conn(this);
    stop_orig = stop_resp = false;
    stream_len_orig = stream_len_resp = 0;
}

BitTorrent_Analyzer::~BitTorrent_Analyzer() { delete interp; }

void BitTorrent_Analyzer::Done() {
    analyzer::tcp::TCP_ApplicationAnalyzer::Done();

    interp->FlowEOF(true);
    interp->FlowEOF(false);
}

void BitTorrent_Analyzer::DeliverStream(int len, const u_char* data, bool orig) {
    uint64_t& this_stream_len = orig ? stream_len_orig : stream_len_resp;
    bool& this_stop = orig ? stop_orig : stop_resp;

    analyzer::tcp::TCP_ApplicationAnalyzer::DeliverStream(len, data, orig);

    if ( TCP() && TCP()->IsPartial() )
        return;

    if ( this_stop )
        return;

    this_stream_len += len;

    try {
        interp->NewData(orig, data, data + len);
    } catch ( binpac::Exception const& e ) {
        const char except[] = "binpac exception: invalid handshake";
        if ( ! strncmp(e.c_msg(), except, strlen(except)) )
            // Does not look like bittorrent - silently
            // drop the connection.
            Parent()->RemoveChildAnalyzer(this);
        else {
            DeliverWeird(util::fmt("Stopping BitTorrent analysis: protocol violation (%s)", e.c_msg()), orig);
            this_stop = true;
            if ( stop_orig && stop_resp )
                AnalyzerViolation("BitTorrent: content gap and/or protocol violation");
        }
    }
}

void BitTorrent_Analyzer::Undelivered(uint64_t seq, int len, bool orig) {
    analyzer::tcp::TCP_ApplicationAnalyzer::Undelivered(seq, len, orig);

    // TODO: Code commented out for now. I think that shoving data that
    // is definitely wrong into the parser seems like a really bad idea.
    // The way it's currently tracking the next message offset isn't
    // compatible with new 64bit int support in binpac either.

    // uint64_t entry_offset = orig ?
    //	*interp->upflow()->next_message_offset() :
    //	*interp->downflow()->next_message_offset();
    // uint64_t& this_stream_len = orig ? stream_len_orig : stream_len_resp;
    // bool& this_stop = orig ? stop_orig : stop_resp;
    //
    // this_stream_len += len;
    //
    // if ( entry_offset < this_stream_len )
    //	{ // entry point is somewhere in the gap
    //	DeliverWeird("Stopping BitTorrent analysis: cannot recover from content gap", orig);
    //	this_stop = true;
    //	if ( stop_orig && stop_resp )
    //		AnalyzerViolation("BitTorrent: content gap and/or protocol violation");
    //	}
    // else
    //	{ // fill the gap
    //	try
    //		{
    //		u_char gap[len];
    //		memset(gap, 0, len);
    //		interp->NewData(orig, gap, gap + len);
    //		}
    //	catch ( binpac::Exception const &e )
    //		{
    //		DeliverWeird("Stopping BitTorrent analysis: filling content gap failed", orig);
    //		this_stop = true;
    //		if ( stop_orig && stop_resp )
    //			AnalyzerViolation("BitTorrent: content gap and/or protocol violation");
    //		}
    //	}
}

void BitTorrent_Analyzer::EndpointEOF(bool is_orig) {
    analyzer::tcp::TCP_ApplicationAnalyzer::EndpointEOF(is_orig);
    interp->FlowEOF(is_orig);
}

void BitTorrent_Analyzer::DeliverWeird(const char* msg, bool orig) {
    if ( bittorrent_peer_weird )

        // TODO: why does bittorrent have a different set of weirds?
        EnqueueConnEvent(bittorrent_peer_weird, ConnVal(), val_mgr->Bool(orig), make_intrusive<StringVal>(msg));
}

} // namespace zeek::analyzer::bittorrent
