#include "ips_signatures.h"

#include <sstream>
#include <algorithm>

#include "ips_comp.h"
#include "ips_basic_policy.h"
#include "snort_basic_policy.h"
#include "generic_rulebase/parameters_config.h"
#include "generic_rulebase/triggers_config.h"
#include "rule_detection.h"
#include "helper.h"
#include "config.h"
#include "context.h"
#include "ips_entry.h"
#include "ips_metric.h"
#include "ips_common_types.h"

USE_DEBUG_FLAG(D_IPS);

using namespace IPSSignatureSubTypes;
using namespace ReportIS;
using namespace std;
using MatchType = BaseSignature::MatchType;

static const LogTriggerConf default_triger;

static const map<IPSLevel, Severity> severities = {
    { IPSLevel::CRITICAL,   Severity::CRITICAL },
    { IPSLevel::HIGH,        Severity::HIGH },
    { IPSLevel::MEDIUM_HIGH, Severity::HIGH },
    { IPSLevel::MEDIUM,      Severity::MEDIUM },
    { IPSLevel::MEDIUM_LOW,  Severity::LOW },
    { IPSLevel::LOW,         Severity::LOW },
    { IPSLevel::VERY_LOW,    Severity::INFO }
};

static const map<string, IPSLevel> levels = {
    { "Critical",    IPSLevel::CRITICAL },
    { "High",        IPSLevel::HIGH },
    { "Medium High", IPSLevel::MEDIUM_HIGH },
    { "Medium",      IPSLevel::MEDIUM },
    { "Medium Low",  IPSLevel::MEDIUM_LOW },
    { "Low",         IPSLevel::LOW },
    { "Very Low",    IPSLevel::VERY_LOW }
};

int CompleteSignature::yield_on_load_cnt = DEFAULT_IPS_YIELD_COUNT;

static IPSLevel
getLevel(const string &level_string, const string &attr_name)
{
    auto index = levels.find(level_string);
    if (index == levels.end()) {
        reportConfigurationError(
            "Unknown level: '" + level_string + "' in attribute " + attr_name
        );
    }
    return index->second;
}

void
IPSSignatureMetaData::setIndicators(const string &_source, const string &_version)
{
    source = _source;
    version = _version;
}

string
IPSSignatureMetaData::getSeverityString() const
{
    switch (severity) {
        case IPSLevel::VERY_LOW:
            return "Very Low";
        case IPSLevel::LOW:
            return "Low";
        case IPSLevel::MEDIUM_LOW:
            return "Medium Low";
        case IPSLevel::MEDIUM:
            return "Medium";
        case IPSLevel::MEDIUM_HIGH:
            return "Medium High";
        case IPSLevel::HIGH:
            return "High";
        case IPSLevel::CRITICAL:
            return "Critical";
    }

    dbgAssert(false) << AlertInfo(AlertTeam::CORE, "ips") << "Illegal severity value: " << static_cast<uint>(severity);
    return "Critical";
}

string
IPSSignatureMetaData::getConfidenceString() const
{
    if (confidence <= IPSLevel::LOW) return "Low";
    if (confidence >= IPSLevel::HIGH) return "High";
    return "Medium";
}

string
IPSSignatureMetaData::getPerformanceString() const
{
    switch (performance) {
        case IPSLevel::VERY_LOW:
            return "Very Low";
        case IPSLevel::LOW:
            return "Low";
        case IPSLevel::MEDIUM_LOW:
            return "Medium Low";
        case IPSLevel::MEDIUM:
            return "Medium";
        case IPSLevel::MEDIUM_HIGH:
            return "Medium High";
        case IPSLevel::HIGH:
            return "High";
        case IPSLevel::CRITICAL:
            return "Critical";
    }

    dbgAssert(false)
        << AlertInfo(AlertTeam::CORE, "ips")
        << "Illegal performance value: "
        << static_cast<uint>(performance);
    return "Critical";
}

void
IPSSignatureMetaData::load(cereal::JSONInputArchive &ar)
{
    string severity_string, confidence_string, performance_string;
    ar(
        cereal::make_nvp("maintrainId",       protection_id),
        cereal::make_nvp("protectionName",    sig_name),
        cereal::make_nvp("severity",          severity_string),
        cereal::make_nvp("lastUpdate",        update),
        cereal::make_nvp("confidenceLevel",   confidence_string),
        cereal::make_nvp("performanceImpact", performance_string),
        cereal::make_nvp("cveList",           cve_list),
        cereal::make_nvp("tags",              tag_list)
    );

    severity = getLevel(severity_string, "severity");
    confidence = getLevel(confidence_string, "confidence");
    performance = getLevel(performance_string, "performance");

    try {
        ar(cereal::make_nvp("logAttackName", event_log));
    } catch (cereal::Exception &) {
        event_log = "IPS Signature '" + sig_name + "' Found";
        ar.setNextName(nullptr);
    }

    try {
        ar(cereal::make_nvp("silent", is_silent));
    } catch (cereal::Exception &) {
        ar.setNextName(nullptr);
    }
}

static const size_t protection_type_pos = strlen("Protection_Type_");
static const size_t vul_type_pos = strlen("Vul_Type_");

string
IPSSignatureSubTypes::IPSSignatureMetaData::getIncidentType() const
{
    for (auto &tag : tag_list) {
        if (tag.compare(0, vul_type_pos, "Vul_Type_") == 0) {
            auto incident_type = tag.substr(vul_type_pos);
            replace(incident_type.begin(), incident_type.end(), '_', ' ');
            if (incident_type == "Vulnerability") return "Vulnerability exploit attempt";
            return incident_type;
        }
    }

    for (auto &tag : tag_list) {
        if (tag.compare(0, protection_type_pos, "Protection_Type_") == 0) {
            auto incident_type = tag.substr(protection_type_pos);
            replace(incident_type.begin(), incident_type.end(), '_', ' ');
            if (incident_type == "Vulnerability") return "Vulnerability exploit attempt";
            return incident_type;
        }
    }

    return "";
}

static const size_t year_start_pos = strlen("Threat_Year_");

bool
IPSSignatureMetaData::isYearAtLeast(const Maybe<int> &year) const
{
    if (!year.ok()) return true;
    auto protection_year = getYear();
    if (!protection_year.ok()) return true;

    return *protection_year >= *year;
}

Maybe<int>
IPSSignatureMetaData::getYear() const
{
    for (auto &tag : tag_list) {
        if (tag.compare(0, year_start_pos, "Threat_Year_") == 0) {
            if (tag.size() != year_start_pos + 4) {
                dbgWarning(D_IPS) << "Threat year tag (" << tag << ") doen't meet expected format";
                return false;
            }
            int protection_year =
                (tag[year_start_pos] - '0') * 1000 +
                (tag[year_start_pos + 1] - '0') * 100 +
                (tag[year_start_pos + 2] - '0') * 10 +
                (tag[year_start_pos + 3] - '0');
            return protection_year;
        }
    }

    return genError("Year not found");
}

void
CompleteSignature::load(cereal::JSONInputArchive &ar)
{
    static int sigs_load_counter = 0;
    static I_Environment *env = Singleton::Consume<I_Environment>::by<IPSComp>();
    static bool post_init = false;

    if (!post_init) {
        auto routine_id = Singleton::Consume<I_MainLoop>::by<IPSComp>()->getCurrentRoutineId();
        if (routine_id.ok()) {
            post_init = true;
            dbgInfo(D_IPS) << "Loading signatures post init, enabling yield with limit " << yield_on_load_cnt;
        }
    }

    try {
        ar(cereal::make_nvp("protectionMetadata", metadata));
        RuleDetection rule_detection(metadata.getName());
        ar(cereal::make_nvp("detectionRules", rule_detection));
        rule = rule_detection.getRule();
        is_loaded = true;
    } catch (cereal::Exception &e) {
        is_loaded = false;
        dbgWarning(D_IPS) << "Failed to load signature: " << e.what();
    }

    if (post_init && (yield_on_load_cnt > 0) && (++sigs_load_counter == yield_on_load_cnt)) {
        sigs_load_counter = 0;
        auto maybe_is_async = env->get<bool>("Is Async Config Load");
        if (maybe_is_async.ok() && *maybe_is_async == true) {
            dbgTrace(D_IPS) << "Yielding after " << yield_on_load_cnt << " signatures";
            Singleton::Consume<I_MainLoop>::by<IPSComp>()->yield(false);
        }
    }
}

MatchType
CompleteSignature::getMatch(const set<PMPattern> &matches) const
{
    return rule->getMatch(matches);
}

set<PMPattern>
CompleteSignature::patternsInSignature() const
{
    return rule->patternsInSignature();
}

void
CompleteSignature::setIndicators(const string &source, const string &version)
{
    metadata.setIndicators(source, version);
}

template <typename ErrorType>
static string
getSubString(const Maybe<Buffer, ErrorType> &buf, uint max_size = 0)
{
    if (max_size == 0) max_size = buf.unpack().size();
    const Buffer &real_buf = buf.unpack();
    auto res = real_buf.size() <= max_size ? real_buf : real_buf.getSubBuffer(0, max_size);
    return static_cast<string>(res);
}

ActionResults
SignatureAndAction::getAction(const IPSEntry &ips_state) const
{
    dbgDebug(D_IPS) << "matching exceptions";

    unordered_map<string, set<string>> exceptions_dict;
    exceptions_dict["protectionName"].insert(signature->getName());

    ScopedContext ctx;
    ctx.registerValue<string>("protectionName", signature->getName());

    auto env = Singleton::Consume<I_Environment>::by<IPSComp>();
    auto host = env->get<string>(HttpTransactionData::host_name_ctx);
    if (host.ok()) exceptions_dict["hostName"].insert(*host);

    auto client_ip = env->get<IPAddr>(HttpTransactionData::client_ip_ctx);
    if (client_ip.ok()) {
        stringstream client_ip_str;
        client_ip_str << client_ip.unpack();
        exceptions_dict["sourceIP"].insert(client_ip_str.str());
    }

    auto path = ips_state.getBuffer("HTTP_PATH_DECODED");
    if (path.size()) exceptions_dict["url"].insert(static_cast<string>(path));

    auto env_source_identifier = env->get<string>(HttpTransactionData::source_identifier);
    if (env_source_identifier.ok()) {
        exceptions_dict["sourceIdentifier"].insert(*env_source_identifier);
    }

    auto behaviors = getBehavior(exceptions_dict);

    set<BehaviorValue> override_actions;
    vector<string> override_ids;
    for (auto const &behavior : behaviors) {
        if (behavior.getKey() == BehaviorKey::ACTION) {
            override_actions.insert(behavior.getValue());
            const string &override_id = behavior.getId();
            if (!override_id.empty()) override_ids.push_back(override_id);
        } else if(behavior.getKey() == BehaviorKey::LOG && behavior.getValue() == BehaviorValue::IGNORE) {
            dbgTrace(D_IPS) << "setting bSupressLog due to override behavior: " << behavior.getId();
            bSupressLog = true;
        }
    }

    if (override_actions.find(BehaviorValue::IGNORE) != override_actions.end()) {
        dbgDebug(D_IPS) << "Exception matched - action=Detect";
        return make_tuple(IPSSignatureSubTypes::SignatureAction::DETECT, string("Skip"), override_ids);
    }
    if (override_actions.find(BehaviorValue::ACCEPT) != override_actions.end()) {
        dbgDebug(D_IPS) << "Exception matched - action=Detect";
        return make_tuple(IPSSignatureSubTypes::SignatureAction::DETECT, string("Accept"), override_ids);
    }
    if (override_actions.find(BehaviorValue::REJECT) != override_actions.end()) {
        dbgDebug(D_IPS) << "Exception matched - action=Prevent";
        return make_tuple(IPSSignatureSubTypes::SignatureAction::PREVENT, string("Drop"), override_ids);
    }
    return make_tuple(action, string("None"), override_ids);
}

static const auto req_body = LogTriggerConf::WebLogFields::webBody;
static const auto headers = LogTriggerConf::WebLogFields::webHeaders;
static const auto url_path = LogTriggerConf::WebLogFields::webUrlPath;
static const auto url_query = LogTriggerConf::WebLogFields::webUrlQuery;
static const auto res_body = LogTriggerConf::WebLogFields::responseBody;
static const auto res_code = LogTriggerConf::WebLogFields::responseCode;

LogTriggerConf
SignatureAndAction::getTrigger() const
{
    if (trigger_id.empty()) return getConfigurationWithDefault(LogTriggerConf(), "rulebase", "log");

    return Singleton::Consume<I_GenericRulebase>::by<IPSComp>()->getLogTriggerConf(trigger_id);
}

set<ParameterBehavior>
SignatureAndAction::getBehavior(const unordered_map<string, set<string>> &exceptions_dict) const
{
    I_GenericRulebase *i_rulebase = Singleton::Consume<I_GenericRulebase>::by<IPSComp>();
    if (exception_id.empty()) {
        dbgTrace(D_RULEBASE_CONFIG) << "No exception id provided, using default behavior";
        return i_rulebase->getBehavior(exceptions_dict);
    }
    return i_rulebase->getParameterException(exception_id).getBehavior(exceptions_dict);
}

bool
SignatureAndAction::matchSilent(const Buffer &sample) const
{
    dbgTrace(D_IPS) << "Matched silent signature";
    MatchEvent(signature, IPSSignatureSubTypes::SignatureAction::IGNORE).notify();

    ScopedContext ctx;
    ctx.registerValue("Audience Team", AudienceTeam::SIGNATURE_DEVELOPERS);

    LogGen log(
        "Silent Protection",
        Audience::INTERNAL,
        Severity::INFO,
        Priority::MEDIUM,
        LogField("practiceType", "Threat Prevention"),
        Tags::IPS,
        StreamType::JSON_FOG
    );
    log << LogField("signatureVersion", signature->getUpdateVersion())
        << LogField("protectionId", signature->getName())
        << LogField("indicatorsSource", signature->getSource())
        << LogField("indicatorsVersion", signature->getFeedVersion())
        << LogField("incidentType", signature->getIncidentType())
        << LogField("matchedSample", static_cast<string>(sample), LogFieldOption::XORANDB64);

    auto env = Singleton::Consume<I_Environment>::by<IPSComp>();
    auto table = Singleton::Consume<I_Table>::by<IPSComp>();
    auto &ips_state = table->getState<IPSEntry>();

    auto method = env->get<string>(HttpTransactionData::method_ctx);
    if (method.ok()) log << LogField("httpMethod", method.unpack());

    auto path  = env->get<Buffer>("HTTP_PATH_DECODED");
    if (path.ok()) {
        log << LogField("httpUriPath", getSubString(path, 1536), LogFieldOption::XORANDB64);
    } else {
        auto transaction_path = env->get<string>(HttpTransactionData::uri_path_decoded);
        if (transaction_path.ok()) {
            auto uri_path = transaction_path.unpack();
            auto question_mark = uri_path.find('?');
            log << LogField("httpUriPath", uri_path.substr(0, question_mark), LogFieldOption::XORANDB64);
        }
    }

    auto req_header = ips_state.getTransactionData(IPSCommonTypes::requests_header_for_log);
    if (req_header.ok()) log << LogField("httpRequestHeaders", getSubString(req_header), LogFieldOption::XORANDB64);

    auto res_code = env->get<Buffer>("HTTP_RESPONSE_CODE");
    if (res_code.ok()) log << LogField("httpResponseCode", static_cast<string>(res_code.unpack()));

    auto req_body = env->get<Buffer>("HTTP_REQUEST_BODY");
    auto res_body = env->get<Buffer>("HTTP_RESPONSE_BODY");
    uint req_size = req_body.ok() ? req_body.unpack().size() : 0;
    uint res_size = res_body.ok() ? res_body.unpack().size() : 0;
    if (req_size + res_size > 1536) {
        if (req_size + 500 > 1536) {
            res_size = std::min(500u, res_size);
            req_size = 1536 - res_size;
        } else {
            res_size = 1536 - req_size;
        }
    }
    if (req_size) log << LogField("httpRequestBody", getSubString(req_body, req_size), LogFieldOption::XORANDB64);
    if (res_size) log << LogField("httpResponseBody", getSubString(res_body, res_size), LogFieldOption::XORANDB64);

    return false;
}

bool
SignatureAndAction::isMatchedPrevent(const Buffer &context_buffer, const set<PMPattern> &pattern) const
{
    if (signature->getMatch(pattern) != MatchType::MATCH) {
        dbgTrace(D_IPS) << "Signature doesn't match";
        return false;
    }

    if (signature->isSilent()) return matchSilent(context_buffer);

    auto table = Singleton::Consume<I_Table>::by<IPSComp>();
    auto &ips_state = table->getState<IPSEntry>();

    auto override_action = getAction(ips_state);

    MatchEvent(signature, get<0>(override_action)).notify();

    if (get<0>(override_action) == IPSSignatureSubTypes::SignatureAction::IGNORE) {
        dbgDebug(D_IPS) << "Ignored signature";
        return false;
    }

    bool is_prevent = get<0>(override_action) == IPSSignatureSubTypes::SignatureAction::PREVENT;
    if(bSupressLog) {
        dbgTrace(D_IPS) << "Signature matched - not sending log due to exception behavior";
    } else {
        sendLog(context_buffer, ips_state, override_action, is_prevent);
    }
    return is_prevent;
}

void SignatureAndAction::sendLog(
    const Buffer &context_buffer,
    const IPSEntry &ips_state,
    const tuple<IPSSignatureSubTypes::SignatureAction, string, vector<string>> &override_action,
    bool is_prevent
) const
{
    dbgFlow(D_IPS) << "Signature matched - sending log";

    auto trigger = getTrigger();

    auto severity = signature->getSeverity() < IPSLevel::HIGH ? Severity::HIGH : Severity::CRITICAL;
    if (get<0>(override_action) == IPSSignatureSubTypes::SignatureAction::DETECT) severity = Severity::INFO;

    LogGen log = trigger(
        "Web Request",
        LogTriggerConf::SecurityType::ThreatPrevention,
        severity,
        Priority::HIGH,
        is_prevent,
        LogField("practiceType", "Threat Prevention"),
        Tags::IPS
    );
    log
        << LogField("matchedSignatureConfidence", signature->getConfidenceString())
        << LogField("matchedSignaturePerformance", signature->getPerformanceString())
        << LogField("matchedSignatureSeverity", signature->getSeverityString())
        << LogField("matchedSignatureCVE", makeSeparatedStr(signature->getCveList(), ", "))
        << LogField("signatureVersion", signature->getUpdateVersion())
        << LogField("protectionId", signature->getName())
        << LogField("indicatorsSource", signature->getSource())
        << LogField("indicatorsVersion", signature->getFeedVersion())
        << LogField("waapIncidentType", signature->getIncidentType());

    if (context_buffer.size() < 1024) {
        log << LogField("matchedSample", static_cast<string>(context_buffer), LogFieldOption::XORANDB64);
    } else {
        auto sample = context_buffer;
        sample.keepHead(1024);
        log << LogField("matchedSample", static_cast<string>(sample), LogFieldOption::XORANDB64);
    }

    auto year = signature->getYear();
    if (year.ok()) log << LogField("matchedSignatureYear", to_string(*year));

    auto env = Singleton::Consume<I_Environment>::by<IPSComp>();
    auto host = env->get<string>(HttpTransactionData::host_name_ctx);
    if (host.ok()) log << LogField("httpHostName", host.unpack());
    auto client_ip = env->get<IPAddr>(HttpTransactionData::client_ip_ctx);

    if (client_ip.ok()) {
        stringstream client_ip_str;
        client_ip_str << client_ip.unpack();
        log << LogField("sourceIP", client_ip_str.str());
    }

    auto proxy_ip = env->get<string>(HttpTransactionData::proxy_ip_ctx);
    if (proxy_ip.ok()) {
        log << LogField("proxyIP", static_cast<string>(proxy_ip.unpack()));
    }

    auto source_identifier = env->get<string>(HttpTransactionData::source_identifier);
    if (source_identifier.ok()) {
        log << LogField("httpSourceId", static_cast<string>(source_identifier.unpack()));
    }

    auto req_header = ips_state.getTransactionData(IPSCommonTypes::requests_header_for_log);
    if (req_header.ok() && trigger.isWebLogFieldActive(headers)) {
        log << LogField("httpRequestHeaders", static_cast<string>(req_header.unpack()), LogFieldOption::XORANDB64);
    }

    auto client_port = env->get<uint16_t>(HttpTransactionData::client_port_ctx);
    if (client_port.ok()) log << LogField("sourcePort", client_port.unpack());
    auto method = env->get<string>(HttpTransactionData::method_ctx);
    if (method.ok()) log << LogField("httpMethod", method.unpack());
    uint max_size = getConfigurationWithDefault<uint>(1536, "IPS", "Max Field Size");

    if (trigger.isWebLogFieldActive(url_path)) {
        auto path  = env->get<Buffer>("HTTP_PATH_DECODED");
        if (path.ok()) {
            log << LogField("httpUriPath", getSubString(path, max_size), LogFieldOption::XORANDB64);
        } else {
            auto transaction_path = env->get<string>(HttpTransactionData::uri_path_decoded);
            if (transaction_path.ok()) {
                auto uri_path = transaction_path.unpack();
                auto question_mark = uri_path.find('?');
                log << LogField("httpUriPath", uri_path.substr(0, question_mark), LogFieldOption::XORANDB64);
            }
        }
    }
    if (trigger.isWebLogFieldActive(url_query)) {
        auto query = env->get<Buffer>("HTTP_QUERY_DECODED");
        if (query.ok()) {
            log << LogField("httpUriQuery", getSubString(query, max_size), LogFieldOption::XORANDB64);
        } else {
            auto transaction_query = env->get<string>(HttpTransactionData::uri_query_decoded);
            if (transaction_query.ok()) {
                log << LogField("httpUriQuery", transaction_query.unpack());
            }
        }
    }

    auto res_code = env->get<Buffer>("HTTP_RESPONSE_CODE");
    if (res_code.ok() && trigger.isWebLogFieldActive(::res_code)) {
        log << LogField("httpResponseCode", static_cast<string>(res_code.unpack()));
    }

    auto req_body = env->get<Buffer>("HTTP_REQUEST_BODY");
    auto res_body = env->get<Buffer>("HTTP_RESPONSE_BODY");
    uint req_size = req_body.ok() && trigger.isWebLogFieldActive(::req_body) ? req_body.unpack().size() : 0;
    uint res_size = res_body.ok() && trigger.isWebLogFieldActive(::res_body) ? res_body.unpack().size() : 0;
    if (req_size + res_size > max_size) {
        if (req_size + 500 > max_size) {
            res_size = min(500u, res_size);
            req_size = max_size - res_size;
        } else {
            res_size = max_size - req_size;
        }
    }
    if (req_size) log << LogField("httpRequestBody", getSubString(req_body, req_size), LogFieldOption::XORANDB64);
    if (res_size) log << LogField("httpResponseBody", getSubString(res_body, res_size), LogFieldOption::XORANDB64);

    log << LogField("waapOverride", get<1>(override_action));

    if (!get<2>(override_action).empty()) log.addToOrigin(LogField("exceptionIdList", get<2>(override_action)));

    log << LogField("securityAction", is_prevent ? "Prevent" : "Detect");
}

void
IPSSignaturesResource::load(cereal::JSONInputArchive &ar)
{
    if (!IPSHelper::hasDeobfuscation()) return;

    vector<CompleteSignature> sigs;
    cereal::load(ar, sigs);

    all_signatures.reserve(sigs.size());
    for (auto &sig : sigs) {
        if (sig.isOk()) {
            all_signatures.emplace_back(make_shared<CompleteSignature>(move(sig)));
        }
    }
}

class CompleteSignatureWrapper
{
public:
    void
    load(cereal::JSONInputArchive &ar)
    {
        try {
            sig.load(ar);
            is_loaded = true;
        } catch (const cereal::Exception &e) {
            ar.finishNode();
            reportError(e.what());
        } catch (const Config::ConfigException &e) {
            ar.finishNode();
            reportError(e.getError());
        }
    }

    bool isOk() const { return is_loaded; }
    void setIndicators(const string &source, const string &version) { sig.setIndicators(source, version); }
    shared_ptr<CompleteSignature> getPtr() { return make_shared<CompleteSignature>(move(sig)); }

private:
    void
    reportError(const string &err)
    {
        dbgError(D_IPS) << "Failed to load signature due to: " << err;

        if (sig.getName() != "") {
            string remediation =
                "Verify the validity of the '" +
                sig.getName() +
                "' signature.";

            LogGen(
                "Could not load a Snort signature from configured file",
                ReportIS::Level::ACTION,
                ReportIS::Audience::SECURITY,
                ReportIS::Severity::CRITICAL,
                ReportIS::Priority::URGENT,
                LogField("EventTopic", "Snort Signatures"),
                ReportIS::Tags::POLICY_INSTALLATION
            ) << LogField("EventRemediation", remediation);
        }
    }

    CompleteSignature sig;
    bool is_loaded = false;
};

void
SnortSignaturesResourceFile::load(cereal::JSONInputArchive &ar)
{
    string time;
    vector<CompleteSignatureWrapper> sigs;
    ar(
        cereal::make_nvp("modificationTime", time),
        cereal::make_nvp("name", name),
        cereal::make_nvp("protections", sigs)
    );

    all_signatures.reserve(sigs.size());
    for (auto &sig : sigs) {
        if (sig.isOk()) {
            sig.setIndicators(name, time);
            all_signatures.emplace_back(sig.getPtr());
        }
    }
}

void
SnortSignaturesResource::load(cereal::JSONInputArchive &ar)
{
    cereal::load(ar, files);
}

void
IPSSignaturesPerContext::addSignature(const IPSSignatureSubTypes::SignatureAndAction &sig)
{
    auto patterns = sig.patternsInSignature();

    if (patterns.empty()) {
        signatures_without_lss.push_back(sig);
        return;
    }

    for (auto &pat : patterns) {
        signatures_per_lss[pat].push_back(sig);
    }
}

void
IPSSignaturesPerContext::calcFirstTier(const string &ctx_name)
{
    std::set<PMPattern> patterns;
    for (const auto &lss_to_sig : signatures_per_lss) {
        patterns.emplace(lss_to_sig.first);
    }

    first_tier = Singleton::Consume<I_FirstTierAgg>::by<IPSSignaturesPerContext>()->getHook(ctx_name, patterns);
}

set<PMPattern>
IPSSignaturesPerContext::getFirstTierMatches(const Buffer &buffer) const
{
    return first_tier->ok() ? first_tier->scanBuf(buffer) : set<PMPattern>();
}

bool
IPSSignaturesPerContext::isMatchedPrevent(const Buffer &context_buffer) const
{
    auto first_tier_res = getFirstTierMatches(context_buffer);

    for (auto &pat : first_tier_res) {
        auto find = signatures_per_lss.find(pat);
        if (find == signatures_per_lss.end()) continue;
        for (auto &sig : find->second) {
            if (sig.isMatchedPrevent(context_buffer, first_tier_res)) return true;
        }
    }

    for (auto &sig : signatures_without_lss) {
        if (sig.isMatchedPrevent(context_buffer, first_tier_res)) return true;
    }

    return false;
}

void
IPSSignatures::load(cereal::JSONInputArchive &ar)
{
    ar(
        cereal::make_nvp("assetName", asset_name),
        cereal::make_nvp("practiceName", practice_name)
    );

    try {
        ar(cereal::make_nvp("assetId", asset_id));
    } catch (const cereal::Exception &e) {
        ar.setNextName(nullptr);
        asset_id = "";
    }

    try {
        ar(cereal::make_nvp("practiceId", practice_id));
    } catch (const cereal::Exception &e) {
        ar.setNextName(nullptr);
        practice_id = "";
    }

    try {
        ar(cereal::make_nvp("sourceIdentifier", source_id));
        for (auto &ch : source_id) {
            ch = tolower(ch);
        }
    } catch (const cereal::Exception &e) {
        ar.setNextName(nullptr);
        source_id = "";
    }

    RuleSelector ruleSelector;
    ruleSelector.load(ar);
    std::vector<IPSSignatureSubTypes::SignatureAndAction> signatures = ruleSelector.selectSignatures();

    if (signatures.empty()) {
        dbgDebug(D_IPS) << "[IPS] Could not find any match between rules and signatures.";
        return;
    }

    for (const auto &sig : signatures) {
        auto &sig_contexts = sig.getContext();
        for (auto &sig_context : sig_contexts) {
            signatures_per_context[sig_context].addSignature(sig);
        }
    }

    for (auto &sig_per_ctx : signatures_per_context) {
        sig_per_ctx.second.calcFirstTier(sig_per_ctx.first);
    }
}

bool
IPSSignatures::isMatchedPrevent(const string &context_name, const Buffer &context_buffer) const
{
    auto curr_sig = signatures_per_context.find(context_name);

    if (curr_sig == signatures_per_context.end()) {
        dbgDebug(D_IPS) << "[IPS] No signatures for " << context_name;
        return false;
    }

    auto &config = getConfiguration<IPSSignatures>("IPS", "IpsProtections");
    ScopedContext ctx;
    auto SOURCE = EnvKeyAttr::LogSection::SOURCE;
    if (config.ok()) {
        ctx.registerValue<string>("practiceName", (*config).getPractice(), SOURCE);
        ctx.registerValue<string>("practiceId", (*config).getPracticeId(), SOURCE);
    }
    ctx.registerValue<string>("practiceSubType", "Web IPS", SOURCE);
    auto is_matched = curr_sig->second.isMatchedPrevent(context_buffer);

    return is_matched;
}

bool
IPSSignatures::isEmpty(const std::string &context) const
{
    return signatures_per_context.find(context) == signatures_per_context.end();
}

void
SnortSignatures::load(cereal::JSONInputArchive &ar)
{
    ar(
        cereal::make_nvp("assetName", asset_name),
        cereal::make_nvp("practiceName", practice_name)
    );

    try {
        ar(cereal::make_nvp("assetId", asset_id));
    } catch (const cereal::Exception &e) {
        ar.setNextName(nullptr);
        asset_id = "";
    }

    try {
        ar(cereal::make_nvp("practiceId", practice_id));
    } catch (const cereal::Exception &e) {
        ar.setNextName(nullptr);
        practice_id = "";
    }

    try {
        ar(cereal::make_nvp("sourceIdentifier", source_id));
        for (auto &ch : source_id) {
            ch = tolower(ch);
        }
    } catch (const cereal::Exception &e) {
        ar.setNextName(nullptr);
        source_id = "";
    }

    SnortRuleSelector ruleSelector;
    ruleSelector.load(ar);
    std::vector<IPSSignatureSubTypes::SignatureAndAction> signatures = ruleSelector.selectSignatures();

    if (signatures.empty()) {
        dbgDebug(D_IPS) << "[Snort] Could not find any match between rules and signatures.";
        return;
    }

    for (const auto &sig : signatures) {
        auto &sig_contexts = sig.getContext();
        for (auto &sig_context : sig_contexts) {
            signatures_per_context[sig_context].addSignature(sig);
        }
    }

    for (auto &sig_per_ctx: signatures_per_context) {
        sig_per_ctx.second.calcFirstTier(sig_per_ctx.first);
    }
}

bool
SnortSignatures::isMatchedPrevent(const string &context_name, const Buffer &context_buffer) const
{
    auto curr_sig = signatures_per_context.find(context_name);

    if (curr_sig == signatures_per_context.end()) {
        dbgDebug(D_IPS) << "[Snort] No signatures for " << context_name;
        return false;
    }

    auto &config = getConfiguration<SnortSignatures>("IPSSnortSigs", "SnortProtections");
    ScopedContext ctx;
    auto SOURCE = EnvKeyAttr::LogSection::SOURCE;
    if (config.ok()) {
        ctx.registerValue<string>("assetName", (*config).getAsset(), SOURCE);
        ctx.registerValue<string>("assetId", (*config).getAssetId(), SOURCE);
        ctx.registerValue<string>("practiceName", (*config).getPractice(), SOURCE);
        ctx.registerValue<string>("practiceId", (*config).getPracticeId(), SOURCE);
    }
    ctx.registerValue<string>("practiceSubType", "Web Snort", SOURCE);
    auto is_matched = curr_sig->second.isMatchedPrevent(context_buffer);

    return is_matched;
}

bool
SnortSignatures::isEmpty(const std::string &context) const
{
    return signatures_per_context.find(context) == signatures_per_context.end();
}
