/*++

Copyright (C) 2009-2018 Intel Corporation

SPDX-License-Identifier: BSD-2-Clause

File Name:
    FWUpdLclApp.c

Abstract:
    FW Update Sample Code demonstrating usage of FW Update Library - EFI only.

--*/

#include "FWUpdateLib.h"

// for error codes.
// for error messages, use function GetErrorString() defined in errorlist.c - should be added to the compilation.
// this increases the size of the app. Do not add if error messages are not needed.
#include "errorlist.h"

#include <Protocol/SimpleFileSystem.h>
#include <Protocol/LoadedImage.h>
#include <Guid/FileInfo.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h> // for gBS
#include <UefiLib.h> // for Print

#ifdef DEBUG
#define DebugPrint Print
#else
#define DebugPrint(...)
#endif

#define CMD_LINE_STATUS_UPDATE_1        0
#define CMD_LINE_STATUS_UPDATE_2        1
#define CMD_LINE_STATUS_UPDATE_3        2
#define CMD_LINE_STATUS_UPDATE_4        3

/**
* @brief Print error message according to error code.
*        GetErrorString() is defined in errorlist.c.
*        If error messages are needed, errorlist.c should be added to the compilation.
*
* @param[in]  errorCode  Error code.
*/
static void errorMessage(const UINT32 errorCode)
{
    // use %a for ascii string. use %s for Unicode string.
    Print(L"Error %u: %a\n", errorCode, GetErrorString(errorCode));
}

/**
* @brief A callback function that reports the progress of sending the update image buffer
*        to FW (not the progress of the update itself).
*
* @param[in]  bytesSentToFw         The number of bytes of the buffer, that were already sent to FW.
* @param[in]  totalBytesToSendToFw  The total number of bytes of the buffer.
*/
static void displaySendStatus(UINT32 bytesSentToFw, UINT32 totalBytesToSendToFw)
{
    UINT32 value = bytesSentToFw * 100 / totalBytesToSendToFw;

    if (value != 100)
    {
        Print(L"Sending the update image to FW for verification:  [ %u%% ]\r", value);
    }
    else
    {
        Print(L"Sending the update image to FW for verification:  [ COMPLETE ]\n");
    }
}

/**
* @brief Return the command line of the application, as CHAR16 *, that ends with '\0'.
*
* @param[in]  imageHandle  Handle of the application.
* @param[out] commandLine  CHAR16 * buffer containing the string that was used to run the application.
*                          Starts with the application name. Ends with '\0'.
*                          Caller should pass reference to CHAR16 *commandLine, should not allocate memory, should not free memory.
*
* @return SUCCESS  If succeeded. Error code otherwise.
*/
static UINT32 getCommandLine(IN EFI_HANDLE imageHandle, OUT CHAR16 **commandLine)
{
    EFI_STATUS efiStatus;
    UINT32 status = SUCCESS;
    EFI_LOADED_IMAGE *image = NULL;
    
    if (commandLine == NULL)
    {
        status = INTERNAL_ERROR;
        goto End;
    }

    *commandLine = NULL;

    efiStatus = gBS->HandleProtocol(imageHandle, &gEfiLoadedImageProtocolGuid, (VOID *)&image);
    if (EFI_ERROR(efiStatus))
    {
        status = CLI_ERROR_INVALID_COMMAND_ARGUMENT;
        goto End;
    }

    if (image->LoadOptionsSize == 0 || // if command line size is 0
        image->LoadOptions == NULL) // no command line
    {
        status = CLI_ERROR_INVALID_COMMAND_ARGUMENT;
        goto End;
    }

    // command line contains CHAR16 - each is 2 bytes.
    // make sure the size is even (2 bytes for each char), by checking that the LSB bit is 0.
    if ((image->LoadOptionsSize & 1) != 0)
    {
        status = CLI_ERROR_INVALID_COMMAND_ARGUMENT;
        goto End;
    }

    *commandLine = image->LoadOptions;

    // make sure that the last 2 bytes are '\0'
    // LoadOptionsSize is the number of bytes. LoadOptionsSize / 2 is the number of CHAR16.
    if ( (*commandLine)[image->LoadOptionsSize / 2 - 1] != '\0')
    {
        status = CLI_ERROR_INVALID_COMMAND_ARGUMENT;
        goto End;
    }

End:
    return status;
}

#define WHITESPACE(c) (c == ' ' || c == '\t')

/**
* @brief Get the next argument in a command line.
*        If there are no more arguments, size will be 0.
*
* @param[in,out]  currBuffer    Input: Pointer into the current location in the command line buffer, which ends with '\0'.
*                               Output: Pointer after the location of the argument in the command line buffer.
*                               Caller should pass reference to CHAR16 *currBuffer, should not allocate memory, should not free memory.
* @param[out]     arg           Pointer to argument in the command line buffer.
*                               Caller should pass reference to CHAR16 *arg, should not allocate memory, should not free memory.
* @param[out]     size          Size of the argument in CHAR16. 0 if there are no more arguments. Caller allocated.
*
* @return SUCCESS  If succeeded. Error code otherwise.
*/
static UINT32 getNextArg(IN OUT CHAR16 **currBuffer,
                         OUT    CHAR16 **arg,
                         OUT    UINT32 *size)
{
    UINT32 status = SUCCESS;

    if (currBuffer == NULL || arg == NULL || size == NULL)
    {
        status = INTERNAL_ERROR;
        goto End;
    }

    *arg = NULL;
    *size = 0;

    // skip white space at the beginning
    while (WHITESPACE(**currBuffer))
    {
        (*currBuffer)++;
    }

    *arg = *currBuffer;

    while (**currBuffer != '\0' && // did not reach the end of the command line
           !WHITESPACE(**currBuffer)) // arg has more characters
    {
        (*currBuffer)++;
        (*size)++;
    }

End:
    return status;
}

/**
* @brief Parse the command line.
*
* @param[in]  imageHandle        Handle for application.
* @param[out] optionAllowSv      Allow same version for Full Update. Caller allocated.
* @param[out] optionPartialIshc  Partial Update of ISHC. Caller allocated.
* @param[out] optionIshcFiles    Handle ISHC files. Caller allocated.
* @param[out] optionIshcVersion  Display ISHC version and more. Caller allocated.
* @param[out] fileName           File Name. Should be relative to the root directory.
*                                Will point to relevant arg in command line. Since it is the last argument it will have '\0' at the end.
*                                If arguments order will be changed in the future, '\0' should be added at the end.
*                                Caller should pass reference to CHAR16 *fileName, should not allocate memory, should not free memory.
*
* @return SUCCESS  If succeeded. Error code otherwise.
*/
static UINT32 parseCommandLine(IN  EFI_HANDLE imageHandle,
                               OUT BOOLEAN *optionAllowSv,
                               OUT BOOLEAN *optionPartialIshc,
                               OUT BOOLEAN *optionIshcFiles,
                               OUT BOOLEAN *optionIshcVersion,
                               OUT CHAR16 **fileName)
{
    UINT32 status;
    CHAR16 *commandLine = NULL;
    CHAR16 *currBuffer = NULL;
    CHAR16 *arg = NULL;
    UINT32 size = 0;

    if (optionAllowSv == NULL || optionPartialIshc == NULL ||
        optionIshcFiles == NULL || optionIshcVersion == NULL ||
        fileName == NULL)
    {
        status = INTERNAL_ERROR;
        goto End;
    }

    *optionAllowSv = FALSE;
    *optionPartialIshc = FALSE;
    *optionIshcFiles = FALSE;
    *optionIshcVersion = FALSE;
    *fileName = NULL;

    // get the command line string from the handle of the application
    status = getCommandLine(imageHandle, &commandLine);
    if (status != SUCCESS)
    {
        goto End;
    }

    currBuffer = commandLine;

    // skip the application name
    status = getNextArg(&currBuffer, &arg, &size);
    if (status != SUCCESS)
    {
        goto End;
    }

    if (size == 0)
    {
        status = CLI_ERROR_INVALID_COMMAND_ARGUMENT;
        goto End;
    }

    // get first argument
    status = getNextArg(&currBuffer, &arg, &size);
    if (status != SUCCESS)
    {
        goto End;
    }
    if (size < 1)
    {
        status = CLI_ERROR_INVALID_COMMAND_ARGUMENT;
        goto End;
    }

    if (arg[0] == '/' || arg[0] == '-') // if option argument - starts with / or -
    {
        if (size != 2) // option argument size must be 2 ("/s" for example)
        {
            status = CLI_ERROR_INVALID_COMMAND_ARGUMENT;
            goto End;
        }

        switch (arg[1])
        {
        case 's':
            *optionAllowSv = TRUE;
            break;
        case 'i':
            *optionPartialIshc = TRUE;
            break;
        case 'd':
            *optionIshcFiles = TRUE;
            break;
        case 'g':
            *optionIshcVersion = TRUE;
            break;
        default:
            status = CLI_ERROR_INVALID_COMMAND_ARGUMENT;
            goto End;
        }

        // get second argument: file name
        status = getNextArg(&currBuffer, &arg, &size);
        if (status != SUCCESS)
        {
            goto End;
        }
        if (size < 1)
        {
            status = CLI_ERROR_INVALID_COMMAND_ARGUMENT;
            goto End;
        }
    }

    // get file name (first or second argument)
    *fileName = arg;

    // since fileName is the last argument it will have '\0' at the end.
    // if arguments order will be changed in the future, '\0' should be added at the end.

    // make sure there are no more arguments
    status = getNextArg(&currBuffer, &arg, &size);
    if (status != SUCCESS)
    {
        goto End;
    }
    if (size != 0)
    {
        status = CLI_ERROR_INVALID_COMMAND_ARGUMENT;
        goto End;
    }

End:
    if (status != SUCCESS)
    {
        Print(L"Usage: FwUpdLcl.efi /s /i /d /g <file>\n");
    }
    return status;
}

/**
* @brief Read file into buffer.
*
* @param[in]  imageHandle   Handle of the application image.
* @param[in]  fileName      File name to read. Should be relative to the root directory.
* @param[out] buffer        Buffer to read the file into. Allocated by the function. Caller should free memory by FreePool().
* @param[out] bufferLength  Length of the buffer.
*
* @return SUCCESS  If succeeded. Error code otherwise.
*/
static UINT32 copyFileToBuffer(IN  EFI_HANDLE imageHandle,
                               IN  CHAR16 *fileName,
                               OUT UINT8 **buffer,
                               OUT UINTN *bufferLength)
{
    EFI_STATUS efiStatus;
    UINT32 status = SUCCESS;
    EFI_LOADED_IMAGE *loadedImage = NULL;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fsProtocol = NULL;
    EFI_FILE_PROTOCOL *rootFs = NULL;
    EFI_FILE_PROTOCOL *fileHandle = NULL;
    UINTN infoBufferSize = 0;
    EFI_FILE_INFO *fileInfo = NULL;
    UINTN bytesRead = 0;

    if (buffer == NULL || bufferLength == NULL)
    {
        status = INTERNAL_ERROR;
        goto End;
    }

    *buffer = NULL;
    *bufferLength = 0;

    efiStatus = gBS->HandleProtocol(imageHandle, &gEfiLoadedImageProtocolGuid, (VOID **)&loadedImage);
    if (EFI_ERROR(efiStatus))
    {
        status = FILEIO_ERROR_OPEN_FILE;
        goto End;
    }

    efiStatus = gBS->HandleProtocol(loadedImage->DeviceHandle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&fsProtocol);
    if (EFI_ERROR(efiStatus))
    {
        status = FILEIO_ERROR_OPEN_FILE;
        goto End;
    }

    // "The OpenVolume() function opens a volume, and returns a file handle to the volume’s root
    // directory. This handle is used to perform all other file I/O operations. The volume remains open
    // until all the file handles to it are closed."
    efiStatus = fsProtocol->OpenVolume(fsProtocol, &rootFs);
    if (EFI_ERROR(efiStatus))
    {
        status = FILEIO_ERROR_OPEN_FILE;
        goto End;
    }

    // "On requesting the file system protocol on a device, the caller gets the EFI_FILE_PROTOCOL to the
    // volume. This interface is used to open the root directory of the file system when needed. The caller
    // must Close() the file handle to the root directory, and any other opened file handles before
    // exiting."

    // "Opens a new file relative to the source file’s location."
    // therefore fileName should be relative to the root directory.
    efiStatus = rootFs->Open(rootFs,
                             &fileHandle,
                             fileName,
                             EFI_FILE_MODE_READ,
                             0);
    if (EFI_ERROR(efiStatus))
    {
        Print(L"Unable to Open File. EFI_STATUS: %r.\n", efiStatus);
        status = FILEIO_ERROR_OPEN_FILE;
        goto End;
    }

    // "The GetInfo() function returns information of type gEfiFileInfoGuid for the requested file.
    // If the buffer is not large enough to fit the requested structure, EFI_BUFFER_TOO_SMALL is returned and
    // the BufferSize is set to the size of buffer that is required to make the request."
    // pass infoBufferSize = 0 to get the size of the buffer to EFI_FILE_INFO, to allocate fileInfo.
    efiStatus = fileHandle->GetInfo(fileHandle, &gEfiFileInfoGuid, &infoBufferSize, NULL);
    if (EFI_ERROR(efiStatus) && efiStatus != EFI_BUFFER_TOO_SMALL) // EFI_BUFFER_TOO_SMALL is legit when passing infoBufferSize = 0
    {
        status = FILE_READ_ERROR;
        goto End;
    }

    fileInfo = (EFI_FILE_INFO *)AllocatePool(infoBufferSize);
    if (fileInfo == NULL)
    {
        DebugPrint(L"Cannot allocate memory for fileInfo.\n");
        status = ERROR_MEMORY_ALLOC;
        goto End;
    }

    efiStatus = fileHandle->GetInfo(fileHandle, &gEfiFileInfoGuid, &infoBufferSize, fileInfo);
    if (EFI_ERROR(efiStatus))
    {
        status = FILE_READ_ERROR;
        goto End;
    }

    *bufferLength = (UINTN)fileInfo->FileSize;

    DebugPrint(L"bufferLength: %u.\n", *bufferLength);

    if (*bufferLength == 0)
    {
        DebugPrint(L"Empty file.\n");
        status = FILE_READ_ERROR;
        goto End;
    }

    *buffer = (UINT8 *)AllocatePool(*bufferLength);
    if (*buffer == NULL)
    {
        DebugPrint(L"Cannot allocate memory for buffer.\n");
        status = ERROR_MEMORY_ALLOC;
        goto End;
    }

    bytesRead = *bufferLength;
    efiStatus = fileHandle->Read(fileHandle, &bytesRead, (VOID *)*buffer);
    if (EFI_ERROR(efiStatus))
    {
        Print(L"Cannot read file into memory. EFI_STATUS: %r.\n", efiStatus);
        status =  FILE_READ_ERROR;
        goto End;
    }
    if (bytesRead != *bufferLength)
    {
        status = FILE_READ_ERROR;
        goto End;
    }

End:
    if (SUCCESS != status && NULL != buffer)
    {
        FreePool(*buffer);
        *buffer = NULL; // OUT parameter should be set to NULL in case of error
    }
	if (NULL != fileInfo)
    {
		FreePool(fileInfo);
	}
    if (fileHandle)
    {
        efiStatus = fileHandle->Close(fileHandle);
        fileHandle = NULL;
    }
    if (rootFs)
    {
        efiStatus = rootFs->Close(rootFs);
        rootFs = NULL;
    }

    return status;
}

/**
* @brief Check if the update image has the same version as the flash image.
*
* @param[in]  buffer         Buffer of Update Image.
* @param[in]  bufferLength   Length of the buffer in bytes.
* @param[out] isSameVersion  TRUE if same version, FALSE if not. Caller allocated.
*
* @return SUCCESS  If succeeded. Error code otherwise.
*/
static UINT32 isUpdateToSameVersion(IN  UINT8 *buffer,
                                    IN  UINT32 bufferLength,
                                    OUT BOOLEAN *isSameVersion)
{
    UINT32 status;
    UINT16 flashMajor = 0;
    UINT16 flashMinor = 0;
    UINT16 flashHotfix = 0;
    UINT16 flashBuild = 0;
    UINT16 bufferMajor = 0;
    UINT16 bufferMinor = 0;
    UINT16 bufferHotfix = 0;
    UINT16 bufferBuild = 0;

    if (buffer == NULL || bufferLength == 0 || isSameVersion == NULL)
    {
        status = INTERNAL_ERROR;
        goto End;
    }

    *isSameVersion = FALSE;

    status = FwuPartitionVersionFromFlash(FPT_PARTITION_NAME_RBEP, &flashMajor, &flashMinor, &flashHotfix, &flashBuild);
    if (status != SUCCESS)
    {
        goto End;
    }

    status = FwuPartitionVersionFromBuffer(buffer, bufferLength, FPT_PARTITION_NAME_RBEP, &bufferMajor, &bufferMinor, &bufferHotfix, &bufferBuild);
    if (status != SUCCESS)
    {
        goto End;
    }

    if (flashMajor == bufferMajor &&
        flashMinor == bufferMinor &&
        flashHotfix == bufferHotfix &&
        flashBuild == bufferBuild)
    {
        *isSameVersion = TRUE;
    }
End:
    return status;
}

// Entry Point function of the application
EFI_STATUS InitializeFwUpdLclApplication(IN EFI_HANDLE imageHandle,
                                         IN EFI_SYSTEM_TABLE *systemTable)
{
    UINT32 status;
    BOOLEAN optionAllowSv = FALSE;
    BOOLEAN optionPartialIshc = FALSE;
    BOOLEAN optionIshcFiles = FALSE;
    BOOLEAN optionIshcVersion = FALSE;
    CHAR16 *fileName = NULL;
    UINT8 *buffer = NULL;
    UINTN bufferLength = 0;
    UINT16 major = 0;
    UINT16 minor = 0;
    UINT16 hotfix = 0;
    UINT16 build = 0;
    UINT32 vendorID = 0;
    _UUID oemId;
    UINT8 pdtVersion = 0;
    UINT8 vdvVersion = 0;
    UINT16 enabledState = FALSE;
    BOOLEAN isSameVersion = FALSE;
    UINT32 flashFwType = FWU_FW_TYPE_INVALID;
    UINT32 updateFwType = FWU_FW_TYPE_INVALID;
    UINT32 flashPchSku = FWU_PCH_SKU_INVALID;
    UINT32 updatePchSku = FWU_PCH_SKU_INVALID;
	UINT32 flashProductSegment = 0;
	UINT32 updateProductSegment = 0;
    UINT32 index = 0;
    UINT32 indexMod = 0;
    CHAR8 symbol;
    BOOLEAN inProgress = FALSE;
    UINT32 currentPercent = 0;
    UINT32 fwUpdateStatus = 0;
    UINT32 neededResetType = MFT_PART_INFO_EXT_UPDATE_ACTION_NONE;
    UINT32 timer = 0;
    UINT32 previousPercent = 0;
    
    Print(L"\nIntel (R) FW Update Sample Application\n\n");

    // determine the command line arguments
    status = parseCommandLine(imageHandle, &optionAllowSv, &optionPartialIshc, &optionIshcFiles, &optionIshcVersion, &fileName);
    if (SUCCESS != status)
    {
        DebugPrint(L"Unable to process command line. error: %u.\n", status);
        goto End;
    }

    // load file into memory buffer
    Print(L"Loading file into memory...\n\n");
    status = copyFileToBuffer(imageHandle, fileName, &buffer, &bufferLength);
    if (SUCCESS != status)
    {
        goto End;
    }

    // /g option: display ISHC version from flash, ISHC version from file, ISHC vendor ID from flash, OEM ID from flash.
    if (optionIshcVersion)
    {
        status = FwuPartitionVersionFromFlash(FPT_PARTITION_NAME_ISHC, &major, &minor, &hotfix, &build);
        if (status != SUCCESS)
        {
            Print(L"FwuPartitionVersionFromFlash error: %u.\n", status);
            goto End;
        }
        Print(L"ISHC partition version from flash: %u.%u.%u.%u.\n\n", major, minor, hotfix, build);

        status = FwuPartitionVersionFromBuffer(buffer, (UINT32)bufferLength, FPT_PARTITION_NAME_ISHC, &major, &minor, &hotfix, &build);
        if (status != SUCCESS)
        {
            Print(L"FwuPartitionVersionFromBuffer error: %u.\n", status);
            goto End;
        }
        Print(L"ISHC partition version from file: %u.%u.%u.%u.\n\n", major, minor, hotfix, build);

        status = FwuPartitionVendorIdFromFlash(FPT_PARTITION_NAME_ISHC, &vendorID);
        if (status != SUCCESS)
        {
            Print(L"FwuPartitionVendorIdFromFlash error: %u.\n", status);
            goto End;
        }
        Print(L"ISHC vendor ID from flash: 0x%x.\n\n", vendorID);

        status = FwuOemId(&oemId);
        if (status != SUCCESS)
        {
            Print(L"FwuOemId error: %u.\n", status);
            goto End;
        }
        Print(L"OEM ID: %08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x.\n\n",
              oemId.Data1, oemId.Data2, oemId.Data3,
              oemId.Data4[0], oemId.Data4[1],
              oemId.Data4[2], oemId.Data4[3],
              oemId.Data4[4], oemId.Data4[5],
              oemId.Data4[6], oemId.Data4[7]);

        goto End;
    }

    // /d option: ISH: get PDT and VDV version, set ISH config file.
    if (optionIshcFiles)
    {
        status = FwuGetIshPdtVersion(&pdtVersion, &vdvVersion);
        if (status != SUCCESS)
        {
            Print(L"FwuGetIshPdtVersion error: %u.\n", status);
            goto End;
        }
        Print(L"PDT version: %u, VDV version: %u.\n\n", pdtVersion, vdvVersion);

        Print(L"Sending Image for executing PDT Update...\n");

        status = FwuSetIshConfig(buffer, (UINT32)bufferLength);
        if (SUCCESS != status)
        {
            Print(L"Set ISH config failed.\n");
        }
        else
        {
            Print(L"Set ISH config succeeded.\n");
        }
        goto End;
    }


    // check FWU enabled state
    status = FwuEnabledState(&enabledState);
    if (SUCCESS != status)
    {
        goto End;
    }

    switch (enabledState)
    {
    case FW_UPDATE_DISABLED:
        status = FWU_LOCAL_DIS;
        goto End;
    case FW_UPDATE_ENABLED:
        break;
    default:
        break;
    }

    // /i option: Partial Update of ISHC partition.
    if (optionPartialIshc)
    {
        Print(L"Executing ISHC Partial FW Update.\n\n");
        Print(L"Warning: Do not exit the process or power off the machine before the firmware update process ends.\n");
        status = FwuPartialUpdateFromBuffer(buffer, (UINT32)bufferLength, FPT_PARTITION_NAME_ISHC, &displaySendStatus);
        if (SUCCESS != status)
        {
            goto End;
        }
    }
    else // Full Update
    {
        // for full update, check if update to the same version
        status = isUpdateToSameVersion(buffer, (UINT32)bufferLength, &isSameVersion);
        if (SUCCESS != status)
        {
            goto End;
        }

        //  need /s option for same version
        if (isSameVersion && !optionAllowSv)
        {
            status = FWU_ALLOWSV_RS_MISSING;
            goto End;
        }

		status = FwuCheckCompatibilityFromBuffer(buffer, (UINT32)bufferLength);
		if (SUCCESS != status)
		{
			goto End;
		}

        // display FW Type
        status = FwuFwType(&flashFwType);
        if (SUCCESS != status)
        {
            goto End;
        }
        switch (flashFwType)
        {
        case FWU_FW_TYPE_CONSUMER:
            Print(L"FW type is: Consumer.\n");
            break;
        case FWU_FW_TYPE_CORPORATE:
            Print(L"FW type is: Corporate.\n");
            break;
        default:
            Print(L"FW type is not supported for FW Update.\n");
            break;
        }
		status = FwuFwTypeFromBuffer(buffer, (UINT32)bufferLength, &updateFwType);
		if (SUCCESS != status)
		{
			goto End;
		}
		if (flashFwType != updateFwType)
		{
			status = FWU_ERROR_SKU_MISMATCH;
			goto End;
		}

        // display PCH SKU
        status = FwuPchSku(&flashPchSku);
        if (SUCCESS != status)
        {
            goto End;
        }
        switch (flashPchSku)
        {
        case FWU_PCH_SKU_H:
            Print(L"PCH SKU is: H.\n");
            break;
        case FWU_PCH_SKU_LP:
            Print(L"PCH SKU is: LP.\n");
            break;
        default:
            Print(L"PCH SKU is not identified.\n");
            break;
        }
		status = FwuPchSkuFromBuffer(buffer, (UINT32)bufferLength, &updatePchSku);
		if (SUCCESS != status)
		{
			goto End;
		}
		if (flashPchSku != updatePchSku)
		{
			status = FWU_ERROR_H_LP_MISMATCH;
			goto End;
		}

		status = FwuProductSegmentFromFlash(&flashProductSegment);
		if (SUCCESS != status)
		{
			goto End;
		}
		status = FwuProductSegmentFromBuffer(buffer, (UINT32)bufferLength, &updateProductSegment);
		if (SUCCESS != status)
		{
			goto End;
		}
		if (flashProductSegment != updateProductSegment)
		{
			status = FWU_ERROR_FW_TYPE_FTPR;
			goto End;
		}

        Print(L"\nExecuting Full FW Update.\n\n");
        Print(L"Warning: Do not exit the process or power off the machine before the firmware update process ends.\n");
        status = FwuFullUpdateFromBuffer(buffer, (UINT32)bufferLength, NULL, &displaySendStatus);
        if (SUCCESS != status)
        {
            goto End;
        }
    }

    Print(L"\n\n\n                        Do not Interrupt.\r");

    // image was sent to FW Update client
    // poll the FW Update progress until finished
    do
    {
        // we mod4 the index to determine which ascii animation frame to display for this iteration
        // loop through (|) (/) (-) (\) (|) (/) ...
        indexMod = (++index % 4);
        switch (indexMod)
        {
        case CMD_LINE_STATUS_UPDATE_1:
            symbol = '|';
            break;
        case CMD_LINE_STATUS_UPDATE_2:
            symbol = '/';
            break;
        case CMD_LINE_STATUS_UPDATE_3:
            symbol = '-';
            break;
        case CMD_LINE_STATUS_UPDATE_4:
            symbol = '\\';
            break;
        default:
            break;
        }

        status = FwuCheckUpdateProgress(&inProgress,
                                        &currentPercent,
                                        &fwUpdateStatus,
                                        &neededResetType);
        if (status != SUCCESS) // update failed
        {
            break;
        }

        if (!inProgress) // update finished successfully
        {
            // print 100% for customers output consistency
            Print(L"FW Update:  [ 100%% (%c)]\r", symbol);
            break;
        }

        // update in progress
        Print(L"FW Update:  [ %3u%% (%c)]\r", currentPercent, symbol);

        gBS->Stall(250000); // wait 250 milliseconds before polling again
        if (timer >= 30000) // if 30 seconds passed
        {
            if (currentPercent == previousPercent) // if percent didn't change in 30 seconds
            {
                status = FWU_UPDATE_TIMEOUT;
                break;
            }
            
            // percent changed
            previousPercent = currentPercent;
            timer = 0;
        }
        else
        {
            timer += 250;
        }
    } while (TRUE);

    // new line after printing all the percents.
    Print(L"\n");

    DebugPrint(L"FW Error Code: %u.\n", FwUpdateStatus);

    if (status != SUCCESS)
    {
        goto End;
    }

    switch (neededResetType)
    {
    case MFT_PART_INFO_EXT_UPDATE_ACTION_NONE:
    case MFT_PART_INFO_EXT_UPDATE_ACTION_CSE_RESET:
        Print(L"FW Update completed successfully.\n");
        break;
    case MFT_PART_INFO_EXT_UPDATE_ACTION_HOST_RESET:
    case MFT_PART_INFO_EXT_UPDATE_ACTION_GLOBAL_RESET:
    default:
        Print(L"FW Update completed successfully and a reboot will run the new FW.\n");
        break;
    }

End:
	if (NULL != buffer)
	{
		FreePool(buffer);
	}
    if (SUCCESS != status)
    {
        errorMessage(status);
    }
    return status;
}
