//--------------------------------------------------------------------------
// Copyright (C) 2016-2026 Cisco and/or its affiliates. All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License Version 2 as published
// by the Free Software Foundation.  You may not use, modify or distribute
// this program under any other version of the GNU General Public License.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//--------------------------------------------------------------------------
// analyzer_command.cc author Michael Altizer <mialtize@cisco.com>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "analyzer_command.h"

#include <cassert>
#include <sys/time.h>

#include "control/control.h"
#include "framework/module.h"
#include "log/messages.h"
#include "managers/module_manager.h"
#include "packet_io/sfdaq_instance.h"
#include "protocols/packet_manager.h"
#include "target_based/host_attributes.h"
#include "time/packet_time.h"
#include "utils/stats.h"

#include "analyzer.h"
#include "reload_tracker.h"
#include "reload_tuner.h"
#include <unistd.h>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "snort_config.h"
#include "thread_config.h"
#include "swapper.h"

using namespace snort;

static THREAD_LOCAL timeval report_time{};
static const timeval report_period = {10, 0};
static const char* invalid_ip = "<invalid>";

// Static helper for formatting IPv4/IPv6 addresses
static void format_ip_addr(const struct in6_addr& addr, uint32_t is_ipv6, char* buf, size_t buflen)
{
    if (is_ipv6) {
        if(!inet_ntop(AF_INET6, &addr, buf, buflen))
            strcpy(buf, invalid_ip);
    }
    else {
        struct in_addr v4addr;
        std::memcpy(&v4addr, &addr, sizeof(v4addr));
        if(!inet_ntop(AF_INET, &v4addr, buf, buflen))
            strcpy(buf, invalid_ip);
    }
}

static void tuner_next(ReloadResourceTuner* tuner)
{
    LogMessage("ReloadResourceTuner[%u] running %s\n", get_instance_id(), tuner->name());

    timeval now;
    packet_gettimeofday(&now);
    timeradd(&now, &report_period, &report_time);
}

static void tuner_update(ReloadResourceTuner* tuner)
{
    timeval now;
    packet_gettimeofday(&now);
    if (timercmp(&now, &report_time, >))
    {
        timeradd(&now, &report_period, &report_time);
        tuner->report_progress();
    }
}

void AnalyzerCommand::log_message(ControlConn* ctrlcon, const char* format, va_list& ap)
{
    if (ctrlcon && !ctrlcon->is_local())
    {
        va_list rap;
        va_copy(rap, ap);
        ctrlcon->respond(format, rap);
        va_end(rap);
    }
    LogMessage(format, ap);
}

void AnalyzerCommand::log_message(ControlConn* ctrlcon, const char* format, ...)
{
    va_list args;
    va_start(args, format);
    log_message(ctrlcon, format, args);
    va_end(args);
}

void AnalyzerCommand::log_message(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    log_message(ctrlcon, format, args);
    va_end(args);
}

bool ACStart::execute(Analyzer& analyzer, void**)
{
    analyzer.start();
    return true;
}

bool ACRun::execute(Analyzer& analyzer, void**)
{
    analyzer.run(paused);
    paused = false;
    return true;
}

bool ACStop::execute(Analyzer& analyzer, void**)
{
    analyzer.stop();
    return true;
}

bool ACPause::execute(Analyzer& analyzer, void**)
{
    analyzer.pause();
    return true;
}

bool ACResume::execute(Analyzer& analyzer, void**)
{
    analyzer.resume(msg_cnt);
    return true;
}

bool ACRotate::execute(Analyzer& analyzer, void**)
{
    analyzer.rotate();
    return true;
}

bool ACGetStats::execute(Analyzer&, void**)
{
    // FIXIT-P This incurs locking on all threads to retrieve stats.  It
    // could be reimplemented to optimize for large thread counts by
    // retrieving stats in the command and accumulating in the main thread.
    PacketManager::accumulate();
    ModuleManager::accumulate(SnortConfig::get_conf());
    return true;
}

ACGetStats::~ACGetStats()
{

    // FIXIT-L This should track the owner so it can dump stats to the
    // shell instead of the logs when initiated by a shell command
    DropStats(ctrlcon);
    LogRespond(ctrlcon, "==================================================\n"); // Marking End of stats

    ModuleManager::clear_global_active_counters();
}

bool ACResetStats::execute(Analyzer&, void**)
{
    ModuleManager::reset_stats(requested_type);
    return true;
}

ACResetStats::ACResetStats(clear_counter_type_t requested_type_l) : requested_type(
        requested_type_l) { }

bool ACSwap::execute(Analyzer& analyzer, void** ac_state)
{
    if (analyzer.get_state() != Analyzer::State::PAUSED and
        analyzer.get_state() != Analyzer::State::RUNNING)
        return false;

    if (ps)
    {
        ps->apply(analyzer);

        const SnortConfig* sc = ps->get_new_conf();
        if ( sc )
        {
            std::list<ReloadResourceTuner*>* reload_tuners;

            if ( !*ac_state )
            {
                LogMessage("ReloadResourceTuner[%u] start\n", get_instance_id());
                reload_tuners = new std::list<ReloadResourceTuner*>(sc->get_reload_resource_tuners());
                std::list<ReloadResourceTuner*>::iterator rtt = reload_tuners->begin();
                while ( rtt != reload_tuners->end() )
                {
                    if ( (*rtt)->tinit() )
                        ++rtt;
                    else
                        rtt = reload_tuners->erase(rtt);
                }
                *ac_state = reload_tuners;
                if (!reload_tuners->empty())
                    tuner_next(reload_tuners->front());
            }
            else
                reload_tuners = (std::list<ReloadResourceTuner*>*)*ac_state;

            if ( !reload_tuners->empty() )
            {
                auto rrt = reload_tuners->front();
                bool tuning_complete = analyzer.is_idling() ? rrt->tune_idle_context() : rrt->tune_packet_context();
                if (tuning_complete)
                {
                    reload_tuners->pop_front();
                    if (!reload_tuners->empty())
                        tuner_next(reload_tuners->front());
                }
                else
                    tuner_update(rrt);
            }

            // check for empty again and free list instance if we are done
            if ( reload_tuners->empty() )
            {
                LogMessage("ReloadResourceTuner[%d] complete\n", get_instance_id());
                delete reload_tuners;
                ps->finish();
                return true;
            }

            return false;
        }
    }

    return true;
}

ACSwap::~ACSwap()
{
    if (ps)
    {
        SnortConfig* sc = ps->get_new_conf();
        if ( sc )
            sc->clear_reload_resource_tuner_list();
    }
    delete ps;
    HostAttributesManager::swap_cleanup();
    ReloadTracker::end(ctrlcon);
    log_message("== reload complete\n");
}

bool ACHostAttributesSwap::execute(Analyzer&, void**)
{
    HostAttributesManager::initialize();
    return true;
}

ACHostAttributesSwap::~ACHostAttributesSwap()
{
    HostAttributesManager::swap_cleanup();
    ReloadTracker::end(ctrlcon);
    log_message("== reload host attributes complete\n");
}

bool ACDAQSwap::execute(Analyzer& analyzer, void**)
{
    analyzer.reload_daq();
    return true;
}

ACDAQSwap::~ACDAQSwap()
{
    LogMessage("== daq module reload complete\n");
}

bool ACScratchUpdate::execute(Analyzer&, void**)
{
    for ( auto* s : handlers )
    {
        if ( s )
            s->update(sc);
    }
    return true;
}

ACScratchUpdate::~ACScratchUpdate()
{
    log_message("== scratch update complete\n");
    ReloadTracker::end(ctrlcon, true);
}

SFDAQInstance* AnalyzerCommand::get_daq_instance(Analyzer& analyzer)
{
    return analyzer.get_daq_instance();
}

ACShowSnortCPU::~ACShowSnortCPU()
{
    double cpu_usage_30s = 0.0;
    double cpu_usage_120s = 0.0;
    double cpu_usage_300s = 0.0;
    int instance = 0;

    for (const auto& cu : cpu_usage) 
    {
        LogRespond(ctrlcon, "%-3d \t%-6d \t%.1f%% \t%.1f%% \t%.1f%%\n",
            instance, ThreadConfig::get_instance_tid(instance), cu.cpu_usage_30s,
            cu.cpu_usage_120s, cu.cpu_usage_300s);

        cpu_usage_30s += cu.cpu_usage_30s;
        cpu_usage_120s += cu.cpu_usage_120s;
        cpu_usage_300s += cu.cpu_usage_300s;
		instance++;
    }

    if (instance)
        LogRespond(ctrlcon, "\nSummary \t%.1f%% \t%.1f%% \t%.1f%%\n",
            cpu_usage_30s/instance, cpu_usage_120s/instance,
            cpu_usage_300s/instance);
}

bool ACShowSnortCPU::execute(Analyzer& analyzer, void**)
{
    DIOCTL_GetCpuProfileData get_data = {};

    SFDAQInstance* instance = get_daq_instance(analyzer);
    if (!instance)
        return true;
    
    int instance_id = get_instance_id();

    int status = instance->ioctl((DAQ_IoctlCmd)DIOCTL_GET_CPU_PROFILE_DATA,
        (void *)(&get_data), sizeof(DIOCTL_GetCpuProfileData));

    if (DAQ_SUCCESS != status)
    {
        LogRespond(ctrlcon, "Fetching profile data failed from DAQ instance %d\n", instance_id);
        return true; 
    }

    auto& stat = cpu_usage[instance_id];
    stat.cpu_usage_30s = static_cast<double>(get_data.cpu_usage_percent_30s);
    stat.cpu_usage_120s = static_cast<double>(get_data.cpu_usage_percent_120s);
    stat.cpu_usage_300s = static_cast<double>(get_data.cpu_usage_percent_300s); 
 
    return true;
}

ACShowSnortPacketLatencyData::~ACShowSnortPacketLatencyData()
{
    const std::array<const char*, 3> protocol_names = { "TCP", "UDP", "Others" };
    int instance = 0;

    // Print header for each instance
    for (auto& ld: latency_data)
    {
        LogRespond(ctrlcon, "\n================ Instance: %-3d ================\nThreadID: %d\n", instance, ThreadConfig::get_instance_tid(instance));
        for (size_t i = 0; i < protocol_names.size(); i++)
        {
            auto& latency_data_proto = ld.snort_latency_data[i];
            double average_pkt_time = latency_data_proto.pkt_count > 0 ? 
            (latency_data_proto.sum_time*1.0 / latency_data_proto.pkt_count / 1000.0) : 0.0;

            char max_pkt_src_ip[INET6_ADDRSTRLEN], max_pkt_dst_ip[INET6_ADDRSTRLEN];
            char up_max_pkt_src_ip[INET6_ADDRSTRLEN], up_max_pkt_dst_ip[INET6_ADDRSTRLEN];

            format_ip_addr(latency_data_proto.max_pkt_src_addr, latency_data_proto.max_pkt_src_ipv6, max_pkt_src_ip, sizeof(max_pkt_src_ip));
            format_ip_addr(latency_data_proto.max_pkt_dst_addr, latency_data_proto.max_pkt_dst_ipv6, max_pkt_dst_ip, sizeof(max_pkt_dst_ip));
            format_ip_addr(latency_data_proto.snort_up_max_pkt_src_addr, latency_data_proto.snort_up_max_pkt_src_ipv6, up_max_pkt_src_ip, sizeof(up_max_pkt_src_ip));
            format_ip_addr(latency_data_proto.snort_up_max_pkt_dst_addr, latency_data_proto.snort_up_max_pkt_dst_ipv6, up_max_pkt_dst_ip, sizeof(up_max_pkt_dst_ip));

            LogRespond(ctrlcon, "  [%s]\n", protocol_names[i]);
            if (latency_data_proto.snort_up_max_pkt_time != 0)
                LogRespond(ctrlcon, "    Max Latency (us, since restart): %lu\n", latency_data_proto.snort_up_max_pkt_time/1000);
            if (latency_data_proto.pkt_count != 0)
                LogRespond(ctrlcon, "    Total Packet Count in last 5mins: %lu\n", latency_data_proto.pkt_count);
            if (latency_data_proto.sum_time != 0)
                LogRespond(ctrlcon, "    Sum of Packet Latencies in last 5mins (us): %lu\n", latency_data_proto.sum_time/1000);
            if (latency_data_proto.conn_meta_null_counters != 0)
                LogRespond(ctrlcon, "    Null ConnMeta Count in last 5mins: %lu\n", latency_data_proto.conn_meta_null_counters);
            if (average_pkt_time != 0.0)
                LogRespond(ctrlcon, "    Average Packet Latency in last 5mins (us): %.3f\n", average_pkt_time);
            if (latency_data_proto.max_pkt_time != 0)
                LogRespond(ctrlcon, "    Maximum Observed Latency in last 5mins (us): %lu\n", latency_data_proto.max_pkt_time/1000);

            // Only print 5-tuple lines if any port is non-zero and if it has valid IPs
            if ( (latency_data_proto.max_pkt_src_port != 0 || latency_data_proto.max_pkt_dst_port != 0)
                && (strcmp(max_pkt_src_ip, invalid_ip) != 0)
                && (strcmp(max_pkt_dst_ip, invalid_ip) != 0) )
                LogRespond(ctrlcon, "    5min Max Latency Packet 5-tuple: %s:%u -> %s:%u (%s)\n",
                    max_pkt_src_ip,
                    latency_data_proto.max_pkt_src_port,
                    max_pkt_dst_ip,
                    latency_data_proto.max_pkt_dst_port,
                    protocol_names[i]);
            if ( (latency_data_proto.snort_up_max_pkt_src_port != 0 || latency_data_proto.snort_up_max_pkt_dst_port != 0)
                && (strcmp(up_max_pkt_src_ip, invalid_ip) != 0)
                && (strcmp(up_max_pkt_dst_ip, invalid_ip) != 0) )
                LogRespond(ctrlcon, "    Max Latency Packet 5-tuple (us, since restart): %s:%u -> %s:%u (%s)\n",
                    up_max_pkt_src_ip,
                    latency_data_proto.snort_up_max_pkt_src_port,
                    up_max_pkt_dst_ip,
                    latency_data_proto.snort_up_max_pkt_dst_port,
                    protocol_names[i]);

        }
        LogRespond(ctrlcon, "------------------------------------------------------------\n");
		instance++;
    }
}

bool ACShowSnortPacketLatencyData::execute(Analyzer& analyzer, void**)
{
    DIOCTL_GetSnortLatencyData latency_data_array = {};

    SFDAQInstance* instance = get_daq_instance(analyzer);
    if (!instance){
        LogRespond(ctrlcon, "Fetching latency data failed from DAQ instance\n");
        return true;
    }
    int instance_id = get_instance_id();
    int status = instance->ioctl(
                    (DAQ_IoctlCmd)DIOCTL_GET_SNORT_LATENCY_DATA,
                    (void *)(&latency_data_array),
                    sizeof(DIOCTL_GetSnortLatencyData));

    if (DAQ_SUCCESS != status)
    {
        LogRespond(ctrlcon, "Fetching latency data failed from DAQ instance\n");
        return true;
    }

    auto& stat = latency_data[instance_id];
    stat = latency_data_array;
    return true;
}
