// ----------------------------------------------------------------------------
// Copyright 2016-2017 ARM Ltd.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------------------------------------------------------

#include "arm_uc_mmCryptoUtils.h"
#include "arm_uc_mmCommon.h"
#include "arm_uc_mmConfig.h"
#include "arm_uc_mmStateSelector.h"
#include "arm_uc_mmDerManifestAccessors.h"
#include "arm_uc_mmDerManifestParser.h"
#include "arm_uc_mmGetLatestTimestamp.h"
#include "arm_uc_mmFSMHelper.h"
#include "arm_uc_mmInsertManifest.h"
#include "update-client-common/arm_uc_scheduler.h"

#include "update-client-manifest-manager/update-client-manifest-manager-context.h"
#include "update-client-manifest-manager/update-client-manifest-manager.h"
#include "update-client-manifest-manager/update-client-manifest-types.h"

#include "pal4life-device-identity/pal_device_identity.h"
#include "pal.h"

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

/**
 * @file arm_uc_mmInsertManifest.c
 * @brief Inserts a manifest into a slot specified by the manifest.
 * @details This API is used by the manifest manager to validate and store a manifest.
 *
 * The workflow for inserting a manifest is:
 * 1. Check the version of the manifest
 * 2. Validate the cryptographic mode
 * 3. Verify the hash of the manifest.
 * 4. Verify each signature of the manifest.
 * 5. Validate the applicability of the manifest (GUID matching)
 * 6. Validate the storage identifier
 * TBD: Store the manifest in the KCM
 *
 * NOTE: There is a security vs. energy tradeoff in this code.
 * To optimize for energy, the cheapest fields should be checked first, that means checking applicability before
 * hash or signature. However, to optimize for security, we must prioritize safety over energy. Since parsers are
 * notorious sources of bugs, we must make every effort to protect the parser from insecure content. This means
 * accessing the absolute minimum of fields prior to verifyinng the signature.
 *
 * The current version of this code optimizes for security. Once the parser has been more thoroughly validated, we can
 * consider exposing it to more unvalidated data as an energy saving measure.
 *
 * @dot
 * digraph {
 *     Idle
 *     Idle -> Begin [label="[event == BEGIN]"]
 *     Begin
 *     Begin -> VerifyBasicParameters
 *     VerifyBasicParameters
 *     // Validate the manifest size             (This is a precursor to security validation)
 *     // Validate the manifest version          (This is required in order to know how to parse the manifest)
 *     // Validate the manifest encryption mode  (This is required in order to know how to validate the signature)
 *     VerifyBasicParameters -> VerifyHash
 *     VerifyBasicParameters -> VerifyFail [label="[Basic Parameters invalid]"]
 *     VerifyHash
 *     VerifyHash -> VerifySignatureLoopStart
 *     VerifyHash -> VerifyFail [label="[Hash invalid]"]
 *     VerifySignatureLoopStart
 *     VerifySignatureLoopStart -> VerifySignatureStart
 *     VerifySignatureStart
 *     VerifySignatureStart -> VerifySignature
 *     VerifySignatureStart -> VerifyParameters [label="[No More Signatures]"]
 *     VerifySignature
 *     VerifySignature -> VerifyParameters [label="[Last Signature]"]
 *     VerifySignature -> VerifyFail [label="[Signature invalid]"]
 *     // Validate the applicability of the manifest (GUID matching)
 *     // Validate the storage identifier
 *     VerifyParameters
 *     VerifyParameters -> VerifyTimestamp
 *     VerifyParameters -> VerifyFail [label="[Parameters invalid]"]
 *     VerifyTimestampStart
 *     VerifyTimestampStart -> VerifyTimestamp
 *     VerifyTimestamp
 *     VerifyTimestamp -> VerifyApplication
 *     VerifyTimestamp -> VerifyFail [label="[Timestamp too old]"]
 *     VerifyApplication
 *     VerifyApplication -> VerifyDone
 *     VerifyApplication -> VerifyFail [label="[App denied]"]
 *     VerifyFail
 *     VerifyFail -> Idle
 *     VerifyDone
 *     VerifyDone -> AlertHub
 *     AlertHub
 *     AlertHub -> Idle
 * }
 * @enddot
 */

#if ARM_UC_MANIFEST_MANAGER_TRACE_ENABLE
static const char* ARM_UC_mmInsertState2Str(uint32_t state)
{
    switch (state) {
        #define ENUM_AUTO(name) case name: return #name;
        #define ENUM_FIXED(name, val) ENUM_AUTO(name)
        ARM_UC_MM_INS_STATE_LIST
        #undef ENUM_AUTO
        #undef ENUM_FIXED
    default:
        return "Unknown State";
    }
}
#endif

#define max(A,B) ((A)>(B)?(A):(B))

/** @brief Validate that the resource contained in this signed container is a manifest.
 */
static arm_uc_error_t validateResourceType(arm_uc_buffer_t* buffer)
{
    arm_uc_error_t err = {MFST_ERR_NONE};
    arm_uc_buffer_t type = { 0 };
    // Read the resource type field.
    err = ARM_UC_mmDERSignedResourceGetSingleValue(buffer,
                                                   ARM_UC_MM_DER_RESOURCE_TYPE, &type);
    if (type.ptr == NULL)
    {
        err.code = MFST_ERR_DER_FORMAT;
    }
    else if (err.error == ERR_NONE)
    {
        // The resource type must be a manifest.
        if (ARM_UC_mmDerBuf2Uint(&type) != 0)
        {
            ARM_UC_MFST_SET_ERROR(err, MFST_ERR_DER_FORMAT);
        }
    }
    return err;
}
/** @brief Validate that this manifest is a supported version
 */
static arm_uc_error_t validateManifestVersion(arm_uc_buffer_t* buffer)
{
    uint32_t val = 0;
    // Read the manifest version
    arm_uc_error_t err = ARM_UC_mmGetVersion(buffer, &val);
    if (err.code == MFST_ERR_NONE)
    {
        // Verify the manifest version
        if (val != MANIFEST_SUPPORTED_VERSION) {
            ARM_UC_MFST_SET_ERROR(err, MFST_ERR_VERSION);
        }
    }
    return err;
}

/** @brief Validate the manifest size
 */
static arm_uc_error_t validateManifestSize(arm_uc_buffer_t* buffer)
{
    arm_uc_error_t err = {MFST_ERR_NONE};
    arm_uc_buffer_t val = {0};

    // Get the manifest inner part
    err = ARM_UC_mmDERSignedResourceGetSingleValue(buffer, ARM_UC_MM_DER_MFST, &val);
    if (err.error == ERR_NONE)
    {
        // Make sure that the manifest does not overrun.
        uintptr_t bufend = (uintptr_t)buffer->ptr + buffer->size;
        uintptr_t valend = (uintptr_t)val.ptr + val.size;
        if (bufend < valend)
        {
            ARM_UC_MFST_SET_ERROR(err, MFST_ERR_SIZE);
        }
        // TODO: There should be a minimum size for the manifest too.
    }
    return err;
}

/** @brief Validate the crypto mode
 *  @details The manifest must contain a cryptographic mode identifier. Only a small number of modes are supported. If
 *           the manifest is to be processed, then one of these modes must be supported.
 *
 *           While the manifest format supports OID cryptographic mode identifiers, these are not currently supported in
 *           the update client.
 */
static arm_uc_error_t validateCryptoMode(arm_uc_buffer_t* buffer, arm_uc_mm_crypto_flags_t* flags)
{
    uint32_t cryptoMode = 1U; // default SHA256 and ECC
    arm_uc_error_t err = ARM_UC_mmGetCryptoMode(buffer, &cryptoMode);
    if (err.error == ERR_NONE)
    {
        if (cryptoMode <= MFST_CRYPT_UNINIT || MFST_CRYPT_MAX <= cryptoMode)
        {
            ARM_UC_MFST_SET_ERROR(err, MFST_ERR_CRYPTO_MODE);
        }
        else
        {
            *flags = ARM_UC_mmGetCryptoFlags(cryptoMode);
        }
    }
    return err;
}

// Validate that the manifest applies to this device
static arm_uc_error_t validateFirmwareApplicability(arm_uc_buffer_t* buffer)
{
    arm_uc_buffer_t vendor_guid = {0};
    arm_uc_buffer_t class_guid  = {0};
    arm_uc_buffer_t device_guid = {0};

    arm_uc_error_t err = {MFST_ERR_NONE};
    if (err.code == MFST_ERR_NONE)
        err = ARM_UC_mmGetVendorGuid(buffer, &vendor_guid);
    if (err.code == MFST_ERR_NONE)
        err = ARM_UC_mmGetClassGuid(buffer, &class_guid);
    if (err.code == MFST_ERR_NONE)
        err = ARM_UC_mmGetDeviceGuid(buffer, &device_guid);
    if (err.code == MFST_ERR_NONE)
        err = pal_deviceIdentityCheck(
            (vendor_guid.size != 0UL ? &vendor_guid : NULL),
            (class_guid.size != 0UL ? &class_guid : NULL),
            (device_guid.size != 0UL ? &device_guid : NULL)
        );
    return err;
}
/*
 * DOT Setup
 * DOT: digraph {
 */

/* @brief Idle state
 * @details The idle state generates no events and causes no state transitions. It only moves to a new state when the
 *          `ARM_UC_MM_EVENT_BEGIN` event is received.
 * DOT States:
 * DOT:    Idle
 * DOT:    Idle -> Begin [label="[event == BEGIN]"]
 */
static arm_uc_error_t state_idle(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    arm_uc_error_t err = {MFST_ERR_NONE};
    if (*event == ARM_UC_MM_EVENT_BEGIN)
    {
        ctx->state = ARM_UC_MM_INS_STATE_BEGIN;
    }
    return err;
}
/* @brief Begin state
 * @details This is an empty placeholder state that is used as a state transition target for Idle. This allows
 *          modifications to the FSM flow without modifying Idle.
 * DOT States:
 * DOT:    Begin
 * DOT:    Begin -> VerifyBasicParameters
 */
static arm_uc_error_t state_begin(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    arm_uc_error_t err = {MFST_ERR_NONE};
    ctx->state = ARM_UC_MM_INS_STATE_VERIFY_BASIC_PARAMS;
    return err;
}
/* @brief Verify critical pre-security parameters
 * @details Some parameters must be verified before security validation. These parameters are critical to either finding
 *          or validating the security parameters themselves. The parameters validated are:
 *
 * * The resource size             (This is a precursor to security validation)
 * * The resource is a manifest    (A non-manifest will not be accepted)
 * * The manifest version          (This is required in order to know how to parse the manifest)
 * * The manifest encryption mode  (This is required in order to know how to validate the signature)
 *
 * DOT States:
 * DOT:    VerifyBasicParameters
 * DOT:    VerifyBasicParameters -> VerifyHash
 * DOT:    VerifyBasicParameters -> VerifyFail [label="[Basic Parameters invalid]"]
 */
static arm_uc_error_t state_verifyBasicParameters(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    arm_uc_error_t err = {MFST_ERR_NONE};

    if (err.error == ERR_NONE)
    {
        err = validateResourceType(&ctx->manifest);
    }
    if (err.error == ERR_NONE)
    {
        err = validateManifestSize(&ctx->manifest);
    }
    if (err.error == ERR_NONE)
    {
        err = validateManifestVersion(&ctx->manifest);
    }
    if (err.error == ERR_NONE)
    {
        err = validateCryptoMode(&ctx->manifest, &ctx->cryptoMode);
    }
    // Set the state based on error condition
    if (err.error == ERR_NONE)
    {
        ctx->state = ARM_UC_MM_INS_STATE_HASH_VERIFY;
    }
    else
    {
        ctx->state = ARM_UC_MM_INS_STATE_VERIFY_FAIL;
    }
    return err;
}
/** @brief   Verify the manifest hash
 *  @details Manifest hash verification happens in a single state. This is because hash verification is currently
 *           considered to be a blocking operation. If an asynchronous hash accelerator is used, this will need to be
 *           modified to use two states to handle hash initiation and waiting for completion.
 *
 * DOT States:
 * DOT:     VerifyHash
 * DOT:     VerifyHash -> VerifySignatureLoopStart
 * DOT:     VerifyHash -> VerifyFail [label="[Hash invalid]"]
 */
static arm_uc_error_t state_verifyHash(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    arm_uc_error_t err = ARM_UC_mmValidateManifestHash(&ctx->manifest);
    if (err.error == ERR_NONE)
    {
        // If the cryptoMode specifies either ecc or rsa, then we can validate that.
        if(ctx->cryptoMode.ecc || ctx->cryptoMode.rsa)
        {
            ctx->state = ARM_UC_MM_INS_STATE_VERIFY_SIG_LOOP;
        }
        else
        {
            // Unsigned manifests are not supported at this time, so they count as a failure.
            ARM_UC_MFST_SET_ERROR(err, MFST_ERR_INVALID_SIGNATURE);
            ctx->state = ARM_UC_MM_INS_STATE_VERIFY_FAIL;
        }
    }
    else
    {
        ARM_UC_MFST_SET_ERROR(err, MFST_ERR_HASH);
        ctx->state = ARM_UC_MM_INS_STATE_VERIFY_FAIL;
    }
    return err;
}
/** @brief Start the signature verification loop.
 *  @details This state provides initialization for the signature verification loop.
 *           The outer loop is tracked by `loopCounters[0]` and represents the signature index.
 *
 * DOT States:
 * DOT:     VerifySignatureLoopStart
 * DOT:     VerifySignatureLoopStart -> VerifySignatureStart
 */
static arm_uc_error_t state_verifySignatureLoopStart(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    arm_uc_error_t err = {MFST_ERR_NONE};
    // Set the exterior loop counter
    ctx->loopCounters[0] = 0;
    ctx->state = ARM_UC_MM_INS_STATE_VERIFY_SIG_START;
    return err;
}
/** @brief   Begin verifying the signature.
 *  @details This calls the ARM_UC_mmValidateSignature setup function, but does not start the signature verification
 *           state machine. `ARM_UC_mmValidateSignature` attempts to read a signature at the index specified by the
 *           outer loop counter (`loopCounters[0]`). If it fails, it assumes that all signatures have been processed.
 *           If at least one signature has been processed, then continue with validation, but a minimum of one signature
 *           is required for validation.
 *
 * DOT States:
 * DOT:     VerifySignatureStart
 * DOT:     VerifySignatureStart -> VerifySignature
 * DOT:     VerifySignatureStart -> VerifyParameters [label="[No More Signatures]"]
 */
static arm_uc_error_t state_verifySignatureStart(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    // start the signature verification
    arm_uc_error_t err = ARM_UC_mmValidateSignature(&ctx->signatureContext,
                                                    ARM_UC_mmCallbackFSMEntry,
                                                    &ctx->manifest,
                                                    &ctx->certificateStorage,
                                                    ctx->loopCounters[0]);
    if (err.error == ERR_NONE)
    {
        ctx->state = ARM_UC_MM_INS_STATE_VERIFY_SIG_WAIT;
        *event = ARM_UC_MM_RC_NONE;
        ARM_UC_MFST_SET_ERROR(err, MFST_ERR_PENDING);
    }
    // If there are no more signatures and at least one signature was validated
    if (err.code == ARM_UC_DP_ERR_NO_MORE_ELEMENTS)
    {
        if (ctx->loopCounters[0] >= 1)
        {
            // Signature validation done. Move on to parameter validation.
            ctx->state = ARM_UC_MM_INS_STATE_VERIFY_PARAMS;
            ARM_UC_MFST_SET_ERROR(err, MFST_ERR_NONE);
        }
        else
        {
            // WARNING: If the fingerprint is empty, MFST_ERR_INVALID_SIGNATURE is returned.
            // At least one signature is required.
            ARM_UC_MFST_SET_ERROR(err, MFST_ERR_INVALID_SIGNATURE);
            ctx->state = ARM_UC_MM_INS_STATE_VERIFY_FAIL;
        }
    }
    return err;
}
/** @brief   Wait for signature validation to complete.
 *  @details Calls the `ARM_UC_mmValidateSignature` state machine and collects exit status. When signature validation is
 *           complete, the return value will be `MFST_ERR_NONE`.
 *
 * DOT States:
 * DOT:     VerifySignature
 * DOT:     VerifySignature -> VerifyFail [label="[Signature invalid]"]
 * DOT:     VerifySignature -> VerifySignatureStart
 */
static arm_uc_error_t state_verifySignature(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    arm_uc_error_t err = {MFST_ERR_NONE};
    // Wait for the signature verification to end.
    // If the signature validation ended
    if (*event == ARM_UC_MM_RC_DONE)
    {
        // Increment the loop counter
        ctx->loopCounters[0] += 1;
        // Return to the beginning of the loop.
        ctx->state = ARM_UC_MM_INS_STATE_VERIFY_SIG_START;
    }
    else if (*event == ARM_UC_MM_RC_ERROR)
    {
        err = ctx->signatureContext.storedError;
    }
    else
    {
        ARM_UC_MFST_SET_ERROR(err,  MFST_ERR_BAD_EVENT);
    }
    return err;
}
/** @brief   Validates remaining parsable parameters
 *  @details This currently means only the firmware applicability, as identified by UUID. Several additiional parameters
 *           could be validated:
 *
 *           * Storage identifier
 *           * Payload type identifier
 *           * URI validation in payload reference
 *           * Valid size of payload hash
 *           * nonce size, non-zero
 *           * Valid From, Valid To
 *           * timestamp
 *           * Encryption info
 *
 *
 * DOT States:
 * DOT:     VerifyParameters
 * DOT:     VerifyParameters -> VerifyApplication
 * DOT:     VerifyParameters -> VerifyFail [label="[Parameters invalid]"]
 */
static arm_uc_error_t state_verifyParameters(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    arm_uc_error_t err = validateFirmwareApplicability(&ctx->manifest);
    if (err.error == ERR_NONE)
    {
        ctx->state = ARM_UC_MM_INS_STATE_VERIFY_TS_START;
    }
    return err;
}
/** @brief   Initiate timestamp verification.
 *  @details This starts the process of loading the active timestamp. This may be a non-blocking operation.
 *
 * DOT States:
 * DOT:     VerifyTimestampStart
 * DOT:     VerifyTimestampStart -> VerifyTimestamp
 */
static arm_uc_error_t state_verifyTimestampStart(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    // Since this is a root manifest, extract maximum stored timestamp
    arm_uc_error_t err = getLatestManifestTimestamp(&ctx->max_ts, NULL);
    if (err.error == ERR_NONE)
    {
        ctx->state = ARM_UC_MM_INS_STATE_VERIFY_TS;
        *event = ARM_UC_MM_EVENT_BEGIN;
    }
    return err;
}
/** @brief   Waits for the active timestamp to be loaded.
 *  @details Once the active timestamp has been loaded, this validates the inserted manifest timestamp.
 *
 * DOT States:
 * DOT:    VerifyTimestamp
 * DOT:    VerifyTimestamp -> VerifyApplication
 * DOT:    VerifyTimestamp -> VerifyFail [label="[Timestamp too old]"]
 */
static arm_uc_error_t state_verifyTimestamp(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    arm_uc_error_t err = getLatestManifestTimestampFSM(*event);
    if (err.error == ERR_NONE)
    {
        err = ARM_UC_mmGetTimestamp(&ctx->manifest, &ctx->current_ts);
    }
    if (err.error == ERR_NONE)
    {
#if MANIFEST_ROLLBACK_PROTECTION
        // Validate the timestamp for rollback protection.
        if (arm_uc_mmContext.max_ts >= arm_uc_mmContext.current_ts)
        {
            ARM_UC_MFST_SET_ERROR(err, MFST_ERR_ROLLBACK);
        }
        else
#endif
        {
            ARM_UC_MFST_SET_ERROR(err, MFST_ERR_NONE);
            ctx->state = ARM_UC_MM_INS_STATE_VERIFY_APP;
        }
    }
    return err;
}

/** @brief   Calls out to a handler provided by the application.
 *  @details Currently unimplemented.
 *
 * DOT States:
 * DOT:     VerifyApplication
 * DOT:     VerifyApplication -> VerifyDone
 * DOT:     VerifyApplication -> VerifyFail [label="[App denied]"]
 */
static arm_uc_error_t state_verifyApplication(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    arm_uc_error_t err = {MFST_ERR_NONE};
    if (err.error == ERR_NONE)
    {
        ctx->state = ARM_UC_MM_INS_STATE_VERIFY_DONE;
    }
    return err;
}
/** @brief   Verification has failed.
 *  @details This state will never be entered. This is for documentation purposes only. The state machine exits when an
 *           error is detected, so this state cannot be entered. The hub will be notified via the state machine's error
 *           handler.
 *
 * DOT States:
 * DOT:     VerifyFail
 * DOT:     VerifyFail -> Idle
 */


/** @brief   Verification has completed successfully.
 *  @details This is a placeholder state that may be useful if more operations must be performed after verification,
 *           for example, storage of the manifest.
 *
 * DOT States:
 * DOT:     VerifyDone
 * DOT:     VerifyDone -> AlertHub
 */
static arm_uc_error_t state_verifyDone(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    arm_uc_error_t err = {MFST_ERR_NONE};
    if (err.error == ERR_NONE)
    {
        ctx->state = ARM_UC_MM_INS_STATE_ALERT;
    }
    return err;
}
/** @brief   Alert the hub that insert has finished processing the manifest.
 *  @details Queues a callback to the hub, that reports completion.
 *
 * DOT States:
 * DOT:     AlertHub
 * DOT:     AlertHub -> Idle
 */
static arm_uc_error_t state_alertHub(struct arm_uc_mmInsertContext_t* ctx, uint32_t* event)
{
    arm_uc_error_t err = {MFST_ERR_NONE};
    return err;
}
/**
 * DOT Teardown
 * DOT: }
 */

arm_uc_error_t ARM_UC_mmInsertFSM(uint32_t event)
{
    arm_uc_error_t err = {MFST_ERR_NONE};
    struct arm_uc_mmInsertContext_t* ctx;
    if (arm_uc_mmPersistentContext.ctx == NULL || *arm_uc_mmPersistentContext.ctx == NULL)
    {
        return (arm_uc_error_t){MFST_ERR_NULL_PTR};
    }
    ctx = &(*arm_uc_mmPersistentContext.ctx)->insert;

    uint32_t oldState;
#if ARM_UC_MM_ENABLE_INSERT_TEST_VECTORS
    uint32_t oldEvent;
#endif
    ARM_UC_MM_DEBUG_LOG(ARM_UC_MM_DEBUG_LOG_LEVEL_STATES, "> %s (%u)\n", __PRETTY_FUNCTION__, (unsigned)event);
    do {
        // Preserve the old state to check for state transitions
        oldState = ctx->state;

#if ARM_UC_MM_ENABLE_INSERT_TEST_VECTORS
        // Preserve the old event for testing
        oldEvent = event;
#endif
        // Reset error logging
        arm_uc_mmPersistentContext.errorFile = NULL;
        arm_uc_mmPersistentContext.errorLine = 0;

        ARM_UC_MM_DEBUG_LOG(ARM_UC_MM_DEBUG_LOG_LEVEL_STATES, "+ %s state: %s(%u)\n", __PRETTY_FUNCTION__,
            ARM_UC_mmInsertState2Str(ctx->state), (unsigned)ctx->state);\
        switch (ctx->state)
        {
            case ARM_UC_MM_INS_STATE_IDLE:
                err = state_idle(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_BEGIN:
                err = state_begin(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_VERIFY_BASIC_PARAMS:
                err = state_verifyBasicParameters(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_HASH_VERIFY:
                err = state_verifyHash(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_VERIFY_SIG_LOOP:
                err = state_verifySignatureLoopStart(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_VERIFY_SIG_START:
                err =  state_verifySignatureStart(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_VERIFY_SIG_WAIT:
                err = state_verifySignature(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_VERIFY_PARAMS:
                err = state_verifyParameters(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_VERIFY_TS_START:
                err = state_verifyTimestampStart(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_VERIFY_TS:
                err = state_verifyTimestamp(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_VERIFY_APP:
                err = state_verifyApplication(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_VERIFY_DONE:
                err = state_verifyDone(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_ALERT:
                err = state_alertHub(ctx, &event);
                break;
            case ARM_UC_MM_INS_STATE_INVALID:
            default:
                err = (arm_uc_error_t){MFST_ERR_INVALID_STATE};
                break;
        }
#if ARM_UC_MM_ENABLE_INSERT_TEST_VECTORS
        if (arm_uc_mmPersistentContext.testHook)
        {
            arm_uc_mmPersistentContext.testHook("insert", *arm_uc_mmPersistentContext.ctx, oldState, oldEvent, err);
        }
#endif
    } while (err.code == MFST_ERR_NONE && oldState != ctx->state);
    ARM_UC_MM_DEBUG_LOG(ARM_UC_MM_DEBUG_LOG_LEVEL_STATES, "< %s %c%c:%hu (%s)\n", __PRETTY_FUNCTION__, err.modulecc[0], err.modulecc[1], err.error, ARM_UC_err2Str(err));
    return err;
}
