#include "stdafx.h"
#include "SecureCipher.h"

// Static initialization flag for libsodium
static bool s_sodiumInitialized = false;

static bool EnsureSodiumInit()
{
    if (!s_sodiumInitialized)
    {
        if (sodium_init() < 0)
        {
            return false;
        }
        s_sodiumInitialized = true;
    }
    return true;
}

SecureCipher::SecureCipher()
{
    sodium_memzero(m_pk, sizeof(m_pk));
    sodium_memzero(m_sk, sizeof(m_sk));
    sodium_memzero(m_tx_key, sizeof(m_tx_key));
    sodium_memzero(m_rx_key, sizeof(m_rx_key));
    sodium_memzero(m_tx_stream_nonce, sizeof(m_tx_stream_nonce));
    sodium_memzero(m_rx_stream_nonce, sizeof(m_rx_stream_nonce));
    sodium_memzero(m_session_token, sizeof(m_session_token));
}

SecureCipher::~SecureCipher()
{
    CleanUp();
}

bool SecureCipher::Initialize()
{
    if (!EnsureSodiumInit())
    {
        return false;
    }

    // Generate X25519 keypair
    if (crypto_kx_keypair(m_pk, m_sk) != 0)
    {
        return false;
    }

    m_tx_nonce = 0;
    m_rx_nonce = 0;
    m_initialized = true;
    m_activated = false;

    return true;
}

void SecureCipher::CleanUp()
{
    // Securely erase all sensitive key material
    sodium_memzero(m_pk, sizeof(m_pk));
    sodium_memzero(m_sk, sizeof(m_sk));
    sodium_memzero(m_tx_key, sizeof(m_tx_key));
    sodium_memzero(m_rx_key, sizeof(m_rx_key));
    sodium_memzero(m_tx_stream_nonce, sizeof(m_tx_stream_nonce));
    sodium_memzero(m_rx_stream_nonce, sizeof(m_rx_stream_nonce));
    sodium_memzero(m_session_token, sizeof(m_session_token));

    m_initialized = false;
    m_activated = false;
    m_tx_nonce = 0;
    m_rx_nonce = 0;
}

void SecureCipher::GetPublicKey(uint8_t* out_pk) const
{
    memcpy(out_pk, m_pk, PK_SIZE);
}

bool SecureCipher::ComputeClientKeys(const uint8_t* server_pk)
{
    if (!m_initialized)
    {
        return false;
    }

    // Client: tx_key is for sending TO server, rx_key is for receiving FROM server
    if (crypto_kx_client_session_keys(m_rx_key, m_tx_key, m_pk, m_sk, server_pk) != 0)
    {
        return false;
    }

    // Set up fixed stream nonces per direction
    // client->server = 0x02, server->client = 0x01
    sodium_memzero(m_tx_stream_nonce, NONCE_SIZE);
    m_tx_stream_nonce[0] = 0x02;
    sodium_memzero(m_rx_stream_nonce, NONCE_SIZE);
    m_rx_stream_nonce[0] = 0x01;

    return true;
}

bool SecureCipher::ComputeServerKeys(const uint8_t* client_pk)
{
    if (!m_initialized)
    {
        return false;
    }

    // Server: tx_key is for sending TO client, rx_key is for receiving FROM client
    if (crypto_kx_server_session_keys(m_rx_key, m_tx_key, m_pk, m_sk, client_pk) != 0)
    {
        return false;
    }

    // Set up fixed stream nonces per direction
    // server->client = 0x01, client->server = 0x02
    sodium_memzero(m_tx_stream_nonce, NONCE_SIZE);
    m_tx_stream_nonce[0] = 0x01;
    sodium_memzero(m_rx_stream_nonce, NONCE_SIZE);
    m_rx_stream_nonce[0] = 0x02;

    sys_log(0, "[CIPHER] Server keys computed (tx_key: %02x%02x%02x%02x, rx_key: %02x%02x%02x%02x)",
        m_tx_key[0], m_tx_key[1], m_tx_key[2], m_tx_key[3],
        m_rx_key[0], m_rx_key[1], m_rx_key[2], m_rx_key[3]);

    return true;
}

void SecureCipher::GenerateChallenge(uint8_t* out_challenge)
{
    randombytes_buf(out_challenge, CHALLENGE_SIZE);
}

void SecureCipher::ComputeChallengeResponse(const uint8_t* challenge, uint8_t* out_response)
{
    // HMAC the challenge using our tx_key as the authentication key
    // Client tx_key == Server rx_key, so the server can verify with its rx_key
    crypto_auth(out_response, challenge, CHALLENGE_SIZE, m_tx_key);
}

bool SecureCipher::VerifyChallengeResponse(const uint8_t* challenge, const uint8_t* response)
{
    // Verify the HMAC - peer should have used their tx_key (our rx_key) to compute it
    return crypto_auth_verify(response, challenge, CHALLENGE_SIZE, m_rx_key) == 0;
}

void SecureCipher::ApplyStreamCipher(void* buffer, size_t len,
                                     const uint8_t* key, uint64_t& byte_counter,
                                     const uint8_t* stream_nonce)
{
    uint8_t* p = (uint8_t*)buffer;

    // Handle partial leading block (if byte_counter isn't block-aligned)
    uint32_t offset = (uint32_t)(byte_counter % 64);
    if (offset != 0 && len > 0)
    {
        // Generate full keystream block, use only the portion we need
        uint8_t ks[64];
        sodium_memzero(ks, 64);
        crypto_stream_xchacha20_xor_ic(ks, ks, 64, stream_nonce, byte_counter / 64, key);

        size_t use = len < (64 - offset) ? len : (64 - offset);
        for (size_t i = 0; i < use; ++i)
            p[i] ^= ks[offset + i];

        p += use;
        len -= use;
        byte_counter += use;
    }

    // Handle remaining data (starts at a block boundary)
    if (len > 0)
    {
        crypto_stream_xchacha20_xor_ic(p, p, (unsigned long long)len,
                                       stream_nonce, byte_counter / 64, key);
        byte_counter += len;
    }
}

void SecureCipher::EncryptInPlace(void* buffer, size_t len)
{
    if (!m_activated || len == 0)
        return;

    ApplyStreamCipher(buffer, len, m_tx_key, m_tx_nonce, m_tx_stream_nonce);
}

void SecureCipher::DecryptInPlace(void* buffer, size_t len)
{
    if (!m_activated || len == 0)
        return;

    ApplyStreamCipher(buffer, len, m_rx_key, m_rx_nonce, m_rx_stream_nonce);
}

bool SecureCipher::EncryptToken(const uint8_t* plaintext, size_t len,
                                 uint8_t* ciphertext, uint8_t* nonce_out)
{
    if (!m_initialized)
    {
        return false;
    }

    // Generate random nonce for this one-time encryption
    randombytes_buf(nonce_out, NONCE_SIZE);

    unsigned long long ciphertext_len = 0;

    if (crypto_aead_xchacha20poly1305_ietf_encrypt(
            ciphertext, &ciphertext_len,
            plaintext, len,
            nullptr, 0,
            nullptr,
            nonce_out,
            m_tx_key) != 0)
    {
        return false;
    }

    return true;
}

bool SecureCipher::DecryptToken(const uint8_t* ciphertext, size_t len,
                                 const uint8_t* nonce, uint8_t* plaintext)
{
    if (!m_initialized)
    {
        return false;
    }

    unsigned long long plaintext_len = 0;

    if (crypto_aead_xchacha20poly1305_ietf_decrypt(
            plaintext, &plaintext_len,
            nullptr,
            ciphertext, len,
            nullptr, 0,
            nonce,
            m_rx_key) != 0)
    {
        return false;
    }

    return true;
}

void SecureCipher::SetSessionToken(const uint8_t* token)
{
    memcpy(m_session_token, token, SESSION_TOKEN_SIZE);
}
