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

#include "zeek/packet_analysis/protocol/tcp/TCPSessionAdapter.h"

#include "zeek/RunState.h"
#include "zeek/Val.h"
#include "zeek/analyzer/Manager.h"
#include "zeek/analyzer/protocol/conn-size/ConnSize.h"
#include "zeek/analyzer/protocol/pia/PIA.h"
#include "zeek/analyzer/protocol/tcp/TCP_Endpoint.h"
#include "zeek/analyzer/protocol/tcp/TCP_Flags.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/packet_analysis/protocol/tcp/TCP.h"

constexpr int ORIG = 1;
constexpr int RESP = 2;
constexpr int32_t TOO_LARGE_SEQ_DELTA = 1048576;

using namespace zeek;
using namespace zeek::packet_analysis::TCP;

TCPSessionAdapter::TCPSessionAdapter(Connection* conn) : packet_analysis::IP::SessionAdapter("TCP", conn) {
    // Set a timer to eventually time out this connection.
    ADD_ANALYZER_TIMER(&TCPSessionAdapter::ExpireTimer, run_state::network_time + zeek::detail::tcp_SYN_timeout, false,
                       zeek::detail::TIMER_TCP_EXPIRE);

    deferred_gen_event = close_deferred = 0;

    seen_first_ACK = 0;
    is_active = 1;
    finished = 0;
    reassembling = 0;
    first_packet_seen = 0;
    is_partial = 0;

    orig = new analyzer::tcp::TCP_Endpoint(this, true);
    resp = new analyzer::tcp::TCP_Endpoint(this, false);

    orig->SetPeer(resp);
    resp->SetPeer(orig);
}

TCPSessionAdapter::~TCPSessionAdapter() {
    for ( Analyzer* a : packet_children )
        delete a;

    delete orig;
    delete resp;
}

void TCPSessionAdapter::Init() {
    Analyzer::Init();
    for ( Analyzer* a : packet_children )
        a->Init();
}

void TCPSessionAdapter::Done() {
    IP::SessionAdapter::Done();

    if ( run_state::terminating && connection_pending && is_active && ! BothClosed() )
        Event(connection_pending);

    for ( Analyzer* a : packet_children )
        a->Done();

    orig->Done();
    resp->Done();

    finished = 1;
}

int TCPSessionAdapter::get_segment_len(int payload_len, analyzer::tcp::TCP_Flags flags) {
    int seg_len = payload_len;

    if ( flags.SYN() )
        // SYN consumes a byte of sequence space.
        ++seg_len;

    if ( flags.FIN() )
        // FIN consumes a bytes of sequence space.
        ++seg_len;

    if ( flags.RST() )
        // Don't include the data in the computation of
        // the sequence space for this connection, as
        // it's not in fact part of the TCP stream.
        seg_len -= payload_len;

    return seg_len;
}

static void init_endpoint(analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Flags flags, uint32_t first_seg_seq,
                          uint32_t last_seq, double t) {
    switch ( endpoint->state ) {
        case analyzer::tcp::TCP_ENDPOINT_INACTIVE:
            if ( flags.SYN() ) {
                endpoint->InitAckSeq(first_seg_seq);
                endpoint->InitStartSeq(first_seg_seq);
            }
            else {
                // This is a partial connection - set up the initial sequence
                // numbers as though we saw a SYN, to keep the relative byte
                // numbering consistent.
                endpoint->InitAckSeq(first_seg_seq - 1);
                endpoint->InitStartSeq(first_seg_seq - 1);
                // But ensure first packet is not marked duplicate
                last_seq = first_seg_seq;
            }

            endpoint->InitLastSeq(last_seq);
            endpoint->start_time = t;
            break;

        case analyzer::tcp::TCP_ENDPOINT_SYN_SENT:
        case analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT:
            if ( flags.SYN() && first_seg_seq != endpoint->StartSeq() ) {
                endpoint->Conn()->Weird("SYN_seq_jump");
                endpoint->InitStartSeq(first_seg_seq);
                endpoint->InitAckSeq(first_seg_seq);
                endpoint->InitLastSeq(last_seq);
            }
            break;

        case analyzer::tcp::TCP_ENDPOINT_ESTABLISHED:
        case analyzer::tcp::TCP_ENDPOINT_PARTIAL:
            if ( flags.SYN() ) {
                if ( endpoint->Size() > 0 )
                    endpoint->Conn()->Weird("SYN_inside_connection");

                if ( first_seg_seq != endpoint->StartSeq() )
                    endpoint->Conn()->Weird("SYN_seq_jump");

                // Make a guess that somehow the connection didn't get established,
                // and this SYN will be the one that actually sets it up.
                endpoint->InitStartSeq(first_seg_seq);
                endpoint->InitAckSeq(first_seg_seq);
                endpoint->InitLastSeq(last_seq);
            }
            break;

        case analyzer::tcp::TCP_ENDPOINT_RESET:
            if ( flags.SYN() ) {
                if ( endpoint->prev_state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) {
                    // Seq. numbers were initialized by a RST packet from this
                    // endpoint, but now that a SYN is seen from it, that could mean
                    // the earlier RST was spoofed/injected, so re-initialize.  This
                    // mostly just helps prevent misrepresentations of payload sizes
                    // that are based on bad initial sequence values.
                    endpoint->InitStartSeq(first_seg_seq);
                    endpoint->InitAckSeq(first_seg_seq);
                    endpoint->InitLastSeq(last_seq);
                }
            }
            break;

        default: break;
    }
}

uint64_t TCPSessionAdapter::get_relative_seq(const analyzer::tcp::TCP_Endpoint* endpoint, uint32_t cur_base,
                                             uint32_t last, uint32_t wraps, bool* underflow) {
    int32_t delta = seq_delta(cur_base, last);

    if ( delta < 0 ) {
        if ( wraps && cur_base > last )
            // Seems to be a part of a previous 32-bit sequence space.
            --wraps;
    }

    else if ( delta > 0 ) {
        if ( cur_base < last )
            // The sequence space wrapped around.
            ++wraps;
    }

    if ( wraps == 0 ) {
        delta = seq_delta(cur_base, endpoint->StartSeq());

        if ( underflow && delta < 0 )
            *underflow = true;

        return delta;
    }

    return endpoint->ToRelativeSeqSpace(cur_base, wraps);
}

static void update_history(analyzer::tcp::TCP_Flags flags, analyzer::tcp::TCP_Endpoint* endpoint, uint64_t rel_seq,
                           int len) {
    int bits_set = (flags.SYN() ? 1 : 0) + (flags.FIN() ? 1 : 0) + (flags.RST() ? 1 : 0);
    if ( bits_set > 1 ) {
        if ( flags.FIN() && flags.RST() )
            endpoint->CheckHistory(analyzer::tcp::TCP_Endpoint::HIST_FIN_RST_PKT, 'I');
        else
            endpoint->CheckHistory(analyzer::tcp::TCP_Endpoint::HIST_MULTI_FLAG_PKT, 'Q');
    }

    else if ( bits_set == 1 ) {
        if ( flags.SYN() ) {
            char code = flags.ACK() ? 'H' : 'S';

            if ( endpoint->CheckHistory(analyzer::tcp::TCP_Endpoint::HIST_SYN_PKT, code) &&
                 rel_seq != endpoint->hist_last_SYN )
                endpoint->AddHistory(code);

            endpoint->hist_last_SYN = rel_seq;
        }

        if ( flags.FIN() ) {
            // For FIN's, the sequence number comes at the
            // end of (any data in) the packet, not the
            // beginning as for SYNs and RSTs.
            if ( endpoint->CheckHistory(analyzer::tcp::TCP_Endpoint::HIST_FIN_PKT, 'F') &&
                 rel_seq + len != endpoint->hist_last_FIN )
                endpoint->AddHistory('F');

            endpoint->hist_last_FIN = rel_seq + len;
        }

        if ( flags.RST() ) {
            if ( endpoint->CheckHistory(analyzer::tcp::TCP_Endpoint::HIST_RST_PKT, 'R') &&
                 rel_seq != endpoint->hist_last_RST )
                endpoint->AddHistory('R');

            endpoint->hist_last_RST = rel_seq;
        }
    }

    else { // bits_set == 0
        if ( len )
            endpoint->CheckHistory(analyzer::tcp::TCP_Endpoint::HIST_DATA_PKT, 'D');

        else if ( flags.ACK() )
            endpoint->CheckHistory(analyzer::tcp::TCP_Endpoint::HIST_ACK_PKT, 'A');
    }
}

static void update_window(analyzer::tcp::TCP_Endpoint* endpoint, unsigned int window, uint32_t base_seq,
                          uint32_t ack_seq, analyzer::tcp::TCP_Flags flags) {
    // Note, applying scaling here would be incorrect for an initial SYN,
    // whose window value is always unscaled.  However, we don't
    // check the window's value for recision in that case anyway, so
    // no-harm-no-foul.
    int scale = endpoint->window_scale;
    window = window << scale;

    // Zero windows are boring if either (1) they come with a RST packet
    // or after a RST packet, or (2) they come after the peer has sent
    // a FIN (because there's no relevant window at that point anyway).
    // (They're also boring if they come after the peer has sent a RST,
    // but *nothing* should be sent in response to a RST, so we ignore
    // that case.)
    //
    // However, they *are* potentially interesting if sent by an
    // endpoint that's already sent a FIN, since that FIN meant "I'm
    // not going to send any more", but doesn't mean "I won't receive
    // any more".
    if ( window == 0 && ! flags.RST() && endpoint->peer->state != analyzer::tcp::TCP_ENDPOINT_CLOSED &&
         endpoint->state != analyzer::tcp::TCP_ENDPOINT_RESET )
        endpoint->ZeroWindow();

    // Don't analyze window values off of SYNs, they're sometimes
    // immediately rescinded.  Also don't do so for FINs or RSTs,
    // or if the connection has already been partially closed, since
    // such recisions occur frequently in practice, probably as the
    // receiver loses buffer memory due to its process going away.

    if ( ! flags.SYN() && ! flags.FIN() && ! flags.RST() && endpoint->state != analyzer::tcp::TCP_ENDPOINT_CLOSED &&
         endpoint->state != analyzer::tcp::TCP_ENDPOINT_RESET ) {
        // ### Decide whether to accept new window based on Active
        // Mapping policy.
        if ( seq_delta(base_seq, endpoint->window_seq) >= 0 && seq_delta(ack_seq, endpoint->window_ack_seq) >= 0 ) {
            uint32_t new_edge = ack_seq + window;
            uint32_t old_edge = endpoint->window_ack_seq + endpoint->window;
            int32_t advance = seq_delta(new_edge, old_edge);

            if ( advance < 0 ) {
                // An apparent window recision.  Allow a
                // bit of slop for window scaling.  This is
                // because sometimes there will be an
                // apparent recision due to the granularity
                // of the scaling.
                if ( (-advance) >= (1 << scale) )
                    endpoint->Conn()->Weird("window_recision");
            }

            endpoint->window = window;
            endpoint->window_ack_seq = ack_seq;
            endpoint->window_seq = base_seq;
        }
    }
}

static zeek::RecordValPtr build_syn_packet_val(bool is_orig, const zeek::IP_Hdr* ip, const struct tcphdr* tcp) {
    int winscale = -1;
    int MSS = 0;
    int SACK = 0;
    std::optional<uint64_t> TSval;
    std::optional<uint64_t> TSecr;

    // Parse TCP options.
    u_char* options = (u_char*)tcp + sizeof(struct tcphdr);
    u_char* opt_end = (u_char*)tcp + static_cast<ptrdiff_t>(tcp->th_off * 4);

    while ( options < opt_end ) {
        unsigned int opt = options[0];

        if ( opt == TCPOPT_EOL )
            // All done - could flag if more junk left over ....
            break;

        if ( opt == TCPOPT_NOP ) {
            ++options;
            continue;
        }

        if ( options + 1 >= opt_end )
            // We've run off the end, no room for the length.
            break;

        unsigned int opt_len = options[1];

        if ( options + opt_len > opt_end )
            // No room for rest of option.
            break;

        if ( opt_len == 0 )
            // Trashed length field.
            break;

        switch ( opt ) {
            case TCPOPT_SACK_PERMITTED: SACK = 1; break;

            case TCPOPT_MAXSEG:
                if ( opt_len < 4 )
                    break; // bad length

                MSS = (options[2] << 8) | options[3];
                break;

            case 3: // TCPOPT_WSCALE
                if ( opt_len < 3 )
                    break; // bad length

                winscale = options[2];
                break;

            case 8: // TCPOPT_TIMESTAMP
                if ( opt_len < 10 )
                    break; // bad length

                TSval =
                    (((((static_cast<uint64_t>(options[2]) << 8) | options[3]) << 8) | options[4]) << 8) | options[5];
                TSecr =
                    (((((static_cast<uint64_t>(options[6]) << 8) | options[7]) << 8) | options[8]) << 8) | options[9];
                break;

            default: // just skip over
                break;
        }

        options += opt_len;
    }

    static auto SYN_packet = zeek::id::find_type<zeek::RecordType>("SYN_packet");
    auto v = zeek::make_intrusive<zeek::RecordVal>(SYN_packet);

    v->Assign(0, is_orig);
    v->Assign(1, static_cast<bool>(ip->DF()));
    v->Assign(2, ip->TTL());
    v->Assign(3, ip->TotalLen());
    v->Assign(4, ntohs(tcp->th_win));
    v->Assign(5, winscale);
    v->Assign(6, MSS);
    v->Assign(7, static_cast<bool>(SACK));

    if ( TSval )
        v->Assign(8, *TSval);

    if ( TSecr )
        v->Assign(9, *TSecr);

    return v;
}

static void init_window(analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer,
                        analyzer::tcp::TCP_Flags flags, zeek_int_t scale, uint32_t base_seq, uint32_t ack_seq) {
    // ### In the following, we could be fooled by an
    // inconsistent SYN retransmission.  Where's a normalizer
    // when you need one?

    if ( scale < 0 ) {       // no window scaling option
        if ( flags.ACK() ) { // window scaling not negotiated
            endpoint->window_scale = 0;
            peer->window_scale = 0;
        }
        else
            // We're not offering window scaling.
            // Ideally, we'd remember this fact so that
            // if the SYN/ACK *does* include window
            // scaling, we know it won't be negotiated.
            // But it's a pain to track that, and hard
            // to see how an adversarial responder could
            // use it to evade.  Also, if we *do* want
            // to track it, we could do so using
            // connection_SYN_packet.
            endpoint->window_scale = 0;
    }
    else {
        endpoint->window_scale = scale;
        endpoint->window_seq = base_seq;
        endpoint->window_ack_seq = ack_seq;

        peer->window_seq = ack_seq;
        peer->window_ack_seq = base_seq;
    }
}

static void init_peer(analyzer::tcp::TCP_Endpoint* peer, analyzer::tcp::TCP_Endpoint* endpoint,
                      analyzer::tcp::TCP_Flags flags, uint32_t ack_seq) {
    if ( ! flags.SYN() && ! flags.FIN() && ! flags.RST() ) {
        if ( endpoint->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT ||
             endpoint->state == analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT ||
             endpoint->state == analyzer::tcp::TCP_ENDPOINT_ESTABLISHED ) {
            // We've already sent a SYN, but that
            // hasn't roused the other end, yet we're
            // ack'ing their data.

            if ( ! endpoint->Conn()->DidWeird() )
                endpoint->Conn()->Weird("possible_split_routing");
        }
    }

    // Start the sequence numbering as if there was an initial
    // SYN, so the relative numbering of subsequent data packets
    // stays consistent.
    peer->InitStartSeq(ack_seq - 1);
    peer->InitAckSeq(ack_seq - 1);
    peer->InitLastSeq(ack_seq - 1);
}

static void update_ack_seq(analyzer::tcp::TCP_Endpoint* endpoint, uint32_t ack_seq) {
    int32_t delta_ack = seq_delta(ack_seq, endpoint->AckSeq());

    if ( ack_seq == 0 && delta_ack > TOO_LARGE_SEQ_DELTA )
        // More likely that this is a broken ack than a
        // large connection that happens to land on 0 in the
        // sequence space.
        ;
    else if ( delta_ack > 0 )
        endpoint->UpdateAckSeq(ack_seq);
}

// Returns the difference between last_seq and the last sequence
// seen by the endpoint (may be negative).
static int32_t update_last_seq(analyzer::tcp::TCP_Endpoint* endpoint, uint32_t last_seq, analyzer::tcp::TCP_Flags flags,
                               int len) {
    int32_t delta_last = seq_delta(last_seq, endpoint->LastSeq());

    if ( (flags.SYN() || flags.RST()) && (delta_last > TOO_LARGE_SEQ_DELTA || delta_last < -TOO_LARGE_SEQ_DELTA) )
        // ### perhaps trust RST seq #'s if initial and not too
        // outlandish, but not if they're coming after the other
        // side has sent a FIN - trust the FIN ack instead
        ; // NOLINT(bugprone-branch-clone)


    else if ( flags.FIN() && endpoint->LastSeq() == endpoint->StartSeq() + 1 )
        // Update last_seq based on the FIN even if delta_last < 0.
        // This is to accommodate > 2 GB connections for which
        // we've only seen the SYN and the FIN (hence the check
        // for last_seq == start_seq + 1).
        endpoint->UpdateLastSeq(last_seq); // NOLINT(bugprone-branch-clone)


    else if ( endpoint->state == analyzer::tcp::TCP_ENDPOINT_RESET )
        // don't trust any subsequent sequence numbers
        ; // NOLINT(bugprone-branch-clone)


    else if ( delta_last > 0 )
        // ### check for large jumps here.
        // ## endpoint->last_seq = last_seq;
        endpoint->UpdateLastSeq(last_seq); // NOLINT(bugprone-branch-clone)


    else if ( delta_last <= 0 && len > 0 )
        endpoint->DidRxmit();

    return delta_last;
}

void TCPSessionAdapter::Process(bool is_orig, const struct tcphdr* tp, int len, const std::shared_ptr<IP_Hdr>& ip,
                                const u_char* data, int remaining) {
    analyzer::tcp::TCP_Flags flags(tp);
    uint32_t base_seq = ntohl(tp->th_seq);
    uint32_t ack_seq = ntohl(tp->th_ack);
    uint32_t tcp_hdr_len = data - (const u_char*)tp;

    analyzer::tcp::TCP_Endpoint* endpoint = is_orig ? orig : resp;
    analyzer::tcp::TCP_Endpoint* peer = endpoint->peer;

    SetPartialStatus(flags, endpoint->IsOrig());

    int seg_len = get_segment_len(len, flags);
    uint32_t seq_one_past_segment = base_seq + seg_len;

    init_endpoint(endpoint, flags, base_seq, seq_one_past_segment, run_state::current_timestamp);

    bool seq_underflow = false;
    uint64_t rel_seq = get_relative_seq(endpoint, base_seq, endpoint->LastSeq(), endpoint->SeqWraps(), &seq_underflow);

    if ( seq_underflow && ! flags.RST() )
        // Can't tell if if this is a retransmit/out-of-order or something
        // before the sequence Zeek initialized the endpoint at or the TCP is
        // just broken and sending garbage sequences.  In either case, some
        // standard analysis doesn't apply (e.g. reassembly).
        Weird("TCP_seq_underflow_or_misorder");

    update_history(flags, endpoint, rel_seq, len);
    update_window(endpoint, ntohs(tp->th_win), base_seq, ack_seq, flags);

    if ( ! orig->did_close || ! resp->did_close )
        Conn()->SetLastTime(run_state::current_timestamp);

    if ( flags.SYN() ) {
        SynWeirds(flags, endpoint, len);
        RecordValPtr SYN_vals = build_syn_packet_val(is_orig, ip.get(), tp);
        init_window(endpoint, peer, flags, SYN_vals->GetFieldAs<IntVal>(5), base_seq, ack_seq);

        if ( connection_SYN_packet )
            EnqueueConnEvent(connection_SYN_packet, ConnVal(), SYN_vals);
    }

    if ( flags.FIN() ) {
        ++endpoint->FIN_cnt;

        if ( endpoint->FIN_cnt >= zeek::detail::tcp_storm_thresh &&
             run_state::current_timestamp < endpoint->last_time + zeek::detail::tcp_storm_interarrival_thresh )
            Weird("FIN_storm");

        endpoint->FIN_seq = rel_seq + seg_len;
    }

    if ( flags.RST() ) {
        ++endpoint->RST_cnt;

        if ( endpoint->RST_cnt >= zeek::detail::tcp_storm_thresh &&
             run_state::current_timestamp < endpoint->last_time + zeek::detail::tcp_storm_interarrival_thresh )
            Weird("RST_storm");

        // This now happens often enough that it's
        // not in the least interesting.
        // if ( len > 0 )
        //	Weird("RST_with_data");

        PacketWithRST();
    }

    uint64_t rel_ack = 0;

    if ( flags.ACK() ) {
        if ( is_orig && ! seen_first_ACK &&
             (endpoint->state == analyzer::tcp::TCP_ENDPOINT_ESTABLISHED ||
              endpoint->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT) ) {
            seen_first_ACK = 1;
            Event(connection_first_ACK);
        }

        if ( peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) {
            rel_ack = 1;
            init_peer(peer, endpoint, flags, ack_seq);
        }
        else {
            bool ack_underflow = false;
            rel_ack = get_relative_seq(peer, ack_seq, peer->AckSeq(), peer->AckWraps(), &ack_underflow);

            if ( ack_underflow ) {
                rel_ack = 0;
                Weird("TCP_ack_underflow_or_misorder");
            }
            else if ( ! flags.RST() )
                // Don't trust ack's in RST packets.
                update_ack_seq(peer, ack_seq);
        }
    }

    int32_t delta_last = update_last_seq(endpoint, seq_one_past_segment, flags, len);
    endpoint->last_time = run_state::current_timestamp;

    bool do_close;
    bool gen_event;
    UpdateStateMachine(run_state::current_timestamp, endpoint, peer, base_seq, ack_seq, len, delta_last, is_orig, flags,
                       do_close, gen_event);

    if ( flags.ACK() )
        // We wait on doing this until we've updated the state
        // machine so that if the ack reveals a content gap,
        // we can tell whether it came at the very end of the
        // connection (in a FIN or RST).  Those gaps aren't
        // reliable - especially those for RSTs - and we refrain
        // from flagging them in the connection history.
        peer->AckReceived(rel_ack);

    if ( tcp_packet )
        GeneratePacketEvent(rel_seq, rel_ack, data, len, remaining, is_orig, flags);

    if ( (tcp_option || tcp_options) && tcp_hdr_len > sizeof(*tp) )
        ParseTCPOptions(tp, is_orig);

    // PIA/signature matching state needs to be initialized before
    // processing/reassembling any TCP data, since that processing may
    // itself try to perform signature matching.  Also note that a SYN
    // packet may technically carry data (see RFC793 Section 3.4 and also
    // TCP Fast Open).
    CheckPIA_FirstPacket(is_orig, ip.get());

    if ( DEBUG_tcp_data_sent ) {
        DEBUG_MSG("%.6f before DataSent: len=%d remaining=%d skip=%d\n", run_state::network_time, len, remaining,
                  Skipping());
    }

    rel_data_seq = flags.SYN() ? rel_seq + 1 : rel_seq;

    bool need_contents = false;
    if ( len > 0 && (remaining >= len || ! packet_children.empty()) && ! flags.RST() && ! Skipping() &&
         ! seq_underflow )
        need_contents =
            endpoint->DataSent(run_state::current_timestamp, rel_data_seq, len, remaining, data, ip.get(), tp);

    endpoint->CheckEOF();

    if ( do_close ) {
        // We need to postpone doing this until after we process
        // DataSent, so we don't generate a connection_finished event
        // until after data perhaps included with the FIN is processed.
        ConnectionClosed(endpoint, peer, gen_event);
    }

    CheckRecording(need_contents, flags);
}

analyzer::Analyzer* TCPSessionAdapter::FindChild(analyzer::ID arg_id) {
    analyzer::Analyzer* child = packet_analysis::IP::SessionAdapter::FindChild(arg_id);

    if ( child )
        return child;

    for ( Analyzer* a : packet_children ) {
        if ( analyzer::Analyzer* child = a->FindChild(arg_id) )
            return child;
    }

    return nullptr;
}

analyzer::Analyzer* TCPSessionAdapter::FindChild(zeek::Tag arg_tag) {
    analyzer::Analyzer* child = packet_analysis::IP::SessionAdapter::FindChild(arg_tag);

    if ( child )
        return child;

    for ( Analyzer* a : packet_children ) {
        if ( analyzer::Analyzer* child = a->FindChild(arg_tag) )
            return child;
    }

    return nullptr;
}

bool TCPSessionAdapter::RemoveChildAnalyzer(analyzer::ID id) {
    auto rval = packet_analysis::IP::SessionAdapter::RemoveChildAnalyzer(id);

    if ( rval )
        return rval;

    return RemoveChild(packet_children, id);
}

void TCPSessionAdapter::EnableReassembly() {
    SetReassembler(new analyzer::tcp::TCP_Reassembler(this, this, analyzer::tcp::TCP_Reassembler::Forward, orig),
                   new analyzer::tcp::TCP_Reassembler(this, this, analyzer::tcp::TCP_Reassembler::Forward, resp));
}

void TCPSessionAdapter::SetReassembler(analyzer::tcp::TCP_Reassembler* rorig, analyzer::tcp::TCP_Reassembler* rresp) {
    orig->AddReassembler(rorig);
    rorig->SetDstAnalyzer(this);
    resp->AddReassembler(rresp);
    rresp->SetDstAnalyzer(this);

    if ( new_connection_contents && reassembling == 0 )
        Event(new_connection_contents);

    reassembling = 1;
}

void TCPSessionAdapter::SetPartialStatus(analyzer::tcp::TCP_Flags flags, bool is_orig) {
    if ( is_orig ) {
        if ( ! (first_packet_seen & ORIG) )
            is_partial = ! flags.SYN() || flags.ACK();
    }
    else {
        if ( ! (first_packet_seen & RESP) && ! is_partial )
            is_partial = ! flags.SYN();
    }
}

void TCPSessionAdapter::SetFirstPacketSeen(bool is_orig) { first_packet_seen |= (is_orig ? ORIG : RESP); }

void TCPSessionAdapter::UpdateInactiveState(double t, analyzer::tcp::TCP_Endpoint* endpoint,
                                            analyzer::tcp::TCP_Endpoint* peer, uint32_t base_seq, uint32_t ack_seq,
                                            int len, bool is_orig, analyzer::tcp::TCP_Flags flags, bool& do_close,
                                            bool& gen_event) {
    if ( flags.SYN() ) {
        if ( is_orig ) {
            if ( flags.ACK() ) {
                Weird("connection_originator_SYN_ack");
                endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT);
            }
            else
                endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_SYN_SENT);

            if ( zeek::detail::tcp_attempt_delay )
                ADD_ANALYZER_TIMER(&TCPSessionAdapter::AttemptTimer, t + zeek::detail::tcp_attempt_delay, true,
                                   zeek::detail::TIMER_TCP_ATTEMPT);
        }
        else {
            if ( flags.ACK() ) {
                if ( peer->state != analyzer::tcp::TCP_ENDPOINT_INACTIVE &&
                     peer->state != analyzer::tcp::TCP_ENDPOINT_PARTIAL &&
                     ! seq_between(ack_seq, peer->StartSeq(), peer->LastSeq()) )
                    Weird("bad_SYN_ack");
            }

            else if ( peer->state == analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT && base_seq == endpoint->StartSeq() ) {
                // This is a SYN/SYN-ACK reversal,
                // per the discussion in IsReuse.
                // Flip the endpoints and establish
                // the connection.
                is_partial = 0;
                Conn()->FlipRoles();
                peer->SetState(analyzer::tcp::TCP_ENDPOINT_ESTABLISHED);
                SetFirstPacketSeen(true);
            }

            else
                Weird("simultaneous_open");

            if ( peer->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT )
                peer->SetState(analyzer::tcp::TCP_ENDPOINT_ESTABLISHED);
            else if ( peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) {
                // If we were to ignore SYNs and
                // only instantiate state on SYN
                // acks, then we'd do:
                //    peer->SetState(analyzer::tcp::TCP_ENDPOINT_ESTABLISHED);
                // here.
                Weird("unsolicited_SYN_response");
            }

            endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_ESTABLISHED);

            if ( peer->state != analyzer::tcp::TCP_ENDPOINT_PARTIAL ) {
                Event(connection_established);
                Conn()->EnableStatusUpdateTimer();
            }
        }
    }

    if ( flags.FIN() ) {
        endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_CLOSED);
        do_close = gen_event = true;
        if ( peer->state != analyzer::tcp::TCP_ENDPOINT_PARTIAL && ! flags.SYN() )
            Weird("spontaneous_FIN");
    }

    if ( flags.RST() ) {
        endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_RESET);

        bool is_reject = false;

        if ( is_orig ) {
            // If our peer is established then we saw
            // a SYN-ack but not SYN - so a reverse
            // scan, and we should treat this as a
            // reject.
            if ( peer->state == analyzer::tcp::TCP_ENDPOINT_ESTABLISHED )
                is_reject = true;
        }

        else if ( peer->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT ||
                  peer->state == analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT )
            // We're rejecting an initial SYN.
            is_reject = true;

        do_close = true;
        gen_event = ! is_reject;

        if ( is_reject )
            Event(connection_rejected);

        else if ( peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE )
            Weird("spontaneous_RST");
    }

    if ( endpoint->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) { // No control flags to change the state.
        if ( ! is_orig && len == 0 && orig->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT )
            // Some eccentric TCP's will ack an initial
            // SYN prior to sending a SYN reply (hello,
            // ftp.microsoft.com).  For those, don't
            // consider the ack as forming a partial
            // connection.
            ;

        else if ( flags.ACK() && peer->state == analyzer::tcp::TCP_ENDPOINT_ESTABLISHED ) {
            // No SYN packet from originator but SYN/ACK from
            // responder, and now a pure ACK. Probably means we
            // just missed that initial SYN. Let's not treat it
            // as partial and instead establish the connection.
            endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_ESTABLISHED);
            is_partial = 0;
            SetFirstPacketSeen(is_orig);
        }

        else {
            endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_PARTIAL);
            Conn()->EnableStatusUpdateTimer();

            if ( peer->state == analyzer::tcp::TCP_ENDPOINT_PARTIAL )
                // We've seen both sides of a partial
                // connection, report it.
                Event(partial_connection);
        }
    }
}

void TCPSessionAdapter::UpdateSYN_SentState(analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer,
                                            int len, bool is_orig, analyzer::tcp::TCP_Flags flags, bool& do_close,
                                            bool& gen_event) {
    if ( flags.SYN() ) {
        if ( is_orig ) {
            if ( flags.ACK() && ! flags.FIN() && ! flags.RST() &&
                 endpoint->state != analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT )
                Weird("repeated_SYN_with_ack");
        }
        else {
            if ( ! flags.ACK() && endpoint->state != analyzer::tcp::TCP_ENDPOINT_SYN_SENT )
                Weird("repeated_SYN_reply_wo_ack");
        }
    }

    if ( flags.FIN() ) {
        if ( peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ||
             peer->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT )
            Weird("inappropriate_FIN");

        endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_CLOSED);
        do_close = gen_event = true;
    }

    if ( flags.RST() ) {
        endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_RESET);
        ConnectionReset();
        do_close = true;
    }

    else if ( len > 0 )
        Weird("data_before_established");
}

void TCPSessionAdapter::UpdateEstablishedState(analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer,
                                               analyzer::tcp::TCP_Flags flags, bool& do_close, bool& gen_event) {
    if ( flags.SYN() ) {
        if ( endpoint->state == analyzer::tcp::TCP_ENDPOINT_PARTIAL &&
             peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE && ! flags.ACK() ) {
            Weird("SYN_after_partial");
            endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_SYN_SENT);
        }
    }

    if ( flags.FIN() && ! flags.RST() ) // ###
    {                                   // should check sequence/ack numbers here ###
        endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_CLOSED);

        if ( peer->state == analyzer::tcp::TCP_ENDPOINT_RESET &&
             peer->prev_state == analyzer::tcp::TCP_ENDPOINT_CLOSED )
            // The peer sent a FIN followed by a RST.
            // Turn it back into CLOSED state, because
            // this was actually normal termination.
            peer->SetState(analyzer::tcp::TCP_ENDPOINT_CLOSED);

        do_close = gen_event = true;
    }

    if ( flags.RST() ) {
        endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_RESET);
        do_close = true;

        if ( peer->state != analyzer::tcp::TCP_ENDPOINT_RESET ||
             peer->prev_state != analyzer::tcp::TCP_ENDPOINT_ESTABLISHED )
            ConnectionReset();
    }
}

void TCPSessionAdapter::UpdateClosedState(double t, analyzer::tcp::TCP_Endpoint* endpoint, int32_t delta_last,
                                          analyzer::tcp::TCP_Flags flags, bool& do_close) {
    if ( flags.SYN() )
        Weird("SYN_after_close");

    if ( flags.FIN() && delta_last > 0 )
        // Probably should also complain on FIN recision.
        // That requires an extra state variable to avoid
        // generating slews of weird's when a TCP gets
        // seriously confused (this from experience).
        Weird("FIN_advanced_last_seq");

    // Previously, our state was CLOSED, since we sent a FIN.
    // If our peer was also closed, then don't change our state
    // now on a RST, since this connection has already seen a FIN
    // exchange.
    if ( flags.RST() && endpoint->peer->state != analyzer::tcp::TCP_ENDPOINT_CLOSED ) {
        endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_RESET);

        if ( ! endpoint->did_close )
            // RST after FIN.
            do_close = true;

        if ( connection_reset )
            ADD_ANALYZER_TIMER(&TCPSessionAdapter::ResetTimer, t + zeek::detail::tcp_reset_delay, true,
                               zeek::detail::TIMER_TCP_RESET);
    }
}

void TCPSessionAdapter::UpdateResetState(int len, analyzer::tcp::TCP_Flags flags) {
    if ( flags.SYN() )
        Weird("SYN_after_reset");

    if ( flags.FIN() )
        Weird("FIN_after_reset");

    if ( len > 0 && ! flags.RST() )
        Weird("data_after_reset");
}

void TCPSessionAdapter::UpdateStateMachine(double t, analyzer::tcp::TCP_Endpoint* endpoint,
                                           analyzer::tcp::TCP_Endpoint* peer, uint32_t base_seq, uint32_t ack_seq,
                                           int len, int32_t delta_last, bool is_orig, analyzer::tcp::TCP_Flags flags,
                                           bool& do_close, bool& gen_event) {
    do_close = false;  // whether to report the connection as closed
    gen_event = false; // if so, whether to generate an event

    switch ( endpoint->state ) {
        case analyzer::tcp::TCP_ENDPOINT_INACTIVE:
            UpdateInactiveState(t, endpoint, peer, base_seq, ack_seq, len, is_orig, flags, do_close, gen_event);
            break;

        case analyzer::tcp::TCP_ENDPOINT_SYN_SENT:
        case analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT:
            UpdateSYN_SentState(endpoint, peer, len, is_orig, flags, do_close, gen_event);
            break;

        case analyzer::tcp::TCP_ENDPOINT_ESTABLISHED:
        case analyzer::tcp::TCP_ENDPOINT_PARTIAL:
            UpdateEstablishedState(endpoint, peer, flags, do_close, gen_event);
            break;

        case analyzer::tcp::TCP_ENDPOINT_CLOSED: UpdateClosedState(t, endpoint, delta_last, flags, do_close); break;

        case analyzer::tcp::TCP_ENDPOINT_RESET: UpdateResetState(len, flags); break;
    }
}

void TCPSessionAdapter::GeneratePacketEvent(uint64_t rel_seq, uint64_t rel_ack, const u_char* data, int len, int caplen,
                                            bool is_orig, analyzer::tcp::TCP_Flags flags) {
    EnqueueConnEvent(tcp_packet, ConnVal(), val_mgr->Bool(is_orig), make_intrusive<StringVal>(flags.AsString()),
                     val_mgr->Count(rel_seq), val_mgr->Count(flags.ACK() ? rel_ack : 0), val_mgr->Count(len),
                     // We need the min() here because Ethernet padding can lead to
                     // caplen > len.
                     make_intrusive<StringVal>(std::min(caplen, len), (const char*)data));
}

bool TCPSessionAdapter::DeliverData(double t, const u_char* data, int len, int caplen, const IP_Hdr* ip,
                                    const struct tcphdr* tp, analyzer::tcp::TCP_Endpoint* endpoint,
                                    uint64_t rel_data_seq, bool is_orig, analyzer::tcp::TCP_Flags flags) {
    return endpoint->DataSent(t, rel_data_seq, len, caplen, data, ip, tp);
}

void TCPSessionAdapter::DeliverPacket(int len, const u_char* data, bool is_orig, uint64_t seq, const IP_Hdr* ip,
                                      int caplen) {
    // Handle child_packet analyzers.  Note: This happens *after* the
    // packet has been processed and the TCP state updated.
    analyzer::analyzer_list::iterator next;

    for ( auto i = packet_children.begin(); i != packet_children.end(); /* nop */ ) {
        auto child = *i;

        if ( child->IsFinished() || child->Removing() ) {
            if ( child->Removing() )
                child->Done();

            DBG_LOG(DBG_ANALYZER, "%s deleted child %s", fmt_analyzer(this).c_str(), fmt_analyzer(child).c_str());
            i = packet_children.erase(i);
            delete child;
        }
        else {
            child->NextPacket(len, data, is_orig, seq, ip, caplen);
            ++i;
        }
    }

    if ( ! reassembling )
        ForwardPacket(len, data, is_orig, seq, ip, caplen);
}

void TCPSessionAdapter::DeliverStream(int len, const u_char* data, bool orig) {
    Analyzer::DeliverStream(len, data, orig);
}

void TCPSessionAdapter::Undelivered(uint64_t seq, int len, bool is_orig) { Analyzer::Undelivered(seq, len, orig); }

void TCPSessionAdapter::FlipRoles() {
    Analyzer::FlipRoles();

    for ( auto* pc : packet_children )
        pc->FlipRoles();

    TCPAnalyzer::GetStats().FlipState(orig->state, resp->state);
    analyzer::tcp::TCP_Endpoint* tmp_ep = resp;
    resp = orig;
    orig = tmp_ep;
    orig->is_orig = ! orig->is_orig;
    resp->is_orig = ! resp->is_orig;
    first_packet_seen = ((first_packet_seen & ORIG) ? RESP : 0) | ((first_packet_seen & RESP) ? ORIG : 0);
    is_partial = 0; // resetting, it may be re-established later
}

void TCPSessionAdapter::UpdateConnVal(RecordVal* conn_val) {
    static const auto& conn_type = zeek::id::find_type<zeek::RecordType>("connection");
    static const int origidx = conn_type->FieldOffset("orig");
    static const int respidx = conn_type->FieldOffset("resp");
    auto* orig_endp_val = conn_val->GetFieldAs<RecordVal>(origidx);
    auto* resp_endp_val = conn_val->GetFieldAs<RecordVal>(respidx);

    orig_endp_val->Assign(0, orig->Size());
    orig_endp_val->Assign(1, orig->state);
    resp_endp_val->Assign(0, resp->Size());
    resp_endp_val->Assign(1, resp->state);

    // Call children's UpdateConnVal
    Analyzer::UpdateConnVal(conn_val);

    // Have to do packet_children ourselves.
    for ( Analyzer* a : packet_children )
        a->UpdateConnVal(conn_val);
}

void TCPSessionAdapter::AttemptTimer(double /* t */) {
    if ( ! is_active )
        return;

    if ( (orig->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT ||
          orig->state == analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT) &&
         resp->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) {
        Event(connection_attempt);
        is_active = 0;

        // All done with this connection.
        session_mgr->Remove(Conn());
    }
}

void TCPSessionAdapter::PartialCloseTimer(double /* t */) {
    if ( ! is_active )
        return;

    if ( orig->state != analyzer::tcp::TCP_ENDPOINT_INACTIVE && resp->state != analyzer::tcp::TCP_ENDPOINT_INACTIVE &&
         (! orig->did_close || ! resp->did_close) ) {
        if ( orig->state == analyzer::tcp::TCP_ENDPOINT_RESET || resp->state == analyzer::tcp::TCP_ENDPOINT_RESET )
            // Presumably the RST is what caused the partial
            // close.  Don't report it.
            return;

        Event(connection_partial_close);
        session_mgr->Remove(Conn());
    }
}

void TCPSessionAdapter::ExpireTimer(double t) {
    if ( ! is_active )
        return;

    if ( Conn()->LastTime() + zeek::detail::tcp_connection_linger < t ) {
        if ( orig->did_close || resp->did_close ) {
            // No activity for tcp_connection_linger seconds, and
            // at least one side has closed.  See whether
            // connection has likely terminated.
            if ( (orig->did_close && resp->did_close) ||
                 (orig->state == analyzer::tcp::TCP_ENDPOINT_RESET ||
                  resp->state == analyzer::tcp::TCP_ENDPOINT_RESET) ||
                 (orig->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ||
                  resp->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE) ) {
                // Either both closed, or one RST,
                // or half-closed.

                // The Timer has Ref()'d us and won't Unref()
                // us until we return, so it's safe to have
                // the session remove and Unref() us here.
                Event(connection_timeout);
                is_active = 0;
                session_mgr->Remove(Conn());
                return;
            }
        }

        if ( resp->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) {
            if ( orig->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) {
                // Nothing ever happened on this connection.
                // This can occur when we see a trashed
                // packet - it's discarded by NextPacket
                // before setting up an attempt timer,
                // so we need to clean it up here.
                Event(connection_timeout);
                session_mgr->Remove(Conn());
                return;
            }
        }
    }

    // Connection still active, so reschedule timer.
    // ### if PQ_Element's were Obj's, could just Ref the timer
    // and adjust its value here, instead of creating a new timer.
    ADD_ANALYZER_TIMER(&TCPSessionAdapter::ExpireTimer, t + zeek::detail::tcp_session_timer, false,
                       zeek::detail::TIMER_TCP_EXPIRE);
}

void TCPSessionAdapter::ResetTimer(double /* t */) {
    if ( ! is_active )
        return;

    if ( ! BothClosed() )
        ConnectionReset();

    session_mgr->Remove(Conn());
}

void TCPSessionAdapter::DeleteTimer(double /* t */) { session_mgr->Remove(Conn()); }

void TCPSessionAdapter::ConnDeleteTimer(double t) { Conn()->DeleteTimer(t); }

void TCPSessionAdapter::SetContentsFile(unsigned int direction, FilePtr f) {
    if ( direction == analyzer::CONTENTS_NONE ) {
        orig->SetContentsFile(nullptr);
        resp->SetContentsFile(nullptr);
    }

    else {
        if ( direction == analyzer::CONTENTS_ORIG || direction == analyzer::CONTENTS_BOTH )
            orig->SetContentsFile(f);
        if ( direction == analyzer::CONTENTS_RESP || direction == analyzer::CONTENTS_BOTH )
            resp->SetContentsFile(f);
    }
}

FilePtr TCPSessionAdapter::GetContentsFile(unsigned int direction) const {
    switch ( direction ) {
        case analyzer::CONTENTS_NONE: return nullptr;

        case analyzer::CONTENTS_ORIG: return orig->GetContentsFile();

        case analyzer::CONTENTS_RESP: return resp->GetContentsFile();

        case analyzer::CONTENTS_BOTH:
            if ( orig->GetContentsFile() != resp->GetContentsFile() )
                // This is an "error".
                return nullptr;
            else
                return orig->GetContentsFile();

        default: break;
    }

    reporter->Error("bad direction %u in TCPSessionAdapter::GetContentsFile", direction);
    return nullptr;
}

void TCPSessionAdapter::ConnectionClosed(analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer,
                                         bool gen_event) {
    const analyzer::analyzer_list& children(GetChildren());

    for ( Analyzer* a : children )
        // Using this type of cast here is nasty (will crash if
        // we inadvertently have a child analyzer that's not a
        // TCP_ApplicationAnalyzer), but we have to ...
        static_cast<analyzer::tcp::TCP_ApplicationAnalyzer*>(a)->ConnectionClosed(endpoint, peer, gen_event);

    if ( DataPending(endpoint) ) {
        // Don't close out the connection yet, there's still data to
        // deliver.
        close_deferred = 1;
        if ( ! deferred_gen_event )
            deferred_gen_event = gen_event;
        return;
    }

    close_deferred = 0;

    if ( endpoint->did_close )
        return; // nothing new to report

    endpoint->did_close = true;

    int close_complete = endpoint->state == analyzer::tcp::TCP_ENDPOINT_RESET || peer->did_close ||
                         peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE;

    if ( DEBUG_tcp_connection_close ) {
        DEBUG_MSG("%.6f close_complete=%d tcp_close_delay=%f\n", run_state::network_time, close_complete,
                  zeek::detail::tcp_close_delay);
    }

    if ( close_complete ) {
        if ( endpoint->prev_state != analyzer::tcp::TCP_ENDPOINT_INACTIVE ||
             peer->state != analyzer::tcp::TCP_ENDPOINT_INACTIVE ) {
            if ( deferred_gen_event ) {
                gen_event = true;
                deferred_gen_event = 0; // clear flag
            }

            // We have something interesting to report.
            if ( gen_event ) {
                if ( peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE )
                    ConnectionFinished(true);
                else
                    ConnectionFinished(false);
            }
        }

        CancelTimers();

        // Note, even if tcp_close_delay is zero, we can't
        // simply do:
        //
        //	session_mgr->Remove(this);
        //
        // here, because that would cause the object to be
        // deleted out from under us.
        if ( zeek::detail::tcp_close_delay != 0.0 )
            ADD_ANALYZER_TIMER(&TCPSessionAdapter::ConnDeleteTimer, Conn()->LastTime() + zeek::detail::tcp_close_delay,
                               false, zeek::detail::TIMER_CONN_DELETE);
        else
            ADD_ANALYZER_TIMER(&TCPSessionAdapter::DeleteTimer, Conn()->LastTime(), false,
                               zeek::detail::TIMER_TCP_DELETE);
    }

    else { // We haven't yet seen a full close.
        if ( endpoint->prev_state ==
             analyzer::tcp::TCP_ENDPOINT_INACTIVE ) { // First time we've seen anything from this side.
            if ( connection_partial_close )
                ADD_ANALYZER_TIMER(&TCPSessionAdapter::PartialCloseTimer,
                                   Conn()->LastTime() + zeek::detail::tcp_partial_close_delay, false,
                                   zeek::detail::TIMER_TCP_PARTIAL_CLOSE);
        }

        else {
            // Create a timer to look for the other side closing,
            // too.
            ADD_ANALYZER_TIMER(&TCPSessionAdapter::ExpireTimer, Conn()->LastTime() + zeek::detail::tcp_session_timer,
                               false, zeek::detail::TIMER_TCP_EXPIRE);
        }
    }
}

void TCPSessionAdapter::ConnectionFinished(bool half_finished) {
    const analyzer::analyzer_list& children(GetChildren());

    for ( Analyzer* a : children )
        // Again, nasty - see TCPSessionAdapter::ConnectionClosed.
        static_cast<analyzer::tcp::TCP_ApplicationAnalyzer*>(a)->ConnectionFinished(half_finished);

    if ( half_finished )
        Event(connection_half_finished);
    else
        Event(connection_finished);

    is_active = 0;
}

void TCPSessionAdapter::ConnectionReset() {
    Event(connection_reset);

    const analyzer::analyzer_list& children(GetChildren());
    for ( Analyzer* a : children )
        static_cast<analyzer::tcp::TCP_ApplicationAnalyzer*>(a)->ConnectionReset();

    is_active = 0;
}

bool TCPSessionAdapter::HadGap(bool is_orig) const {
    analyzer::tcp::TCP_Endpoint* endp = is_orig ? orig : resp;
    return endp && endp->HadGap();
}

void TCPSessionAdapter::AddChildPacketAnalyzer(analyzer::Analyzer* a) {
    DBG_LOG(DBG_ANALYZER, "%s added packet child %s", this->GetAnalyzerName(), a->GetAnalyzerName());

    packet_children.push_back(a);
    a->SetParent(this);
}

bool TCPSessionAdapter::DataPending(analyzer::tcp::TCP_Endpoint* closing_endp) {
    if ( Skipping() )
        return false;

    return closing_endp->DataPending();
}

void TCPSessionAdapter::EndpointEOF(analyzer::tcp::TCP_Reassembler* endp) {
    if ( connection_EOF )
        EnqueueConnEvent(connection_EOF, ConnVal(), val_mgr->Bool(endp->IsOrig()));

    const analyzer::analyzer_list& children(GetChildren());
    for ( Analyzer* a : children )
        static_cast<analyzer::tcp::TCP_ApplicationAnalyzer*>(a)->EndpointEOF(endp->IsOrig());

    if ( close_deferred ) {
        if ( DataPending(endp->Endpoint()) ) {
            if ( BothClosed() )
                Weird("pending_data_when_closed");

            // Defer further, until the other endpoint
            // EOF's, too.
        }

        ConnectionClosed(endp->Endpoint(), endp->Endpoint()->peer, deferred_gen_event);
        close_deferred = 0;
    }
}

void TCPSessionAdapter::PacketWithRST() {
    const analyzer::analyzer_list& children(GetChildren());
    for ( Analyzer* a : children )
        static_cast<analyzer::tcp::TCP_ApplicationAnalyzer*>(a)->PacketWithRST();
}

void TCPSessionAdapter::CheckPIA_FirstPacket(bool is_orig, const IP_Hdr* ip) {
    if ( is_orig && ! (first_packet_seen & ORIG) ) {
        if ( auto* pia = static_cast<analyzer::pia::PIA_TCP*>(Conn()->GetPrimaryPIA()) )
            pia->FirstPacket(is_orig, ip);
        first_packet_seen |= ORIG;
    }

    if ( ! is_orig && ! (first_packet_seen & RESP) ) {
        if ( auto* pia = static_cast<analyzer::pia::PIA_TCP*>(Conn()->GetPrimaryPIA()) )
            pia->FirstPacket(is_orig, ip);
        first_packet_seen |= RESP;
    }
}

bool TCPSessionAdapter::IsReuse(double t, const u_char* pkt) {
    const struct tcphdr* tp = (const struct tcphdr*)pkt;

    if ( unsigned(tp->th_off) < sizeof(struct tcphdr) / 4 )
        // Bogus header, don't interpret further.
        return false;

    analyzer::tcp::TCP_Endpoint* conn_orig = orig;

    // Reuse only occurs on initial SYN's, except for half connections
    // it can occur on SYN-acks.
    if ( ! (tp->th_flags & TH_SYN) )
        return false;

    if ( (tp->th_flags & TH_ACK) ) {
        if ( orig->state != analyzer::tcp::TCP_ENDPOINT_INACTIVE )
            // Not a half connection.
            return false;

        conn_orig = resp;
    }

    if ( ! IsClosed() ) {
        uint32_t base_seq = ntohl(tp->th_seq);
        if ( base_seq == conn_orig->StartSeq() )
            return false;

        if ( (tp->th_flags & TH_ACK) == 0 && conn_orig->state == analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT &&
             resp->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE && base_seq == resp->StartSeq() ) {
            // This is an initial SYN with the right sequence
            // number, and the state is consistent with the
            // SYN & the SYN-ACK being flipped (e.g., due to
            // reading from two interfaces w/ interrupt
            // coalescence).  Don't treat this as a reuse.
            // NextPacket() will flip set the connection
            // state correctly
            return false;
        }

        if ( conn_orig->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT )
            Weird("SYN_seq_jump");
        else
            Weird("active_connection_reuse");
    }

    else if ( (orig->IsActive() || resp->IsActive()) && orig->state != analyzer::tcp::TCP_ENDPOINT_RESET &&
              resp->state != analyzer::tcp::TCP_ENDPOINT_RESET )
        Weird("active_connection_reuse");

    else if ( t - Conn()->LastTime() < zeek::detail::tcp_connection_linger &&
              orig->state != analyzer::tcp::TCP_ENDPOINT_RESET && resp->state != analyzer::tcp::TCP_ENDPOINT_RESET )
        Weird("premature_connection_reuse");

    return true;
}

void TCPSessionAdapter::AddExtraAnalyzers(Connection* conn) {
    static zeek::Tag analyzer_connsize = analyzer_mgr->GetComponentTag("CONNSIZE");
    static zeek::Tag analyzer_tcpstats = analyzer_mgr->GetComponentTag("TCPSTATS");

    // We have to decide whether to reassemble the stream.
    // We turn it on right away if we already have an app-layer
    // analyzer, reassemble_first_packets is true, or the user
    // asks us to do so.  In all other cases, reassembly may
    // be turned on later by the TCP PIA.

    bool reass = (! GetChildren().empty()) || zeek::detail::dpd_reassemble_first_packets ||
                 zeek::detail::tcp_content_deliver_all_orig || zeek::detail::tcp_content_deliver_all_resp;

    if ( tcp_contents && ! reass ) {
        static auto tcp_content_delivery_ports_orig = id::find_val<TableVal>("tcp_content_delivery_ports_orig");
        static auto tcp_content_delivery_ports_resp = id::find_val<TableVal>("tcp_content_delivery_ports_resp");
        const auto& dport = val_mgr->Port(ntohs(Conn()->RespPort()), TRANSPORT_TCP);

        if ( ! reass )
            reass = (bool)tcp_content_delivery_ports_orig->FindOrDefault(dport);

        if ( ! reass )
            reass = (bool)tcp_content_delivery_ports_resp->FindOrDefault(dport);
    }

    if ( reass )
        EnableReassembly();

    if ( analyzer_mgr->IsEnabled(analyzer_tcpstats) )
        // Add TCPStats analyzer. This needs to see packets so
        // we cannot add it as a normal child.
        AddChildPacketAnalyzer(new analyzer::tcp::TCPStats_Analyzer(conn));

    if ( analyzer_mgr->IsEnabled(analyzer_connsize) )
        // Add ConnSize analyzer. Needs to see packets, not stream.
        AddChildPacketAnalyzer(new analyzer::conn_size::ConnSize_Analyzer(conn));
}

void TCPSessionAdapter::SynWeirds(analyzer::tcp::TCP_Flags flags, analyzer::tcp::TCP_Endpoint* endpoint,
                                  int data_len) const {
    if ( flags.RST() )
        endpoint->Conn()->Weird("TCP_christmas", "", GetAnalyzerName());

    if ( flags.URG() )
        endpoint->Conn()->Weird("baroque_SYN", "", GetAnalyzerName());

    if ( data_len > 0 )
        // Not technically wrong according to RFC 793, but the other side
        // would be forced to buffer data until the handshake succeeds, and
        // that could be bad in some cases, e.g. SYN floods.
        // T/TCP definitely complicates this.
        endpoint->Conn()->Weird("SYN_with_data", "", GetAnalyzerName());
}

int TCPSessionAdapter::ParseTCPOptions(const struct tcphdr* tcp, bool is_orig) {
    // Parse TCP options.
    const u_char* options = (const u_char*)tcp + sizeof(struct tcphdr);
    const u_char* opt_end = (const u_char*)tcp + static_cast<ptrdiff_t>(tcp->th_off * 4);
    std::vector<const u_char*> opts;

    while ( options < opt_end ) {
        unsigned int opt = options[0];

        unsigned int opt_len;

        if ( opt < 2 )
            opt_len = 1;

        else if ( options + 1 >= opt_end )
            // We've run off the end, no room for the length.
            break;

        else
            opt_len = options[1];

        if ( opt_len == 0 )
            break; // trashed length field

        if ( options + opt_len > opt_end )
            // No room for rest of option.
            break;

        opts.emplace_back(options);
        options += opt_len;

        if ( opt == TCPOPT_EOL )
            // All done - could flag if more junk left over ....
            break;
    }

    if ( tcp_option )
        for ( const auto& o : opts ) {
            auto kind = o[0];
            auto length = kind < 2 ? 1 : o[1];
            EnqueueConnEvent(tcp_option, ConnVal(), val_mgr->Bool(is_orig), val_mgr->Count(kind),
                             val_mgr->Count(length));
        }

    if ( tcp_options ) {
        auto option_list = make_intrusive<VectorVal>(BifType::Vector::TCP::OptionList);

        auto add_option_data = [](const RecordValPtr& rv, const u_char* odata, int olen) {
            if ( olen <= 2 )
                return;

            auto data_len = olen - 2;
            auto data = reinterpret_cast<const char*>(odata + 2);
            rv->Assign(2, make_intrusive<StringVal>(data_len, data));
        };

        for ( const auto& o : opts ) {
            auto kind = o[0];
            auto length = kind < 2 ? 1 : o[1];
            auto option_record = make_intrusive<RecordVal>(BifType::Record::TCP::Option);
            option_list->Assign(option_list->Size(), option_record);
            option_record->Assign(0, kind);
            option_record->Assign(1, length);

            switch ( kind ) {
                case 2:
                    // MSS
                    if ( length == 4 ) {
                        auto mss = ntohs(*reinterpret_cast<const uint16_t*>(o + 2));
                        option_record->Assign(3, mss);
                    }
                    else {
                        add_option_data(option_record, o, length);
                        Weird("tcp_option_mss_invalid_len", util::fmt("%d", length));
                    }
                    break;

                case 3:
                    // window scale
                    if ( length == 3 ) {
                        auto scale = o[2];
                        option_record->Assign(4, scale);
                    }
                    else {
                        add_option_data(option_record, o, length);
                        Weird("tcp_option_window_scale_invalid_len", util::fmt("%d", length));
                    }
                    break;

                case 4:
                    // sack permitted (implicit boolean)
                    if ( length != 2 ) {
                        add_option_data(option_record, o, length);
                        Weird("tcp_option_sack_invalid_len", util::fmt("%d", length));
                    }
                    break;

                case 5:
                    // SACK blocks (1-4 pairs of 32-bit begin+end pointers)
                    if ( length == 10 || length == 18 || length == 26 || length == 34 ) {
                        auto p = reinterpret_cast<const uint32_t*>(o + 2);
                        auto num_pointers = (length - 2) / 4;
                        auto vt = id::index_vec;
                        auto sack = make_intrusive<VectorVal>(std::move(vt));

                        for ( auto i = 0; i < num_pointers; ++i )
                            sack->Assign(sack->Size(), val_mgr->Count(ntohl(p[i])));

                        option_record->Assign(5, sack);
                    }
                    else {
                        add_option_data(option_record, o, length);
                        Weird("tcp_option_sack_blocks_invalid_len", util::fmt("%d", length));
                    }
                    break;

                case 8:
                    // timestamps
                    if ( length == 10 ) {
                        uint32_t send = ntohl(*reinterpret_cast<const uint32_t*>(o + 2));
                        uint32_t echo = ntohl(*reinterpret_cast<const uint32_t*>(o + 6));
                        option_record->Assign(6, send);
                        option_record->Assign(7, echo);
                    }
                    else {
                        add_option_data(option_record, o, length);
                        Weird("tcp_option_timestamps_invalid_len", util::fmt("%d", length));
                    }
                    break;

                case 27:
                    // TCP Quick Start Response
                    if ( length == 8 ) {
                        auto rate = o[2];
                        auto ttl_diff = o[3];
                        uint32_t qs_nonce = ntohl(*reinterpret_cast<const uint32_t*>(o + 4));
                        option_record->Assign(8, rate);
                        option_record->Assign(9, ttl_diff);
                        option_record->Assign(10, qs_nonce);
                    }
                    else {
                        add_option_data(option_record, o, length);
                        Weird("tcp_option_qsresponse_invalid_len", util::fmt("%d", length));
                    }
                    break;

                case 28:
                    // TCP User Timeout option UTO
                    if ( length != 4 ) {
                        add_option_data(option_record, o, length);
                        Weird("tcp_option_uto_invalid_len", util::fmt("%d", length));
                    }
                    break;

                case 29:
                    // TCP Auth Option AO
                    if ( length < 4 ) {
                        add_option_data(option_record, o, length);
                        Weird("tcp_option_ao_invalid_len", util::fmt("%d", length));
                    }
                    break;

                case 34:
                    // TCP Fast open TFO
                    if ( (length != 2) && (length < 6 || length > 18) ) {
                        add_option_data(option_record, o, length);
                        Weird("tcp_option_tfo_invalid_len", util::fmt("%d", length));
                    }
                    break;

                default: add_option_data(option_record, o, length); break;
            }
        }

        EnqueueConnEvent(tcp_options, ConnVal(), val_mgr->Bool(is_orig), std::move(option_list));
    }

    if ( options < opt_end )
        return -1;

    return 0;
}

void TCPSessionAdapter::CheckRecording(bool need_contents, analyzer::tcp::TCP_Flags flags) {
    bool record_current_content = need_contents || Conn()->RecordContents();
    bool record_current_packet = Conn()->RecordPackets() || flags.SYN() || flags.FIN() || flags.RST();

    Conn()->SetRecordCurrentContent(record_current_content);
    Conn()->SetRecordCurrentPacket(record_current_packet);
}
