/*
 * Planet Security Defense — embedded reference (C99, Cortex-M class)
 *
 * Patent : PCT WO 2025/127469 A1 (inventor/applicant: 이정훈, 2025-06-19)
 * Site   : https://planet.winnerbrothers.org
 *
 * This is a minimal embedded receiver that:
 *   - Holds the device's K = (axis, omega, p0) in HSM/secure flash
 *   - Receives signed commands (commandId, senderStateHash, nonce, timestamp, signature)
 *   - Verifies HMAC-SHA-256 against the session key (provisioned at handshake)
 *   - Verifies time-window (Δ) and replay
 *
 * Dependencies:
 *   - mbedTLS (sha256 + hmac) — typical RTOS choice
 *   - or wolfSSL, or hardware crypto accelerator
 *
 * Memory footprint (typical Cortex-M4):
 *   - .text  : ~3.2 KB
 *   - .data  : ~256 B per active session
 *   - .bss   : ~128 B
 *
 * Replay window: keeps last 64 commandIds (ring buffer). Adjust per use case.
 */

#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <stdbool.h>

#include "mbedtls/sha256.h"
#include "mbedtls/md.h"

/* ────────────────────────────────────────────────────────────────────────── */
/*  Configuration — adjust per environment                                    */
/* ────────────────────────────────────────────────────────────────────────── */

/* Δ in ms (Theorem 6) — match server-side device.environment:
 *   gps_disciplined : 0.1
 *   datacenter      : 1
 *   lan             : 10
 *   field           : 50
 *   satellite       : 100
 *   space           : 500
 */
#define PLANET_DELTA_MS  50      /* tactical field default */

/* Replay window — last N commandIds remembered */
#define PLANET_REPLAY_WINDOW  64

/* Maximum command payload length */
#define PLANET_MAX_CMD       4096

/* ────────────────────────────────────────────────────────────────────────── */
/*  Types                                                                     */
/* ────────────────────────────────────────────────────────────────────────── */

typedef struct {
    uint8_t  session_key[32];       /* HMAC-SHA-256 key from handshake */
    uint64_t session_expires_at_ms;
    bool     active;
} planet_session_t;

typedef struct {
    char     command_id[40];        /* "cmd_..." string id */
    uint64_t timestamp_ms;
} planet_replay_entry_t;

typedef struct {
    planet_replay_entry_t entries[PLANET_REPLAY_WINDOW];
    size_t                head;
} planet_replay_log_t;

typedef struct {
    const char  *command_id;
    const char  *command;
    size_t       command_len;
    const char  *sender_state_hash;     /* hex */
    const char  *nonce;                 /* hex */
    uint64_t     timestamp_ms;
    const char  *signature;             /* hex */
} planet_signed_command_t;

typedef enum {
    PLANET_OK = 0,
    PLANET_ERR_SESSION_INACTIVE,
    PLANET_ERR_SESSION_EXPIRED,
    PLANET_ERR_REPLAY_DETECTED,
    PLANET_ERR_CLOCK_SKEW,
    PLANET_ERR_SIGNATURE_INVALID,
    PLANET_ERR_BAD_PARAM,
} planet_status_t;

/* ────────────────────────────────────────────────────────────────────────── */
/*  Helpers                                                                   */
/* ────────────────────────────────────────────────────────────────────────── */

/* Constant-time hex-string compare */
static int ct_streq(const char *a, const char *b)
{
    size_t la = strlen(a), lb = strlen(b);
    if (la != lb) return 0;
    int diff = 0;
    for (size_t i = 0; i < la; i++) diff |= (unsigned char)a[i] ^ (unsigned char)b[i];
    return diff == 0;
}

/* Convert 32-byte SHA-256/HMAC output to lowercase hex (64 chars + NUL) */
static void to_hex(const uint8_t *bin, size_t n, char *out)
{
    static const char *hex = "0123456789abcdef";
    for (size_t i = 0; i < n; i++) {
        out[2*i]   = hex[bin[i] >> 4];
        out[2*i+1] = hex[bin[i] & 0xF];
    }
    out[2*n] = '\0';
}

/* ────────────────────────────────────────────────────────────────────────── */
/*  Replay log                                                                */
/* ────────────────────────────────────────────────────────────────────────── */

static int replay_seen(planet_replay_log_t *log, const char *cmd_id, uint64_t now_ms)
{
    for (size_t i = 0; i < PLANET_REPLAY_WINDOW; i++) {
        const planet_replay_entry_t *e = &log->entries[i];
        if (e->command_id[0] == '\0') continue;
        /* Expire entries older than 60s (well beyond max Δ) */
        if (now_ms - e->timestamp_ms > 60000) continue;
        if (strcmp(e->command_id, cmd_id) == 0) return 1;
    }
    return 0;
}

static void replay_record(planet_replay_log_t *log, const char *cmd_id, uint64_t ts_ms)
{
    planet_replay_entry_t *slot = &log->entries[log->head];
    strncpy(slot->command_id, cmd_id, sizeof(slot->command_id) - 1);
    slot->command_id[sizeof(slot->command_id) - 1] = '\0';
    slot->timestamp_ms = ts_ms;
    log->head = (log->head + 1) % PLANET_REPLAY_WINDOW;
}

/* ────────────────────────────────────────────────────────────────────────── */
/*  HMAC verification                                                         */
/* ────────────────────────────────────────────────────────────────────────── */

/*
 * The signed message (must match server-side construction):
 *   command || "|" || senderStateHash || "|" || nonce || "|" || timestamp || "|" || commandId
 */
static int build_message(
    const planet_signed_command_t *sc,
    char *buf, size_t bufsz, size_t *out_len)
{
    int n = snprintf(
        buf, bufsz, "%.*s|%s|%s|%llu|%s",
        (int)sc->command_len, sc->command,
        sc->sender_state_hash, sc->nonce,
        (unsigned long long)sc->timestamp_ms,
        sc->command_id
    );
    if (n < 0 || (size_t)n >= bufsz) return -1;
    *out_len = (size_t)n;
    return 0;
}

static int hmac_verify(
    const uint8_t *key, size_t keylen,
    const char *msg, size_t msglen,
    const char *signature_hex)
{
    uint8_t mac[32];
    char    mac_hex[65];
    const mbedtls_md_info_t *info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
    if (!info) return 0;
    if (mbedtls_md_hmac(info, key, keylen, (const uint8_t *)msg, msglen, mac) != 0)
        return 0;
    to_hex(mac, sizeof(mac), mac_hex);
    return ct_streq(mac_hex, signature_hex);
}

/* ────────────────────────────────────────────────────────────────────────── */
/*  Public API                                                                */
/* ────────────────────────────────────────────────────────────────────────── */

void planet_session_install(
    planet_session_t *sess,
    const uint8_t *session_key_32,
    uint64_t expires_at_ms)
{
    memcpy(sess->session_key, session_key_32, 32);
    sess->session_expires_at_ms = expires_at_ms;
    sess->active = true;
}

void planet_session_clear(planet_session_t *sess)
{
    /* Zeroize key in HSM-backed scratch */
    volatile uint8_t *p = sess->session_key;
    for (size_t i = 0; i < sizeof(sess->session_key); i++) p[i] = 0;
    sess->session_expires_at_ms = 0;
    sess->active = false;
}

planet_status_t planet_verify_command(
    planet_session_t *sess,
    planet_replay_log_t *log,
    const planet_signed_command_t *sc,
    uint64_t now_ms)
{
    if (!sess || !log || !sc) return PLANET_ERR_BAD_PARAM;
    if (!sess->active) return PLANET_ERR_SESSION_INACTIVE;
    if (sess->session_expires_at_ms < now_ms) return PLANET_ERR_SESSION_EXPIRED;

    /* (a) Replay */
    if (replay_seen(log, sc->command_id, now_ms))
        return PLANET_ERR_REPLAY_DETECTED;

    /* (b) Time window — Theorem 6 */
    int64_t skew = (int64_t)now_ms - (int64_t)sc->timestamp_ms;
    if (skew < 0) skew = -skew;
    if (skew > PLANET_DELTA_MS)
        return PLANET_ERR_CLOCK_SKEW;

    /* (c) HMAC signature — Theorem 1, 2 */
    char msg[PLANET_MAX_CMD + 256];
    size_t msg_len = 0;
    if (build_message(sc, msg, sizeof(msg), &msg_len) != 0)
        return PLANET_ERR_BAD_PARAM;

    if (!hmac_verify(sess->session_key, 32, msg, msg_len, sc->signature))
        return PLANET_ERR_SIGNATURE_INVALID;

    /* All good — record commandId */
    replay_record(log, sc->command_id, sc->timestamp_ms);
    return PLANET_OK;
}

const char *planet_status_str(planet_status_t s)
{
    switch (s) {
        case PLANET_OK:                       return "ok";
        case PLANET_ERR_SESSION_INACTIVE:     return "session_inactive";
        case PLANET_ERR_SESSION_EXPIRED:      return "session_expired";
        case PLANET_ERR_REPLAY_DETECTED:      return "replay_detected";
        case PLANET_ERR_CLOCK_SKEW:           return "clock_skew";
        case PLANET_ERR_SIGNATURE_INVALID:    return "signature_invalid";
        default:                              return "bad_param";
    }
}

/* ────────────────────────────────────────────────────────────────────────── */
/*  Self-test (host-side, build with -DPLANET_TEST)                          */
/* ────────────────────────────────────────────────────────────────────────── */

#ifdef PLANET_TEST
#include <stdio.h>
#include <time.h>

static uint64_t now_ms_host(void) {
    struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts);
    return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000ULL;
}

int main(void)
{
    /* In production: session_key is provisioned via the 3-Round handshake.
     * In this self-test we just install a known key and verify HMAC math.   */
    uint8_t key[32]; memset(key, 0xAB, sizeof(key));

    planet_session_t sess; memset(&sess, 0, sizeof(sess));
    planet_replay_log_t log; memset(&log, 0, sizeof(log));

    planet_session_install(&sess, key, now_ms_host() + 5*60*1000);

    /* Compute a valid signature for a fake command */
    const char *cmd = "TAKEOFF altitude=50m";
    const char *sh  = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff";
    const char *nc  = "deadbeefdeadbeefdeadbeefdeadbeef";
    uint64_t    ts  = now_ms_host();
    const char *id  = "cmd_test123";

    char msg[1024]; int n = snprintf(msg, sizeof(msg),
        "%s|%s|%s|%llu|%s", cmd, sh, nc, (unsigned long long)ts, id);
    uint8_t mac[32]; char mac_hex[65];
    mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
        key, 32, (uint8_t *)msg, n, mac);
    to_hex(mac, 32, mac_hex);

    planet_signed_command_t sc = {
        .command_id = id, .command = cmd, .command_len = strlen(cmd),
        .sender_state_hash = sh, .nonce = nc, .timestamp_ms = ts,
        .signature = mac_hex,
    };

    planet_status_t s1 = planet_verify_command(&sess, &log, &sc, now_ms_host());
    printf("[%s] verify-1: %s\n", s1 == PLANET_OK ? "OK" : "NO", planet_status_str(s1));

    planet_status_t s2 = planet_verify_command(&sess, &log, &sc, now_ms_host());
    printf("[%s] verify-2 (replay): %s\n", s2 == PLANET_ERR_REPLAY_DETECTED ? "OK" : "NO",
           planet_status_str(s2));

    return 0;
}
#endif
