/*
 * Copyright (C) 2006-2026 wolfSSL Inc.
 *
 * This file is part of wolfSSL.
 *
 * wolfSSL is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * wolfSSL 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-1335, USA
 */

#![cfg(lms)]

mod common;

use wolfssl_wolfcrypt::lms::Lms;
use wolfssl_wolfcrypt::sys;
#[cfg(all(lms_make_key, random))]
use wolfssl_wolfcrypt::random::RNG;

/// Private key NV storage for tests that require make_key / sign / reload.
///
/// The write and read callbacks use a raw pointer to this struct as the
/// context argument. Each test that needs persistent NV storage creates its
/// own instance and boxes it to keep it alive for the duration of the test.
#[cfg(lms_make_key)]
struct KeyStore {
    buf: [u8; 16384],
}

#[cfg(lms_make_key)]
unsafe extern "C" fn write_key_cb(
    priv_data: *const u8,
    priv_sz: u32,
    ctx: *mut core::ffi::c_void,
) -> i32 {
    let store = unsafe { &mut *(ctx as *mut KeyStore) };
    let priv_sz = priv_sz as usize;
    store.buf[..priv_sz]
        .copy_from_slice(unsafe { core::slice::from_raw_parts(priv_data, priv_sz) });
    sys::wc_LmsRc_WC_LMS_RC_SAVED_TO_NV_MEMORY as i32
}

#[cfg(lms_make_key)]
unsafe extern "C" fn read_key_cb(
    priv_data: *mut u8,
    priv_sz: u32,
    ctx: *mut core::ffi::c_void,
) -> i32 {
    let store = unsafe { &*(ctx as *mut KeyStore) };
    let priv_sz = priv_sz as usize;
    unsafe { core::slice::from_raw_parts_mut(priv_data, priv_sz) }
        .copy_from_slice(&store.buf[..priv_sz]);
    sys::wc_LmsRc_WC_LMS_RC_READ_TO_MEMORY as i32
}

/// Register the write and read callbacks and a context pointer on `key`.
#[cfg(lms_make_key)]
fn setup_callbacks(key: &mut Lms, ctx: *mut core::ffi::c_void) {
    key.set_write_cb(Some(write_key_cb)).expect("Error with set_write_cb()");
    key.set_read_cb(Some(read_key_cb)).expect("Error with set_read_cb()");
    // Safety: ctx points to a BoxedKeyStore that outlives all callback invocations.
    unsafe { key.set_context(ctx).expect("Error with set_context()") };
}

// ---------------------------------------------------------------------------
// Constant and construction tests (no keygen required)
// ---------------------------------------------------------------------------

/// Verify the KEY_ID_LEN constant matches the C `WC_LMS_I_LEN` value.
#[test]
fn test_key_id_len_constant() {
    assert_eq!(Lms::KEY_ID_LEN, 16);
}

/// Verify that `new()` succeeds.
#[test]
fn test_new() {
    common::setup();
    Lms::new().expect("Error with Lms::new()");
}

/// Verify that `new_ex()` accepts the optional heap and device ID parameters.
#[test]
fn test_new_ex() {
    common::setup();
    Lms::new_ex(None, None).expect("Error with Lms::new_ex()");
}

/// Verify that `set_parm()` accepts a predefined wc_LmsParm value.
#[test]
fn test_set_parm() {
    common::setup();
    let mut key = Lms::new().expect("Error with Lms::new()");
    key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()");
}

/// Verify that `set_parameters()` accepts explicit L/H/W values and that
/// `get_parameters()` returns them unchanged.
#[test]
fn test_set_get_parameters() {
    common::setup();

    for &(levels, height, winternitz) in &[(1, 5, 8), (1, 10, 4), (2, 5, 8)] {
        // Use a fresh key for each parameter set; wc_LmsKey_SetParameters does
        // not allow re-setting parameters on an already-configured key.
        let mut k = Lms::new().expect("Error with Lms::new()");
        k.set_parameters(levels, height, winternitz)
            .expect("Error with set_parameters()");
        let (l, h, w) = k.get_parameters().expect("Error with get_parameters()");
        assert_eq!(l, levels, "levels mismatch for ({},{},{})", levels, height, winternitz);
        assert_eq!(h, height, "height mismatch for ({},{},{})", levels, height, winternitz);
        assert_eq!(w, winternitz, "winternitz mismatch for ({},{},{})", levels, height, winternitz);
    }
}

/// Verify that `get_sig_len()` and `get_pub_len()` return positive values
/// after setting a predefined parameter set.
#[test]
fn test_size_queries_after_set_parm() {
    common::setup();
    for &parm in &[
        Lms::PARM_L1_H5_W8,
        Lms::PARM_L1_H5_W4,
        Lms::PARM_L1_H10_W8,
    ] {
        let mut key = Lms::new().expect("Error with Lms::new()");
        key.set_parm(parm).expect("Error with set_parm()");
        let sig_len = key.get_sig_len().expect("Error with get_sig_len()");
        let pub_len = key.get_pub_len().expect("Error with get_pub_len()");
        assert!(sig_len > 0, "sig_len must be positive for parm {}", parm);
        assert!(pub_len > 0, "pub_len must be positive for parm {}", parm);
    }
}

/// Verify that `get_sig_len()` grows with the number of levels (higher
/// level count increases signature size significantly).
#[test]
fn test_sig_len_increases_with_levels() {
    common::setup();
    let mut key1 = Lms::new().expect("Error with Lms::new()");
    key1.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()");
    let sig_len_l1 = key1.get_sig_len().expect("Error with get_sig_len() L1");

    let mut key2 = Lms::new().expect("Error with Lms::new()");
    key2.set_parm(Lms::PARM_L2_H5_W8).expect("Error with set_parm()");
    let sig_len_l2 = key2.get_sig_len().expect("Error with get_sig_len() L2");

    assert!(
        sig_len_l2 > sig_len_l1,
        "L2 sig_len ({}) must exceed L1 sig_len ({})",
        sig_len_l2,
        sig_len_l1
    );
}

// ---------------------------------------------------------------------------
// Private-key-length query (needs lms_make_key for the API)
// ---------------------------------------------------------------------------

/// Verify that `get_priv_len()` returns a positive value after parameters
/// are set. No key generation is required for this query.
#[test]
#[cfg(lms_make_key)]
fn test_get_priv_len() {
    common::setup();
    let mut key = Lms::new().expect("Error with Lms::new()");
    key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()");
    let priv_len = key.get_priv_len().expect("Error with get_priv_len()");
    assert!(priv_len > 0, "priv_len must be positive");
}

// ---------------------------------------------------------------------------
// Key generation and signing tests
// ---------------------------------------------------------------------------

/// Verify that `make_key()` completes without error for the test parameter set.
#[test]
#[cfg(all(lms_make_key, random))]
fn test_make_key() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut store = Box::new(KeyStore { buf: [0u8; 16384] });
    let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void;

    let mut key = Lms::new().expect("Error with Lms::new()");
    key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()");
    setup_callbacks(&mut key, ctx);
    key.make_key(&mut rng).expect("Error with make_key()");

    let _ = store; // keep alive
}

/// Sign a message and verify it with the same key.
#[test]
#[cfg(all(lms_make_key, random))]
fn test_sign_verify() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut store = Box::new(KeyStore { buf: [0u8; 16384] });
    let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void;

    let mut key = Lms::new().expect("Error with Lms::new()");
    key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()");
    setup_callbacks(&mut key, ctx);
    key.make_key(&mut rng).expect("Error with make_key()");

    let message = b"Hello, LMS/HSS!";
    let sig_len = key.get_sig_len().expect("Error with get_sig_len()");
    let mut sig = vec![0u8; sig_len];

    let written = key.sign(message, &mut sig).expect("Error with sign()");
    assert_eq!(written, sig_len, "sign() must fill the entire signature buffer");

    key.verify(&sig, message).expect("Valid signature must verify");

    let _ = store;
}

/// Verify that a signature does not verify for a different message.
#[test]
#[cfg(all(lms_make_key, random))]
fn test_sign_tampered_message() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut store = Box::new(KeyStore { buf: [0u8; 16384] });
    let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void;

    let mut key = Lms::new().expect("Error with Lms::new()");
    key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()");
    setup_callbacks(&mut key, ctx);
    key.make_key(&mut rng).expect("Error with make_key()");

    let message = b"Authentic message";
    let sig_len = key.get_sig_len().expect("Error with get_sig_len()");
    let mut sig = vec![0u8; sig_len];
    key.sign(message, &mut sig).expect("Error with sign()");

    let result = key.verify(&sig, b"Tampered message");
    assert!(result.is_err(), "Signature must not verify for a different message");

    let _ = store;
}

/// Verify that a tampered signature is rejected.
#[test]
#[cfg(all(lms_make_key, random))]
fn test_sign_tampered_signature() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut store = Box::new(KeyStore { buf: [0u8; 16384] });
    let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void;

    let mut key = Lms::new().expect("Error with Lms::new()");
    key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()");
    setup_callbacks(&mut key, ctx);
    key.make_key(&mut rng).expect("Error with make_key()");

    let message = b"Message under test";
    let sig_len = key.get_sig_len().expect("Error with get_sig_len()");
    let mut sig = vec![0u8; sig_len];
    key.sign(message, &mut sig).expect("Error with sign()");

    // Flip a byte in the signature body.
    sig[sig_len / 2] ^= 0xFF;

    let result = key.verify(&sig, message);
    assert!(result.is_err(), "Tampered signature must be rejected");

    let _ = store;
}

/// Export the raw public key, import it into a fresh key, and verify a
/// signature produced by the original key.
#[test]
#[cfg(all(lms_make_key, random))]
fn test_export_pub_raw_import_verify() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut store = Box::new(KeyStore { buf: [0u8; 16384] });
    let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void;

    let mut sign_key = Lms::new().expect("Error with Lms::new()");
    sign_key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()");
    setup_callbacks(&mut sign_key, ctx);
    sign_key.make_key(&mut rng).expect("Error with make_key()");

    let pub_len = sign_key.get_pub_len().expect("Error with get_pub_len()");
    let sig_len = sign_key.get_sig_len().expect("Error with get_sig_len()");

    let mut pub_buf = vec![0u8; pub_len];
    let written = sign_key.export_pub_raw(&mut pub_buf)
        .expect("Error with export_pub_raw()");
    assert_eq!(written, pub_len, "export_pub_raw must fill the entire buffer");

    let message = b"Public key export/import test";
    let mut sig = vec![0u8; sig_len];
    sign_key.sign(message, &mut sig).expect("Error with sign()");

    // Import the raw public key into a new key and verify.
    // wc_LmsKey_ImportPubRaw requires params to be set first.
    let mut verify_key = Lms::new().expect("Error with Lms::new() for verify");
    verify_key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm() for verify key");
    verify_key.import_pub_raw(&pub_buf)
        .expect("Error with import_pub_raw()");
    verify_key.verify(&sig, message)
        .expect("Signature must verify against imported public key");

    let _ = store;
}

/// Verify that `export_pub_from()` copies the public key into a destination
/// key and that signatures from the source verify against that destination.
#[test]
#[cfg(all(lms_make_key, random))]
fn test_export_pub_from() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut store = Box::new(KeyStore { buf: [0u8; 16384] });
    let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void;

    let mut sign_key = Lms::new().expect("Error with Lms::new()");
    sign_key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()");
    setup_callbacks(&mut sign_key, ctx);
    sign_key.make_key(&mut rng).expect("Error with make_key()");

    let message = b"export_pub_from test message";
    let sig_len = sign_key.get_sig_len().expect("Error with get_sig_len()");
    let mut sig = vec![0u8; sig_len];
    sign_key.sign(message, &mut sig).expect("Error with sign()");

    // Copy the public portion into a fresh key.
    let mut verify_key = Lms::new().expect("Error with Lms::new() for verify");
    verify_key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm() for verify key");
    verify_key.export_pub_from(&sign_key)
        .expect("Error with export_pub_from()");

    verify_key.verify(&sig, message)
        .expect("Signature must verify against export_pub_from() key");

    let _ = store;
}

/// Verify that `has_sigs_left()` indicates signatures are available immediately
/// after `make_key()`.
#[test]
#[cfg(all(lms_make_key, random))]
fn test_has_sigs_left_after_make_key() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut store = Box::new(KeyStore { buf: [0u8; 16384] });
    let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void;

    let mut key = Lms::new().expect("Error with Lms::new()");
    key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()");
    setup_callbacks(&mut key, ctx);
    key.make_key(&mut rng).expect("Error with make_key()");

    let remaining = key.has_sigs_left().expect("Error with has_sigs_left()");
    assert!(remaining, "has_sigs_left must be true immediately after make_key()");

    let _ = store;
}

/// Verify that `get_kid()` returns a slice of exactly `KEY_ID_LEN` bytes
/// after key generation.
#[test]
#[cfg(all(lms_make_key, random))]
fn test_get_kid() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut store = Box::new(KeyStore { buf: [0u8; 16384] });
    let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void;

    let mut key = Lms::new().expect("Error with Lms::new()");
    key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()");
    setup_callbacks(&mut key, ctx);
    key.make_key(&mut rng).expect("Error with make_key()");

    let mut kid = [0u8; Lms::KEY_ID_LEN];
    let kid_len = key.get_kid(&mut kid).expect("Error with get_kid()");
    assert_eq!(kid_len, Lms::KEY_ID_LEN, "get_kid() must write KEY_ID_LEN bytes");
    assert!(kid.iter().any(|&b| b != 0), "get_kid() must populate the output buffer");

    let _ = store;
}

/// Verify that `reload()` restores a key to a usable signing state and that
/// a signature produced by the reloaded key verifies correctly.
#[test]
#[cfg(all(lms_make_key, random))]
fn test_reload() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut store = Box::new(KeyStore { buf: [0u8; 16384] });
    let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void;

    // Generate a key pair and export the public key.
    let mut key = Lms::new().expect("Error with Lms::new()");
    key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()");
    setup_callbacks(&mut key, ctx);
    key.make_key(&mut rng).expect("Error with make_key()");

    let pub_len = key.get_pub_len().expect("Error with get_pub_len()");
    let mut pub_buf = vec![0u8; pub_len];
    key.export_pub_raw(&mut pub_buf).expect("Error with export_pub_raw()");

    // Create a new key, reload from NV storage (written by make_key above).
    let mut reloaded = Lms::new().expect("Error with Lms::new() for reload");
    reloaded.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm() for reload");
    setup_callbacks(&mut reloaded, ctx);
    reloaded.reload().expect("Error with reload()");

    // Sign with the reloaded key and verify against the exported public key.
    let message = b"Reload round-trip message";
    let sig_len = reloaded.get_sig_len().expect("Error with get_sig_len()");
    let mut sig = vec![0u8; sig_len];
    reloaded.sign(message, &mut sig).expect("Error with sign() after reload");

    let mut verify_key = Lms::new().expect("Error with Lms::new() for verify");
    // wc_LmsKey_ImportPubRaw requires params to be set first.
    verify_key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm() for verify key");
    verify_key.import_pub_raw(&pub_buf).expect("Error with import_pub_raw()");
    verify_key.verify(&sig, message)
        .expect("Signature from reloaded key must verify");

    let _ = store;
}

/// Sign and verify round-trips across several predefined parameter sets,
/// confirming the implementation is parameter-agnostic.
#[test]
#[cfg(all(lms_make_key, random))]
fn test_sign_verify_multiple_parms() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");

    for &parm in &[
        Lms::PARM_L1_H5_W8,
        Lms::PARM_L1_H5_W4,
        Lms::PARM_L1_H5_W2,
    ] {
        let mut store = Box::new(KeyStore { buf: [0u8; 16384] });
        let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void;

        let mut key = Lms::new().expect("Error with Lms::new()");
        key.set_parm(parm).expect("Error with set_parm()");
        setup_callbacks(&mut key, ctx);
        key.make_key(&mut rng)
            .unwrap_or_else(|e| panic!("make_key failed for parm {}: {}", parm, e));

        let message = b"Parameter set round-trip";
        let sig_len = key.get_sig_len().expect("Error with get_sig_len()");
        let mut sig = vec![0u8; sig_len];

        key.sign(message, &mut sig)
            .unwrap_or_else(|e| panic!("sign failed for parm {}: {}", parm, e));
        key.verify(&sig, message)
            .unwrap_or_else(|e| panic!("verify failed for parm {}: {}", parm, e));

        let _ = store;
    }
}
