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

#include "zeek/analyzer/protocol/tcp/TCP.h"

#include <vector>

#include "zeek/DebugLogger.h"
#include "zeek/Event.h"
#include "zeek/File.h"
#include "zeek/IP.h"
#include "zeek/NetVar.h"
#include "zeek/Reporter.h"
#include "zeek/RunState.h"
#include "zeek/analyzer/protocol/pia/PIA.h"
#include "zeek/analyzer/protocol/tcp/TCP_Reassembler.h"
#include "zeek/analyzer/protocol/tcp/events.bif.h"
#include "zeek/analyzer/protocol/tcp/types.bif.h"
#include "zeek/session/Manager.h"

namespace zeek::analyzer::tcp {

packet_analysis::TCP::TCPSessionAdapter* TCP_ApplicationAnalyzer::TCP() {
    return tcp ? tcp : static_cast<packet_analysis::TCP::TCPSessionAdapter*>(Conn()->FindAnalyzer("TCP"));
}

void TCP_ApplicationAnalyzer::Init() {
    Analyzer::Init();

    if ( Parent()->IsAnalyzer("TCP") )
        SetTCP(static_cast<packet_analysis::TCP::TCPSessionAdapter*>(Parent()));
}

void TCP_ApplicationAnalyzer::AnalyzerViolation(const char* reason, const char* data, int len, zeek::Tag tag) {
    if ( auto* tcp = TCP() ) {
        if ( tcp->IsPartial() || tcp->HadGap(false) || tcp->HadGap(true) )
            // Filter out incomplete connections.  Parsing them is
            // too unreliable.
            return;
    }

    Analyzer::AnalyzerViolation(reason, data, len, tag);
}

void TCP_ApplicationAnalyzer::DeliverPacket(int len, const u_char* data, bool is_orig, uint64_t seq, const IP_Hdr* ip,
                                            int caplen) {
    Analyzer::DeliverPacket(len, data, is_orig, seq, ip, caplen);
    DBG_LOG(DBG_ANALYZER, "TCP_ApplicationAnalyzer ignoring DeliverPacket(%d, %s, %" PRIu64 ", %p, %d) [%s%s]", len,
            is_orig ? "T" : "F", seq, ip, caplen, util::fmt_bytes((const char*)data, std::min(40, len)),
            len > 40 ? "..." : "");
}

void TCP_ApplicationAnalyzer::SetEnv(bool /* is_orig */, char* name, char* val) {
    delete[] name;
    delete[] val;
}

void TCP_ApplicationAnalyzer::EndpointEOF(bool is_orig) {
    analyzer::SupportAnalyzer* sa = is_orig ? orig_supporters : resp_supporters;
    for ( ; sa; sa = sa->Sibling() )
        static_cast<TCP_SupportAnalyzer*>(sa)->EndpointEOF(is_orig);
}

void TCP_ApplicationAnalyzer::ConnectionClosed(TCP_Endpoint* endpoint, TCP_Endpoint* peer, bool gen_event) {
    analyzer::SupportAnalyzer* sa = endpoint->IsOrig() ? orig_supporters : resp_supporters;

    for ( ; sa; sa = sa->Sibling() )
        static_cast<TCP_SupportAnalyzer*>(sa)->ConnectionClosed(endpoint, peer, gen_event);
}

void TCP_ApplicationAnalyzer::ConnectionFinished(bool half_finished) {
    for ( analyzer::SupportAnalyzer* sa = orig_supporters; sa; sa = sa->Sibling() )
        static_cast<TCP_SupportAnalyzer*>(sa)->ConnectionFinished(half_finished);

    for ( analyzer::SupportAnalyzer* sa = resp_supporters; sa; sa = sa->Sibling() )
        static_cast<TCP_SupportAnalyzer*>(sa)->ConnectionFinished(half_finished);
}

void TCP_ApplicationAnalyzer::ConnectionReset() {
    for ( analyzer::SupportAnalyzer* sa = orig_supporters; sa; sa = sa->Sibling() )
        static_cast<TCP_SupportAnalyzer*>(sa)->ConnectionReset();

    for ( analyzer::SupportAnalyzer* sa = resp_supporters; sa; sa = sa->Sibling() )
        static_cast<TCP_SupportAnalyzer*>(sa)->ConnectionReset();
}

void TCP_ApplicationAnalyzer::PacketWithRST() {
    for ( analyzer::SupportAnalyzer* sa = orig_supporters; sa; sa = sa->Sibling() )
        static_cast<TCP_SupportAnalyzer*>(sa)->PacketWithRST();

    for ( analyzer::SupportAnalyzer* sa = resp_supporters; sa; sa = sa->Sibling() )
        static_cast<TCP_SupportAnalyzer*>(sa)->PacketWithRST();
}

TCPStats_Endpoint::TCPStats_Endpoint(TCP_Endpoint* e) {
    endp = e;
    num_pkts = 0;
    num_rxmit = 0;
    num_rxmit_bytes = 0;
    num_in_order = 0;
    num_OO = 0;
    num_repl = 0;
    max_top_seq = 0;
    last_id = 0;
    endian_type = ENDIAN_UNKNOWN;
}

int endian_flip(int n) { return ((n & 0xff) << 8) | ((n & 0xff00) >> 8); }

bool TCPStats_Endpoint::DataSent(double /* t */, uint64_t seq, int len, int caplen, const u_char* /* data */,
                                 const IP_Hdr* ip, const struct tcphdr* /* tp */) {
    if ( ++num_pkts == 1 ) { // First packet.
        last_id = ip->ID();
        return false;
    }

    int id = ip->ID();

    if ( id == last_id ) {
        ++num_repl;
        return false;
    }

    short id_delta = id - last_id;
    short id_endian_delta = endian_flip(id) - endian_flip(last_id);

    int abs_id_delta = id_delta > 0 ? id_delta : -id_delta;
    int abs_id_endian_delta = id_endian_delta > 0 ? id_endian_delta : -id_endian_delta;

    int final_id_delta;

    if ( abs_id_delta < abs_id_endian_delta ) { // Consistent with big-endian.
        if ( endian_type == ENDIAN_UNKNOWN )
            endian_type = ENDIAN_BIG;
        else if ( endian_type == ENDIAN_BIG )
            ;
        else
            endian_type = ENDIAN_CONFUSED;

        final_id_delta = id_delta;
    }
    else { // Consistent with little-endian.
        if ( endian_type == ENDIAN_UNKNOWN )
            endian_type = ENDIAN_LITTLE;
        else if ( endian_type == ENDIAN_LITTLE )
            ;
        else
            endian_type = ENDIAN_CONFUSED;

        final_id_delta = id_endian_delta;
    }

    if ( final_id_delta < 0 && final_id_delta > -256 ) {
        ++num_OO;
        return false;
    }

    last_id = id;

    ++num_in_order;

    uint64_t top_seq = seq + len;

    int32_t data_in_flight = seq_delta(endp->LastSeq(), endp->AckSeq());
    if ( data_in_flight < 0 )
        data_in_flight = 0;

    int64_t sequence_delta = top_seq - max_top_seq;
    if ( sequence_delta <= 0 ) {
        if ( ! BifConst::ignore_keep_alive_rexmit || len > 1 || data_in_flight > 0 ) {
            ++num_rxmit;
            num_rxmit_bytes += len;
        }

        DEBUG_MSG("%.6f rexmit %" PRIu64 " + %d <= %" PRIu64 " data_in_flight = %d\n", run_state::network_time, seq,
                  len, max_top_seq, data_in_flight);

        if ( tcp_rexmit )
            endp->TCP()->EnqueueConnEvent(tcp_rexmit, endp->TCP()->ConnVal(), val_mgr->Bool(endp->IsOrig()),
                                          val_mgr->Count(seq), val_mgr->Count(len), val_mgr->Count(data_in_flight),
                                          val_mgr->Count(endp->peer->window));
    }
    else
        max_top_seq = top_seq;

    return false;
}

RecordVal* TCPStats_Endpoint::BuildStats() {
    static auto endpoint_stats = id::find_type<RecordType>("endpoint_stats");
    auto* stats = new RecordVal(endpoint_stats);

    stats->Assign(0, num_pkts);
    stats->Assign(1, num_rxmit);
    stats->Assign(2, num_rxmit_bytes);
    stats->Assign(3, num_in_order);
    stats->Assign(4, num_OO);
    stats->Assign(5, num_repl);
    stats->Assign(6, endian_type);

    return stats;
}

TCPStats_Analyzer::TCPStats_Analyzer(Connection* c)
    : TCP_ApplicationAnalyzer("TCPSTATS", c), orig_stats(), resp_stats() {}

TCPStats_Analyzer::~TCPStats_Analyzer() {
    delete orig_stats;
    delete resp_stats;
}

void TCPStats_Analyzer::Init() {
    TCP_ApplicationAnalyzer::Init();

    orig_stats = new TCPStats_Endpoint(TCP()->Orig());
    resp_stats = new TCPStats_Endpoint(TCP()->Resp());
}

void TCPStats_Analyzer::Done() {
    TCP_ApplicationAnalyzer::Done();

    if ( conn_stats )
        EnqueueConnEvent(conn_stats, ConnVal(), IntrusivePtr{AdoptRef{}, orig_stats->BuildStats()},
                         IntrusivePtr{AdoptRef{}, resp_stats->BuildStats()});
}

void TCPStats_Analyzer::DeliverPacket(int len, const u_char* data, bool is_orig, uint64_t seq, const IP_Hdr* ip,
                                      int caplen) {
    TCP_ApplicationAnalyzer::DeliverPacket(len, data, is_orig, seq, ip, caplen);

    if ( is_orig )
        orig_stats->DataSent(run_state::network_time, seq, len, caplen, data, ip, nullptr);
    else
        resp_stats->DataSent(run_state::network_time, seq, len, caplen, data, ip, nullptr);
}

} // namespace zeek::analyzer::tcp
