// =============================================================================
// PDL CONSULTING LLC — TRADE SECRET
// Copyright (c) 2026 PDL Consulting LLC. All rights reserved.
//
// This file contains trade-secret information of PDL Consulting LLC,
// protected under the Defend Trade Secrets Act, 18 U.S.C. § 1836 et seq.,
// and the Virginia Uniform Trade Secrets Act, Va. Code § 59.1-336 et seq.
//
// UNAUTHORIZED DISCLOSURE, COPYING, REPRODUCTION, OR USE IS STRICTLY
// PROHIBITED AND MAY RESULT IN CIVIL AND CRIMINAL LIABILITY.
// =============================================================================
//
// attack_graph_local.cpp — Local self-diagnosis attack graph
//
// C++ port of the LOCAL subset of:
//   pdl-engine/pdl_platform/redcell/attack_graph.py
//
// 6 assets, 8 event types, per-(event,node) weight matrix, 1%/min decay.
// Pure rule-based: no networkx, no heap, no STL.
//
// Static memory only.  <cstdint> <cstring> <cmath> only.
// =============================================================================

#include "attack_graph_local.h"
#include <cstdint>
#include <cstring>
#include <cmath>

// ---------------------------------------------------------------------------
// Per-(event, node) weight matrix
//
// s_event_weight[event_type][node_id]
//
// Rationale per row:
//   SIG_FAIL          — strong signal for FIRMWARE, BOOTLOADER, AUTH_PUBKEY
//   OOB_TIME          — affects FIRMWARE execution integrity + MODELS timing
//   MISSING_HEARTBEAT — MESH_KEY may be offline/compromised; FIRMWARE suspect
//   OTA_REJECT        — direct evidence against FIRMWARE, BOOTLOADER integrity
//   PEC_DEVIATION     — primary signal for MODELS drift
//   AUTH_FAIL         — AUTH_PUBKEY and MESH_KEY most affected
//   REPLAY_DETECT     — MESH_KEY, AUTH_PUBKEY (replay attacks target keying material)
//   MESH_INJECT       — MESH_KEY (injected frames bypass key checks), BLUE_FORCE_LIST
// ---------------------------------------------------------------------------
static const float s_event_weight[AG_EVENT_COUNT][AG_NODE_COUNT] = {
    // BL     FW     MDL    KEY    BFL    APK
    { 0.20f, 0.35f, 0.05f, 0.10f, 0.05f, 0.30f },  // EVT_SIG_FAIL
    { 0.10f, 0.25f, 0.20f, 0.05f, 0.05f, 0.10f },  // EVT_OOB_TIME
    { 0.05f, 0.20f, 0.05f, 0.25f, 0.10f, 0.10f },  // EVT_MISSING_HEARTBEAT
    { 0.30f, 0.40f, 0.10f, 0.05f, 0.05f, 0.15f },  // EVT_OTA_REJECT
    { 0.05f, 0.10f, 0.45f, 0.05f, 0.10f, 0.05f },  // EVT_PEC_DEVIATION
    { 0.10f, 0.10f, 0.05f, 0.25f, 0.05f, 0.40f },  // EVT_AUTH_FAIL
    { 0.05f, 0.05f, 0.05f, 0.35f, 0.10f, 0.35f },  // EVT_REPLAY_DETECT
    { 0.05f, 0.05f, 0.05f, 0.40f, 0.35f, 0.10f },  // EVT_MESH_INJECT
};

// ---------------------------------------------------------------------------
// Per-node compromise probability (mutable state)
// ---------------------------------------------------------------------------
static float s_risk[AG_NODE_COUNT];

// Timestamp of last decay call (ms) — 0 on init
static uint32_t s_last_decay_ms;

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
static inline float clampf(float v, float lo, float hi) {
    if (v < lo) return lo;
    if (v > hi) return hi;
    return v;
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

void attackgraph_init(void) {
    std::memset(s_risk, 0, sizeof(s_risk));
    s_last_decay_ms = 0;
}

void attackgraph_decay(uint32_t elapsed_ms) {
    // 1% per 60 000 ms = 0.01 / 60000 per ms
    // decay_factor = (1 - 0.01)^(elapsed_ms / 60000)
    //             = exp(elapsed_ms * ln(0.99) / 60000)
    // For small elapsed_ms this approximates to (1 - elapsed_ms * 0.000000166...)
    if (elapsed_ms == 0) return;
    float decay = std::exp(static_cast<float>(elapsed_ms) * (-0.0001667f));
    for (int n = 0; n < AG_NODE_COUNT; ++n) {
        s_risk[n] = clampf(s_risk[n] * decay, 0.0f, 1.0f);
    }
}

void attackgraph_observe(uint8_t node_id, uint8_t event_type, float weight) {
    if (node_id >= AG_NODE_COUNT)   return;
    if (event_type >= AG_EVENT_COUNT) return;

    // Clamp weight to [0, 1]
    float w = clampf(weight, 0.0f, 1.0f);

    // Update target node
    float delta = s_event_weight[event_type][node_id] * w;
    s_risk[node_id] = clampf(s_risk[node_id] + delta, 0.0f, 1.0f);

    // Propagation: adjacent nodes absorb a fraction (10%) of the delta
    // to model the chain-exploit structure in the Python reference graph.
    // Propagation order mirrors the reference topology's highest-prob paths:
    //   BOOTLOADER → FIRMWARE → MESH_KEY → AUTH_PUBKEY
    //   FIRMWARE   → MODELS
    static const int8_t propagation[AG_NODE_COUNT][2] = {
        { NODE_FIRMWARE,       -1 },                   // BL propagates to FW
        { NODE_MODELS,         NODE_MESH_KEY },        // FW propagates to MDL, KEY
        { -1,                  -1 },                   // MDL: terminal
        { NODE_AUTH_PUBKEY,    NODE_BLUE_FORCE_LIST }, // KEY propagates to APK, BFL
        { -1,                  -1 },                   // BFL: terminal
        { -1,                  -1 },                   // APK: terminal
    };
    float prop_delta = delta * 0.10f;
    for (int k = 0; k < 2; ++k) {
        int8_t pn = propagation[node_id][k];
        if (pn < 0) break;
        s_risk[pn] = clampf(s_risk[pn] + prop_delta, 0.0f, 1.0f);
    }
}

float attackgraph_node_risk(uint8_t node_id) {
    if (node_id >= AG_NODE_COUNT) return 0.0f;
    return s_risk[node_id];
}

bool attackgraph_should_failsafe(void) {
    for (int n = 0; n < AG_NODE_COUNT; ++n) {
        if (s_risk[n] > AG_FAILSAFE_THRESHOLD) return true;
    }
    return false;
}

uint8_t attackgraph_highest_risk_node(void) {
    uint8_t best = 0xFF;
    float   best_v = 0.0f;
    for (int n = 0; n < AG_NODE_COUNT; ++n) {
        if (s_risk[n] > best_v) { best_v = s_risk[n]; best = static_cast<uint8_t>(n); }
    }
    return best;
}
