/* BEGIN_HEADER */
#include "psa_crypto_helpers.h"
#include "psa/crypto_se_driver.h"

#include "psa_crypto_se.h"
#include "psa_crypto_storage.h"

/** The location and lifetime used for tests that use a single driver. */
#define TEST_DRIVER_LOCATION 1
#define TEST_SE_PERSISTENT_LIFETIME                             \
    ( PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION(           \
        PSA_KEY_PERSISTENCE_DEFAULT, TEST_DRIVER_LOCATION ) )

static struct
{
    uint16_t called;
    psa_key_location_t location;
    psa_status_t return_value;
} mock_init_data;

static struct
{
    uint16_t called;
    psa_key_slot_number_t key_slot;
    psa_key_attributes_t attributes;
    size_t pubkey_size;
    psa_status_t return_value;
} mock_generate_data;

static struct
{
    uint16_t called;
    psa_key_slot_number_t key_slot;
    psa_key_attributes_t attributes;
    size_t bits;
    size_t data_length;
    psa_status_t return_value;
} mock_import_data;

static struct
{
    uint16_t called;
    psa_key_slot_number_t slot_number;
    size_t data_size;
    psa_status_t return_value;
} mock_export_data;

static struct
{
    uint16_t called;
    psa_key_slot_number_t slot_number;
    size_t data_size;
    psa_status_t return_value;
} mock_export_public_data;

static struct
{
    uint16_t called;
    psa_key_slot_number_t key_slot;
    psa_algorithm_t alg;
    size_t hash_length;
    size_t signature_size;
    psa_status_t return_value;
} mock_sign_data;

static struct
{
    uint16_t called;
    psa_key_slot_number_t key_slot;
    psa_algorithm_t alg;
    size_t hash_length;
    size_t signature_length;
    psa_status_t return_value;
} mock_verify_data;

static struct
{
    uint16_t called;
    psa_status_t return_value;
} mock_allocate_data;

static struct
{
    uint16_t called;
    psa_key_slot_number_t slot_number;
    psa_status_t return_value;
} mock_destroy_data;

#define MAX_KEY_ID_FOR_TEST 10
static void psa_purge_storage( void )
{
    psa_key_id_t id;
    psa_key_location_t location;
    /* The tests may have potentially created key ids from 1 to
     * MAX_KEY_ID_FOR_TEST. In addition, run the destroy function on key id
     * 0, which file-based storage uses as a temporary file. */
    for( id = 0; id <= MAX_KEY_ID_FOR_TEST; id++ )
        psa_destroy_persistent_key( id );
    /* Purge the transaction file. */
    psa_crypto_stop_transaction( );
    /* Purge driver persistent data. */
    for( location = 0; location < PSA_MAX_SE_LOCATION; location++ )
        psa_destroy_se_persistent_data( location );
}

static void mock_teardown( void )
{
    memset( &mock_init_data, 0, sizeof( mock_init_data ) );
    memset( &mock_import_data, 0, sizeof( mock_import_data ) );
    memset( &mock_export_data, 0, sizeof( mock_export_data ) );
    memset( &mock_export_public_data, 0, sizeof( mock_export_public_data ) );
    memset( &mock_sign_data, 0, sizeof( mock_sign_data ) );
    memset( &mock_verify_data, 0, sizeof( mock_verify_data ) );
    memset( &mock_allocate_data, 0, sizeof( mock_allocate_data ) );
    memset( &mock_destroy_data, 0, sizeof( mock_destroy_data ) );
    memset( &mock_generate_data, 0, sizeof( mock_generate_data ) );
    psa_purge_storage( );
}

static psa_status_t mock_init( psa_drv_se_context_t *drv_context,
                               void *persistent_data,
                               psa_key_location_t location )
{
    (void) drv_context;
    (void) persistent_data;

    mock_init_data.called++;
    mock_init_data.location = location;
    return( mock_init_data.return_value );
}

static psa_status_t mock_generate( psa_drv_se_context_t *drv_context,
                                   psa_key_slot_number_t key_slot,
                                   const psa_key_attributes_t *attributes,
                                   uint8_t *pubkey,
                                   size_t pubkey_size,
                                   size_t *pubkey_length )
{
    (void) drv_context;
    (void) pubkey;
    (void) pubkey_length;

    mock_generate_data.called++;
    mock_generate_data.key_slot = key_slot;
    mock_generate_data.attributes = *attributes;
    mock_generate_data.pubkey_size = pubkey_size;

    return( mock_generate_data.return_value );
}

static psa_status_t mock_import( psa_drv_se_context_t *drv_context,
                                 psa_key_slot_number_t key_slot,
                                 const psa_key_attributes_t *attributes,
                                 const uint8_t *data,
                                 size_t data_length,
                                 size_t *bits )
{
    (void) drv_context;
    (void) data;

    *bits = mock_import_data.bits;

    mock_import_data.called++;
    mock_import_data.key_slot = key_slot;
    mock_import_data.attributes = *attributes;
    mock_import_data.data_length = data_length;

    return( mock_import_data.return_value );
}

psa_status_t mock_export( psa_drv_se_context_t *context,
                          psa_key_slot_number_t slot_number,
                          uint8_t *p_data,
                          size_t data_size,
                          size_t *p_data_length )
{
    (void) context;
    (void) p_data;
    (void) p_data_length;

    mock_export_data.called++;
    mock_export_data.slot_number = slot_number;
    mock_export_data.data_size = data_size;

    return( mock_export_data.return_value );
}

psa_status_t mock_export_public( psa_drv_se_context_t *context,
                                 psa_key_slot_number_t slot_number,
                                 uint8_t *p_data,
                                 size_t data_size,
                                 size_t *p_data_length )
{
    (void) context;
    (void) p_data;
    (void) p_data_length;

    mock_export_public_data.called++;
    mock_export_public_data.slot_number = slot_number;
    mock_export_public_data.data_size = data_size;

    return( mock_export_public_data.return_value );
}

psa_status_t mock_sign( psa_drv_se_context_t *context,
                        psa_key_slot_number_t key_slot,
                        psa_algorithm_t alg,
                        const uint8_t *p_hash,
                        size_t hash_length,
                        uint8_t *p_signature,
                        size_t signature_size,
                        size_t *p_signature_length )
{
    (void) context;
    (void) p_hash;
    (void) p_signature;
    (void) p_signature_length;

    mock_sign_data.called++;
    mock_sign_data.key_slot = key_slot;
    mock_sign_data.alg = alg;
    mock_sign_data.hash_length = hash_length;
    mock_sign_data.signature_size = signature_size;

    return mock_sign_data.return_value;
}

psa_status_t mock_verify( psa_drv_se_context_t *context,
                          psa_key_slot_number_t key_slot,
                          psa_algorithm_t alg,
                          const uint8_t *p_hash,
                          size_t hash_length,
                          const uint8_t *p_signature,
                          size_t signature_length )
{
    (void) context;
    (void) p_hash;
    (void) p_signature;

    mock_verify_data.called++;
    mock_verify_data.key_slot = key_slot;
    mock_verify_data.alg = alg;
    mock_verify_data.hash_length = hash_length;
    mock_verify_data.signature_length = signature_length;

    return mock_verify_data.return_value;
}

psa_status_t mock_allocate( psa_drv_se_context_t *drv_context,
                            void *persistent_data,
                            const psa_key_attributes_t *attributes,
                            psa_key_creation_method_t method,
                            psa_key_slot_number_t *key_slot )
{
    (void) drv_context;
    (void) persistent_data;
    (void) attributes;
    (void) method;
    (void) key_slot;

    mock_allocate_data.called++;
    *key_slot = 0;

    return( mock_allocate_data.return_value );
}

psa_status_t mock_destroy( psa_drv_se_context_t *context,
                           void *persistent_data,
                           psa_key_slot_number_t slot_number )
{
    (void) context;
    (void) persistent_data;

    mock_destroy_data.called++;
    mock_destroy_data.slot_number = slot_number;

    return( mock_destroy_data.return_value );
}

/* END_HEADER */

/* BEGIN_DEPENDENCIES
 * depends_on:MBEDTLS_PSA_CRYPTO_SE_C
 * END_DEPENDENCIES
 */

/* BEGIN_CASE */
void mock_init( int location_arg,
                int expected_register_status_arg,
                int driver_status_arg,
                int expected_psa_status_arg,
                int expected_called )
{
    psa_key_location_t location = location_arg;
    psa_status_t expected_register_status = expected_register_status_arg;
    psa_status_t driver_status = driver_status_arg;
    psa_status_t expected_psa_status = expected_psa_status_arg;
    psa_drv_se_t driver = {
        .hal_version = PSA_DRV_SE_HAL_VERSION,
        .p_init = mock_init,
    };
    int psa_crypto_init_called = 0;

    mock_init_data.return_value = driver_status;

    TEST_EQUAL( psa_register_se_driver( location, &driver ),
                expected_register_status );

    psa_crypto_init_called = 1;
    TEST_EQUAL( psa_crypto_init( ), expected_psa_status );

    TEST_EQUAL( mock_init_data.called, expected_called );
    if( expected_called )
        TEST_EQUAL( mock_init_data.location, location );

exit:
    if( psa_crypto_init_called )
        PSA_DONE( );
    mock_teardown( );
}
/* END_CASE */

/* BEGIN_CASE */
void mock_import( int mock_alloc_return_value,
                  int mock_import_return_value,
                  int bits,
                  int expected_result )
{
    psa_drv_se_t driver;
    psa_drv_se_key_management_t key_management;
    psa_key_lifetime_t lifetime = TEST_SE_PERSISTENT_LIFETIME;
    psa_key_location_t location = PSA_KEY_LIFETIME_GET_LOCATION( lifetime );
    psa_key_id_t id = 1;
    psa_key_handle_t handle = 0;
    psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
    const uint8_t key_material[3] = {0xfa, 0xca, 0xde};

    mock_allocate_data.return_value = mock_alloc_return_value;
    mock_import_data.return_value = mock_import_return_value;
    mock_import_data.bits = bits;
    memset( &driver, 0, sizeof( driver ) );
    memset( &key_management, 0, sizeof( key_management ) );
    driver.hal_version = PSA_DRV_SE_HAL_VERSION;
    driver.key_management = &key_management;
    key_management.p_import = mock_import;
    key_management.p_destroy = mock_destroy;
    key_management.p_allocate = mock_allocate;

    PSA_ASSERT( psa_register_se_driver( location, &driver ) );
    PSA_ASSERT( psa_crypto_init( ) );

    psa_set_key_id( &attributes, id );
    psa_set_key_lifetime( &attributes, lifetime );
    psa_set_key_usage_flags( &attributes, PSA_KEY_USAGE_EXPORT );
    psa_set_key_type( &attributes, PSA_KEY_TYPE_RAW_DATA );
    TEST_ASSERT( psa_import_key( &attributes,
                                 key_material, sizeof( key_material ),
                                 &handle ) == expected_result );

    TEST_ASSERT( mock_allocate_data.called == 1 );
    TEST_ASSERT( mock_import_data.called ==
        ( mock_alloc_return_value == PSA_SUCCESS? 1 : 0 ) );
    TEST_ASSERT( mock_import_data.attributes.core.id ==
        ( mock_alloc_return_value == PSA_SUCCESS? id : 0 ) );
    TEST_ASSERT( mock_import_data.attributes.core.lifetime ==
        ( mock_alloc_return_value == PSA_SUCCESS? lifetime : 0 ) );
    TEST_ASSERT( mock_import_data.attributes.core.policy.usage ==
        ( mock_alloc_return_value == PSA_SUCCESS? PSA_KEY_USAGE_EXPORT : 0 ) );
    TEST_ASSERT( mock_import_data.attributes.core.type ==
        ( mock_alloc_return_value == PSA_SUCCESS? PSA_KEY_TYPE_RAW_DATA : 0 ) );

    if( expected_result == PSA_SUCCESS )
    {
        PSA_ASSERT( psa_destroy_key( handle ) );
        TEST_ASSERT( mock_destroy_data.called == 1 );
    }
exit:
    PSA_DONE( );
    mock_teardown( );
}
/* END_CASE */

/* BEGIN_CASE */
void mock_export( int mock_export_return_value, int expected_result )
{
    psa_drv_se_t driver;
    psa_drv_se_key_management_t key_management;
    psa_key_lifetime_t lifetime = TEST_SE_PERSISTENT_LIFETIME;
    psa_key_location_t location = PSA_KEY_LIFETIME_GET_LOCATION( lifetime );
    psa_key_id_t id = 1;
    psa_key_handle_t handle = 0;
    psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
    const uint8_t key_material[3] = {0xfa, 0xca, 0xde};
    uint8_t exported[sizeof( key_material )];
    size_t exported_length;

    mock_export_data.return_value = mock_export_return_value;
    memset( &driver, 0, sizeof( driver ) );
    memset( &key_management, 0, sizeof( key_management ) );
    driver.hal_version = PSA_DRV_SE_HAL_VERSION;
    driver.key_management = &key_management;
    driver.p_init = mock_init;
    key_management.p_import = mock_import;
    key_management.p_export = mock_export;
    key_management.p_destroy = mock_destroy;
    key_management.p_allocate = mock_allocate;

    PSA_ASSERT( psa_register_se_driver( location, &driver ) );
    PSA_ASSERT( psa_crypto_init( ) );

    psa_set_key_id( &attributes, id );
    psa_set_key_lifetime( &attributes, lifetime );
    psa_set_key_usage_flags( &attributes, PSA_KEY_USAGE_EXPORT );
    psa_set_key_type( &attributes, PSA_KEY_TYPE_RAW_DATA );
    PSA_ASSERT( psa_import_key( &attributes,
                                key_material, sizeof( key_material ),
                                &handle ) );

    TEST_ASSERT( psa_export_key( handle,
                                exported, sizeof( exported ),
                                &exported_length ) == expected_result );

    TEST_ASSERT( mock_export_data.called == 1 );

    PSA_ASSERT( psa_destroy_key( handle ) );

    TEST_ASSERT( mock_destroy_data.called == 1 );

exit:
    PSA_DONE( );
    mock_teardown( );
}
/* END_CASE */

/* BEGIN_CASE */
void mock_generate( int mock_alloc_return_value,
                    int mock_generate_return_value,
                    int expected_result )
{
    psa_drv_se_t driver;
    psa_drv_se_key_management_t key_management;
    psa_key_lifetime_t lifetime = TEST_SE_PERSISTENT_LIFETIME;
    psa_key_location_t location = PSA_KEY_LIFETIME_GET_LOCATION( lifetime );
    psa_key_id_t id = 1;
    psa_key_handle_t handle = 0;
    psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;

    mock_allocate_data.return_value = mock_alloc_return_value;
    mock_generate_data.return_value = mock_generate_return_value;
    memset( &driver, 0, sizeof( driver ) );
    memset( &key_management, 0, sizeof( key_management ) );
    driver.hal_version = PSA_DRV_SE_HAL_VERSION;
    driver.key_management = &key_management;
    key_management.p_generate = mock_generate;
    key_management.p_destroy = mock_destroy;
    key_management.p_allocate = mock_allocate;

    PSA_ASSERT( psa_register_se_driver( location, &driver ) );
    PSA_ASSERT( psa_crypto_init( ) );

    psa_set_key_id( &attributes, id );
    psa_set_key_lifetime( &attributes, lifetime );
    psa_set_key_usage_flags( &attributes, PSA_KEY_USAGE_EXPORT );
    psa_set_key_type( &attributes, PSA_KEY_TYPE_RAW_DATA );
    psa_set_key_bits( &attributes, 8 );
    TEST_ASSERT( psa_generate_key( &attributes, &handle ) == expected_result );
    TEST_ASSERT( mock_allocate_data.called == 1 );
    TEST_ASSERT( mock_generate_data.called ==
        ( mock_alloc_return_value == PSA_SUCCESS? 1 : 0 ) );
    TEST_ASSERT( mock_generate_data.attributes.core.id ==
        ( mock_alloc_return_value == PSA_SUCCESS? id : 0 ) );
    TEST_ASSERT( mock_generate_data.attributes.core.lifetime ==
        ( mock_alloc_return_value == PSA_SUCCESS? lifetime : 0 ) );
    TEST_ASSERT( mock_generate_data.attributes.core.policy.usage ==
        ( mock_alloc_return_value == PSA_SUCCESS? PSA_KEY_USAGE_EXPORT : 0 ) );
    TEST_ASSERT( mock_generate_data.attributes.core.type ==
        ( mock_alloc_return_value == PSA_SUCCESS? PSA_KEY_TYPE_RAW_DATA : 0 ) );

    if( expected_result == PSA_SUCCESS )
    {
        PSA_ASSERT( psa_destroy_key( handle ) );
        TEST_ASSERT( mock_destroy_data.called == 1 );
    }

exit:
    PSA_DONE( );
    mock_teardown( );
}
/* END_CASE */

/* BEGIN_CASE */
void mock_export_public( int mock_export_public_return_value,
                         int expected_result )
{
    psa_drv_se_t driver;
    psa_drv_se_key_management_t key_management;
    psa_key_lifetime_t lifetime = TEST_SE_PERSISTENT_LIFETIME;
    psa_key_location_t location = PSA_KEY_LIFETIME_GET_LOCATION( lifetime );
    psa_key_id_t id = 1;
    psa_key_handle_t handle = 0;
    psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
    const uint8_t key_material[3] = {0xfa, 0xca, 0xde};
    uint8_t exported[sizeof( key_material )];
    size_t exported_length;

    mock_export_public_data.return_value = mock_export_public_return_value;
    memset( &driver, 0, sizeof( driver ) );
    memset( &key_management, 0, sizeof( key_management ) );
    driver.hal_version = PSA_DRV_SE_HAL_VERSION;
    driver.key_management = &key_management;
    key_management.p_import = mock_import;
    key_management.p_export_public = mock_export_public;
    key_management.p_destroy = mock_destroy;
    key_management.p_allocate = mock_allocate;

    PSA_ASSERT( psa_register_se_driver( location, &driver ) );
    PSA_ASSERT( psa_crypto_init( ) );

    psa_set_key_id( &attributes, id );
    psa_set_key_lifetime( &attributes, lifetime );
    psa_set_key_usage_flags( &attributes, PSA_KEY_USAGE_EXPORT );
    psa_set_key_type( &attributes, PSA_KEY_TYPE_RSA_PUBLIC_KEY );

    PSA_ASSERT( psa_import_key( &attributes,
                                key_material, sizeof( key_material ),
                                &handle ) );

    TEST_ASSERT( psa_export_public_key( handle, exported, sizeof(exported),
                                        &exported_length ) == expected_result );
    TEST_ASSERT( mock_export_public_data.called == 1 );

    PSA_ASSERT( psa_destroy_key( handle ) );
    TEST_ASSERT( mock_destroy_data.called == 1 );

exit:
    PSA_DONE( );
    mock_teardown( );
}
/* END_CASE */

/* BEGIN_CASE */
void mock_sign( int mock_sign_return_value, int expected_result )
{
    psa_drv_se_t driver;
    psa_drv_se_key_management_t key_management;
    psa_drv_se_asymmetric_t asymmetric;
    psa_key_lifetime_t lifetime = TEST_SE_PERSISTENT_LIFETIME;
    psa_key_location_t location = PSA_KEY_LIFETIME_GET_LOCATION( lifetime );
    psa_key_id_t id = 1;
    psa_key_handle_t handle = 0;
    psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
    const uint8_t key_material[3] = {0xfa, 0xca, 0xde};
    psa_algorithm_t algorithm = PSA_ALG_ECDSA(PSA_ALG_SHA_256);
    const uint8_t hash[1] = {'H'};
    uint8_t signature[1] = {'S'};
    size_t signature_length;

    mock_sign_data.return_value = mock_sign_return_value;
    memset( &driver, 0, sizeof( driver ) );
    memset( &key_management, 0, sizeof( key_management ) );
    memset( &asymmetric, 0, sizeof( asymmetric ) );

    driver.hal_version = PSA_DRV_SE_HAL_VERSION;

    driver.key_management = &key_management;
    key_management.p_import = mock_import;
    key_management.p_destroy = mock_destroy;
    key_management.p_allocate = mock_allocate;

    driver.asymmetric = &asymmetric;
    asymmetric.p_sign = mock_sign;

    PSA_ASSERT( psa_register_se_driver( location, &driver ) );
    PSA_ASSERT( psa_crypto_init( ) );

    psa_set_key_id( &attributes, id );
    psa_set_key_lifetime( &attributes, lifetime );
    psa_set_key_usage_flags( &attributes, PSA_KEY_USAGE_SIGN_HASH );
    psa_set_key_algorithm( &attributes, algorithm );
    psa_set_key_type( &attributes, PSA_KEY_TYPE_RSA_KEY_PAIR );

    PSA_ASSERT( psa_import_key( &attributes,
                                key_material, sizeof( key_material ),
                                &handle ) );

    TEST_ASSERT( psa_sign_hash( handle, algorithm,
                                hash, sizeof( hash ),
                                signature, sizeof( signature ),
                                &signature_length)
                 == expected_result );
    TEST_ASSERT( mock_sign_data.called == 1 );

    PSA_ASSERT( psa_destroy_key( handle ) );
    TEST_ASSERT( mock_destroy_data.called == 1 );

exit:
    PSA_DONE( );
    mock_teardown( );
}
/* END_CASE */

/* BEGIN_CASE */
void mock_verify( int mock_verify_return_value, int expected_result )
{
    psa_drv_se_t driver;
    psa_drv_se_key_management_t key_management;
    psa_drv_se_asymmetric_t asymmetric;
    psa_key_lifetime_t lifetime = TEST_SE_PERSISTENT_LIFETIME;
    psa_key_location_t location = PSA_KEY_LIFETIME_GET_LOCATION( lifetime );
    psa_key_id_t id = 1;
    psa_key_handle_t handle = 0;
    psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
    const uint8_t key_material[3] = {0xfa, 0xca, 0xde};
    psa_algorithm_t algorithm = PSA_ALG_ECDSA(PSA_ALG_SHA_256);
    const uint8_t hash[1] = {'H'};
    const uint8_t signature[1] = {'S'};

    mock_verify_data.return_value = mock_verify_return_value;
    memset( &driver, 0, sizeof( driver ) );
    memset( &key_management, 0, sizeof( key_management ) );
    memset( &asymmetric, 0, sizeof( asymmetric ) );

    driver.hal_version = PSA_DRV_SE_HAL_VERSION;

    driver.key_management = &key_management;
    key_management.p_import = mock_import;
    key_management.p_destroy = mock_destroy;
    key_management.p_allocate = mock_allocate;

    driver.asymmetric = &asymmetric;
    asymmetric.p_verify = mock_verify;

    PSA_ASSERT( psa_register_se_driver( location, &driver ) );
    PSA_ASSERT( psa_crypto_init( ) );

    psa_set_key_id( &attributes, id );
    psa_set_key_lifetime( &attributes, lifetime );
    psa_set_key_usage_flags( &attributes, PSA_KEY_USAGE_VERIFY_HASH );
    psa_set_key_algorithm( &attributes, algorithm );
    psa_set_key_type( &attributes, PSA_KEY_TYPE_RAW_DATA );

    PSA_ASSERT( psa_import_key( &attributes,
                                key_material, sizeof( key_material ),
                                &handle ) );

    TEST_ASSERT( psa_verify_hash( handle, algorithm,
                                  hash, sizeof( hash ),
                                  signature, sizeof( signature ) )
                 == expected_result );
    TEST_ASSERT( mock_verify_data.called == 1 );

    PSA_ASSERT( psa_destroy_key( handle ) );
    TEST_ASSERT( mock_destroy_data.called == 1 );

exit:
    PSA_DONE( );
    mock_teardown( );
}
/* END_CASE */
