//**********************************************************************;
// Copyright (c) 2016, Intel Corporation
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//**********************************************************************;

#include <stdarg.h>
#include <stddef.h>

#include <stdio.h>

#include <setjmp.h>
#include <cmocka.h>

#include <sapi/tpm20.h>
#include "rc-decode.h"

/* Lookup an error code using the provided function. Assert that the returned
 * id matches the one that we looked up.
 *   tpm_rc: TPM_RC we're looking up
 *   lookup_func: function to lookup the TPM_RC
 */
#define LOOKUP_SUCCESS(tpm_rc, lookup_func) \
    LOOKUP_EXPECTED_SUCCESS (tpm_rc, tpm_rc, lookup_func);
/* Same as LOOKUP_SUCCESS but expect a failure.
 */
#define LOOKUP_FAILURE(tpm_rc, lookup_func) \
    TPM_RC rc = (tpm_rc); \
    tpm2_rc_entry_t *entry = NULL; \
    entry = (lookup_func)(rc); \
    assert_null (entry);
/* Lookup TPM_RC and compare to expected value. Assert success condition.
 *   tpm_rc: TPM_RC we're looking up
 *   layer:  expected layer
 *   lookup_func: function to lookup the layer from the provided TPM_RC
 */
#define LOOKUP_EXPECTED_SUCCESS(rc_in, rc_expect, lookup_func) \
    TPM_RC rc = (rc_in); \
    tpm2_rc_entry_t *entry = NULL; \
    entry = (lookup_func)(rc); \
    assert_non_null (entry); \
    assert_int_equal ((rc_expect), entry->id);
/* Check for match in tpm2_tss_base_rc_entry table. */
static void
tpm2_rc_entry_tss_base_rc_general (void **state)
{
    (void) state;
    LOOKUP_SUCCESS (TSS2_BASE_RC_GENERAL_FAILURE, tpm2_get_tss_base_rc_entry);
}
static void
tpm2_rc_entry_tss_base_rc_insufficient_buffer (void **state)
{
    (void) state;
    LOOKUP_SUCCESS (TSS2_BASE_RC_INSUFFICIENT_BUFFER, tpm2_get_tss_base_rc_entry);
}
/* Check for non-existant error codes in the tpm2_tss_base_rc_entry table */
static void
tpm2_rc_entry_tss_base_rc_bad_min (void **state)
{
    (void) state;
    LOOKUP_FAILURE (0x0, tpm2_get_tss_base_rc_entry);
}
static void
tpm2_rc_entry_tss_base_rc_bad_max (void **state)
{
    (void) state;
    LOOKUP_FAILURE (0xffff, tpm2_get_tss_base_rc_entry);
}
/* Check for match in tpm2_tss_base_rc_entry with TCTI layer indicator set */
static void
tpm2_rc_entry_tss_tcti_rc_general (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TSS2_TCTI_RC_INSUFFICIENT_BUFFER,
                             TSS2_BASE_RC_INSUFFICIENT_BUFFER,
                             tpm2_get_tss_base_rc_entry);
}

/* Check for match in tpm2_tss_base_rc_entry with SAPI (aka SYS) layer set */
static void
tpm2_rc_entry_tss_sapi_rc_bad_value (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TSS2_SYS_RC_BAD_VALUE,
                             TSS2_BASE_RC_BAD_VALUE,
                             tpm2_get_tss_base_rc_entry);
}
/* Check for match in tpm2_position_entry for the first parameter indicator.
 * NOTE: The parameter indicator is ignored. In reality this should be checked
 * first.
 */
static void
tpm2_rc_entry_parameter_1 (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TPM_RC_P + TPM_RC_1,
                             TPM_RC_1,
                             tpm2_get_parameter_entry);
}
static void
tpm2_rc_entry_parameter_a (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TPM_RC_P + TPM_RC_A,
                             TPM_RC_A,
                             tpm2_get_parameter_entry);
}
/* same for tpm2_handle_entry */
static void
tpm2_rc_entry_handle_3 (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TPM_RC_H + TPM_RC_3,
                             TPM_RC_3,
                             tpm2_get_handle_entry);
}
static void
tpm2_rc_entry_handle_7 (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TPM_RC_H + TPM_RC_7,
                             TPM_RC_7,
                             tpm2_get_handle_entry);
}
/* same for tpm2_session_entry */
static void
tpm2_rc_entry_session_2 (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TPM_RC_H + TPM_RC_2,
                             TPM_RC_2,
                             tpm2_get_session_entry);
}
static void
tpm2_rc_entry_session_5 (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TPM_RC_H + TPM_RC_5,
                             TPM_RC_5,
                             tpm2_get_session_entry);
}
/* Check for match in tpm2_tss_layer_entry for the TCTI layer. */
static void
tpm2_rc_entry_layer_tcti_from_general_failure (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TSS2_TCTI_RC_GENERAL_FAILURE,
                             TSS2_TCTI_ERROR_LEVEL,
                             tpm2_get_layer_entry);
}
static void
tpm2_rc_entry_layer_tcti_from_not_permitted (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TSS2_TCTI_RC_NOT_PERMITTED,
                             TSS2_TCTI_ERROR_LEVEL,
                             tpm2_get_layer_entry);
}
/* Check for match in tpm2_tss_layer_entry for the SAPI (aka SYS) layer */
static void
tpm2_rc_entry_layer_sapi_from_no_encrypt_param (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TSS2_SYS_RC_NO_ENCRYPT_PARAM,
                             TSS2_SYS_ERROR_LEVEL,
                             tpm2_get_layer_entry);
}
static void
tpm2_rc_entry_layer_sapi_from_bad_tcti_structure (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TSS2_SYS_RC_BAD_TCTI_STRUCTURE,
                             TSS2_SYS_ERROR_LEVEL,
                             tpm2_get_layer_entry);
}
/* Part2 error messages generated by SAPI as part of offloading the error
 * checking.
 */
static void
tpm2_rc_entry_layer_part2_from_all (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TSS2_SYS_PART2_ERROR_LEVEL | ~(0xff << TSS2_RC_LEVEL_SHIFT),
                             TSS2_SYS_PART2_ERROR_LEVEL,
                             tpm2_get_layer_entry);
}
/* Lookup layer to identify errors from the TPM */
static void
tpm2_rc_entry_layer_tpm (void **state)
{
    (void) state;
    LOOKUP_EXPECTED_SUCCESS (TSS2_TPM_ERROR_LEVEL | ~(0xff << TSS2_RC_LEVEL_SHIFT),
                             TSS2_TPM_ERROR_LEVEL,
                             tpm2_get_layer_entry);
}
/* Lookup non-existant error level */
static void
tpm2_rc_entry_layer_bad (void **state)
{
    (void) state;
    LOOKUP_FAILURE (~(0x1 << TSS2_RC_LEVEL_SHIFT), tpm2_get_layer_entry);
}
/* Lookup structures holding data about format0 / VER1 error codes.
 */
static void
tpm2_rc_entry_fmt0_failure (void **state)
{
    (void) state;
    LOOKUP_SUCCESS (TPM_RC_FAILURE, tpm2_get_fmt0_entry);
}
static void
tpm2_rc_entry_fmt0_pcr_changed (void **state)
{
    (void) state;
    LOOKUP_SUCCESS (TPM_RC_PCR_CHANGED, tpm2_get_fmt0_entry);
}
/* Check for no match on invalid fmt0 / ver1 error code */
static void
tpm2_rc_entry_fmt0_bad (void **state)
{
    (void) state;
    LOOKUP_FAILURE (0x023, tpm2_get_fmt0_entry);
}
/* Lookup structures holding data about format1 / FMT1 error codes.
 */
static void
tpm2_rc_entry_fmt1_hierarchy (void **state)
{
    (void) state;
    LOOKUP_SUCCESS (TPM_RC_HIERARCHY, tpm2_get_fmt1_entry);
}
static void
tpm2_rc_entry_fmt1_expired (void **state)
{
    (void) state;
    LOOKUP_SUCCESS (TPM_RC_EXPIRED, tpm2_get_fmt1_entry);
}
static void
tpm2_rc_entry_fmt1_bad (void **state)
{
    (void) state;
    LOOKUP_FAILURE (0x30, tpm2_get_fmt1_entry);
}
/* Lookup structures holding data about fmt1 warning error codes.
 */
static void
tpm2_rc_entry_warn_memory (void **state)
{
    (void) state;
    LOOKUP_SUCCESS (TPM_RC_MEMORY, tpm2_get_warn_entry);
}
static void
tpm2_rc_entry_warn_reference_s5 (void **state)
{
    (void) state;
    LOOKUP_SUCCESS (TPM_RC_REFERENCE_S5, tpm2_get_warn_entry);
}
static void
tpm2_rc_entry_warn_bad (void **state)
{
    (void) state;
    LOOKUP_FAILURE (0x17, tpm2_get_warn_entry);
}
int
main (int   argc,
      char *argv[])
{
    (void)argc;
    (void)argv;

    const struct CMUnitTest tests[] = {
        cmocka_unit_test (tpm2_rc_entry_tss_base_rc_general),
        cmocka_unit_test (tpm2_rc_entry_tss_base_rc_insufficient_buffer),
        cmocka_unit_test (tpm2_rc_entry_tss_base_rc_bad_min),
        cmocka_unit_test (tpm2_rc_entry_tss_base_rc_bad_max),
        cmocka_unit_test (tpm2_rc_entry_tss_tcti_rc_general),
        cmocka_unit_test (tpm2_rc_entry_tss_sapi_rc_bad_value),
        cmocka_unit_test (tpm2_rc_entry_parameter_1),
        cmocka_unit_test (tpm2_rc_entry_parameter_a),
        cmocka_unit_test (tpm2_rc_entry_handle_3),
        cmocka_unit_test (tpm2_rc_entry_handle_7),
        cmocka_unit_test (tpm2_rc_entry_session_2),
        cmocka_unit_test (tpm2_rc_entry_session_5),
        cmocka_unit_test (tpm2_rc_entry_layer_tcti_from_general_failure),
        cmocka_unit_test (tpm2_rc_entry_layer_tcti_from_not_permitted),
        cmocka_unit_test (tpm2_rc_entry_layer_sapi_from_no_encrypt_param),
        cmocka_unit_test (tpm2_rc_entry_layer_sapi_from_bad_tcti_structure),
        cmocka_unit_test (tpm2_rc_entry_layer_part2_from_all),
        cmocka_unit_test (tpm2_rc_entry_layer_tpm),
        cmocka_unit_test (tpm2_rc_entry_layer_bad),
        cmocka_unit_test (tpm2_rc_entry_fmt0_failure),
        cmocka_unit_test (tpm2_rc_entry_fmt0_pcr_changed),
        cmocka_unit_test (tpm2_rc_entry_fmt0_bad),
        cmocka_unit_test (tpm2_rc_entry_fmt1_hierarchy),
        cmocka_unit_test (tpm2_rc_entry_fmt1_expired),
        cmocka_unit_test (tpm2_rc_entry_fmt1_bad),
        cmocka_unit_test (tpm2_rc_entry_warn_memory),
        cmocka_unit_test (tpm2_rc_entry_warn_reference_s5),
        cmocka_unit_test (tpm2_rc_entry_warn_bad),
    };

    return cmocka_run_group_tests(tests, NULL, NULL);
}
