/*
 *****************************************************************************
 *                                                                           *
 *                 IMPINJ CONFIDENTIAL AND PROPRIETARY                       *
 *                                                                           *
 * This source code is the sole property of Impinj, Inc.  Reproduction or    *
 * utilization of this source code in whole or in part is forbidden without  *
 * the prior written consent of Impinj, Inc.                                 *
 *                                                                           *
 * (c) Copyright Impinj, Inc. 2009. All rights reserved.                     *
 *                                                                           *
 *****************************************************************************
 */

/*
 *****************************************************************************
 *
 * $Id: nvmem_update.c 66046 2010-11-15 18:52:20Z dshaheen $
 *
 * Description:
 *     This file contains the implementation of the logic necessary for a sample
 *     application for updating the nonvolatile memory on an Indy RFID radio
 *     module.  Much of this file pertains to parsing and validatig the XML
 *     input and loading NV memory file(s) into a list of NV Memory Blocks.
 *     Once this is complete, the NV memory blocks are written to the Radio,
 *     conditioned upon the test mode controls (refer to USAGE for details).
 *
 * Note:  Refer to the USAGE: label for details of command line arguments
 *
 *
 *****************************************************************************
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "mapfile.h"
#include "sample_stack.h"
#include "rfid_library.h"
#include "sample_utility.h"
#include "macregs.h"
#include "compat_time.h"


typedef struct xmlNode
{
    char*           pName;          /* The name of this node                  */
    struct xmlNode* pNextSibling;   /* The next sibling (if any) of this node */
    struct xmlNode* pFirstChild;    /* The first child (if any) of this node  */
    struct xmlNode* pLastChild;     /* The first child (if any) of this node  */
    char*           pValue;         /* The value (if any) of this node        */
} XML_NODE;


enum
{
    TM_SKIP_TEST,
    TM_TEST_ONLY,
    TM_TEST_AND_UPDATE
};
typedef INT32U  TM_OPTIONS;

enum
{
    UM_SAFE,
    UM_DOWNGRADE,
    UM_BOOT_LOADER,
    UM_UNSAFE
};
typedef INT32U  UM_OPTIONS;

static XML_NODE* CreateXmlTree(
    const char* pFileStream,
    INT32U      length
    );
static void DestroyXmlTree(
    XML_NODE*   pRoot
    );
static INT32S ProcessXmlTree(
    const XML_NODE*                 pRoot,
    RFID_NONVOLATILE_MEMORY_BLOCK** ppBlocks,
    MEMMAP_HANDLE**                 ppHandles,
    INT32U*                         pNumberBlocks
    );
static RFID_RADIO_ENUM* EnumerateAttachedRadios(
    void
    );
static INT32S UpdateRadioNonvolatileMemory(
    RFID_RADIO_INFO*                pInfo,
    INT32U                          numberBlocks,
    RFID_NONVOLATILE_MEMORY_BLOCK*  pBlocks,
    INT32U                          testModeFlag,
    UM_OPTIONS                      upgradeMode
    );

static INT32S ProcessCommandLine(int argc, char** argv);

static INT32S EnumerateAndResetRadio (void);

const char*     ROOT_TAG_NAME       = "blocks";
const char*     BLOCK_TAG_NAME      = "block";
const char*     ADDRESS_TAG_NAME    = "address";
const char*     FILE_TAG_NAME       = "file";


#define MILLISEC 1000

#define  MINIMUM_NUM_ARGS    2

#define  DEF_TEST_MODE       TM_TEST_AND_UPDATE
#define  DEF_UPGRADE_MODE    UM_UNSAFE

#define TM_SWITCH            "-tm"
#define UM_SWITCH            "-um"
#define HELP1_SWITCH         "-h"
#define HELP2_SWITCH         "-?"

#define ALL_SWITCH           "-a"
#define TEST_SWITCH          "-t"
#define SKIP_SWITCH          "-s"

#define TM_ST                "st"
#define TM_TO                "to"
#define TM_TU                "tu"

#define UM_SF                "sf"
#define UM_DG                "dg"
#define UM_BL                "bl"
#define UM_UF                "uf"

/*  default init for original behavior (unsafe, test and update)  */
static TM_OPTIONS gTestMode     = DEF_TEST_MODE;
static UM_OPTIONS gUpgradeMode  = DEF_UPGRADE_MODE;

static INT32U     startupFlags  = 0;
static char       *configFile   = NULL;

int main(int argc, char** argv)
{
    MEMMAP_HANDLE                   memmap;
    char*                           pFileStart;
    INT32U                          fileSize;
    XML_NODE*                       pRoot;
    RFID_NONVOLATILE_MEMORY_BLOCK*  pBlocks = NULL;
    INT32U                          numberBlocks;
    INT32U                          blockNumber;
    MEMMAP_HANDLE*                  pUpdateHandles = NULL;
    RFID_STATUS                     status;
    RFID_VERSION                    version;
    RFID_RADIO_ENUM*                pEnum = NULL;
    RFID_RADIO_HANDLE               handle;
//    BOOL32                          runTestMode = 0;


    if (ProcessCommandLine(argc, argv) != 0)
    {
        goto EXIT;
    }

    /* Map the configuration file onto memory...this will make life a little  */
    /* simpler                                                                */
    memmap = MapFileOntoMemory(configFile);
    if (INVALID_MEMMAP_HANDLE == memmap)
    {
        fprintf(stderr, "ERROR: Unable to open file \"%s\"\n", argv[1]);
        goto EXIT;
    }
    pFileStart  = (char *) GetFileMappingStartAddress(memmap);
    fileSize    = GetFileMappingSize(memmap);

    /* Create the XML tree that represents the file.                          */
    pRoot = CreateXmlTree(pFileStart, fileSize);
    if (NULL == pRoot)
    {
        goto UNMAP_CONFIG_FILE;
    }

    /* Process the XML tree to create the nonvolatile memory blocks and map   */
    /* the update files into memory                                           */
    if (ProcessXmlTree(pRoot, &pBlocks, &pUpdateHandles, &numberBlocks))
    {
        fprintf(stderr, "ERROR: Contents of configuration file were invalid\n");
        goto DESTROY_XML_TREE;
    }

    /* Start up the library                                                   */
    status = RFID_Startup(&version, startupFlags);
    if (RFID_STATUS_OK != status)
    {
        fprintf(
            stderr,
            "ERROR: Failed to initialize RFID library (%s)\n",
            RFIDStatusToString(status));
        goto UNMAP_UPDATE_FILES;
    }

    printf("Indy RFID Reader Library Version %d.%d.%d.%d\n",
        version.major,
        version.minor,
        version.maintenance,
        version.release);


    /* Enumerate and reset the attached radio.  We do this to establish
     * a radio id that will remain consistent as we go through the
     * test update sequence.
    */
    if (0 != EnumerateAndResetRadio ())
    {
        goto SHUTDOWN_RFID_LIBRARY;
    }

    /* Enumerate the attached radios                                          */
    pEnum = EnumerateAttachedRadios();
    if (NULL == pEnum)
    {
        fprintf(stderr, "ERROR: Failed to enumerate radios\n");
        goto SHUTDOWN_RFID_LIBRARY;
    }
    if (pEnum->countRadios != 1)
    {
        printf("ERROR: Only 1 radio can be attached to the system\n");
        goto DESTROY_ENUMERATION;
    }



    /* if we aren't skipping test mode, run the test update */
    if (gTestMode != TM_SKIP_TEST )
    {

        /* Test mode is all about validating the input files to ensure they
         * pass validity checks on the radio (ie, they don't exceed memory
         * constraints/bounds) before acutally trying to commit the content
         * to flash.  Test mode is inherently safe since it does
         * not write to flash, so we run the test mode first across all
         * blocks without regard to update mode settings.
        */
        if (UpdateRadioNonvolatileMemory(
            pEnum->ppRadioInfo[0],
            numberBlocks,
            pBlocks,
            RFID_FLAG_NVMEM_UPDATE_TEST,
            gUpgradeMode))
        {
            fprintf(stderr, "ERROR: Update failed for radio\n");
            goto DESTROY_ENUMERATION;
        }
    }

    /* if we aren't just running test mode, do the real update*/
    if (gTestMode != TM_TEST_ONLY )
    {
        /* For the real test update, we honor the update mode settings
         * which may require resetting the radio into BootLoader mode,
         * re-enumerating radios because the radio id will change, setting
         * up the nv update control word appropriately, etc. before we do the
         * actual update
        */


        /*  If we need to reset to BootLoader, do that first because we'll need to
         *  re-enumerate to pick up the new radio id
        */
        if ((gUpgradeMode == UM_BOOT_LOADER) ||
            (gUpgradeMode == UM_SAFE))
        {
            /* first open the radio to get a handle */
            status = RFID_RadioOpen(pEnum->ppRadioInfo[0]->cookie, &handle, 0);
            if (RFID_STATUS_OK != status)
            {
                fprintf(
                    stderr,
                    "ERROR: Unable to open radio (%s)\n",
                    RFIDStatusToString(status));
                goto DESTROY_ENUMERATION;
            }

            printf("Resetting to BootLoader\n");
            RFID_MacReset(handle, RFID_MAC_RESET_TYPE_TO_BOOTLOADER);

            /* the library closes the radio on reset */

            /* give the radio some time to reset. */
            CPL_MillisecondSleep(3000);


            /* now cleanup the old enumeration */
            free (pEnum);
            pEnum = NULL;

            /* re-enumerate to get the new radio id */
            pEnum = EnumerateAttachedRadios();
            if (NULL == pEnum)
            {
                fprintf(stderr, "ERROR: Failed to enumerate radios\n");
                goto SHUTDOWN_RFID_LIBRARY;
            }
            if (pEnum->countRadios != 1)
            {
                printf("ERROR: Only 1 radio can be attached to the system\n");
                goto DESTROY_ENUMERATION;
            }

        }

        /* now do the update */
        if (UpdateRadioNonvolatileMemory(
            pEnum->ppRadioInfo[0],
            numberBlocks,
            pBlocks,
            0,
            gUpgradeMode))
        {
            fprintf(stderr, "ERROR: Update failed for radio\n");
            goto DESTROY_ENUMERATION;
        }
    }


DESTROY_ENUMERATION:
    if (pEnum != NULL)
    {
        free(pEnum);
    }

SHUTDOWN_RFID_LIBRARY:
    RFID_Shutdown();

UNMAP_UPDATE_FILES:
    for (blockNumber = 0; blockNumber < numberBlocks; ++blockNumber)
    {
        if (INVALID_MEMMAP_HANDLE != pUpdateHandles[blockNumber])
        {
            UnmapFileFromMemory(pUpdateHandles[blockNumber]);
        }
    }

    free(pUpdateHandles);
    free(pBlocks);

DESTROY_XML_TREE:
    DestroyXmlTree(pRoot);

UNMAP_CONFIG_FILE:
    UnmapFileFromMemory(memmap);
    goto EXIT;


EXIT:
    return 0;
}



XML_NODE* CreateXmlTree(
    const char* pFileStream,
    INT32U      length
    )
{
    XML_NODE*       pRoot = NULL;
    STACK_HANDLE    stack = INVALID_STACK_HANDLE;
    INT8S           stayInLoop = 1;  // just to avoid compiler warning

    /* Create a stack that is used during process of XML text                 */
    stack = StackCreate();
    if (INVALID_STACK_HANDLE == stack)
    {
        goto EXIT;
    }

    while (stayInLoop)
    {
        /* Get rid of any whitespace and if we are at the end of the file data*/
        /* then we are done.                                                  */
        for ( ; length && isspace(*pFileStream); --length, ++pFileStream);
        if (!length)
        {
            break;
        }

        /* If we are at a tag, then we need to figure out the tag name        */
        if ('<' == *pFileStream)
        {
            /* Find the tag's closing >                                       */
            const char* pStartTag = ++pFileStream;
            for (--length;
                length && ('>' != *pFileStream);
                --length, ++pFileStream);
            if (pStartTag == pFileStream)
            {
                fprintf(stderr, "ERROR: Found an empty XML begin tag\n");
                goto DESTROY_XML_TREE;
            }

            /* If this is a begin tag, then we'll create a new tree node and  */
            /* push it onto the stack.                                        */
            if ('/' != *pStartTag)
            {
                /* Allocate and initialize a new node.                        */
                XML_NODE* pNewNode = (XML_NODE *) malloc(sizeof(XML_NODE));
                if (NULL == pNewNode)
                {
                    fprintf(stderr, "ERROR: Failed to allocate memory\n");
                    goto DESTROY_XML_TREE;
                }
                pNewNode->pNextSibling  = NULL;
                pNewNode->pFirstChild   = NULL;
                pNewNode->pLastChild    = NULL;
                pNewNode->pValue        = NULL;
                pNewNode->pName         = NULL;

                /* If the stack is empty, then this tag is the root           */
                if (StackEmpty(stack))
                {
                    if (NULL != pRoot)
                    {
                        fprintf(
                            stderr,
                            "ERROR: Found a second root tag named %.*s\n",
                            pFileStream - pStartTag,
                            pStartTag);
                        free(pNewNode);
                        goto DESTROY_XML_TREE;
                    }
                    pRoot = pNewNode;
                }
                /* Otherwise, the tag is a child of the node that is on top   */
                /* of the stack.  Additionally, if the top node already has   */
                /* children, then this child is a sibling to that(those)      */
                /* node(s)                                                    */
                else
                {
                    XML_NODE* pTop = (XML_NODE *) StackTop(stack);
                    if (NULL == pTop->pFirstChild)
                    {
                        pTop->pFirstChild = pNewNode;
                    }
                    else
                    {
                        pTop->pLastChild->pNextSibling = pNewNode;
                    }
                    pTop->pLastChild = pNewNode;
                }

                if (StackPush(stack, pNewNode))
                {
                    fprintf(stderr, "ERROR: Failed to push value on stack\n");
                    free(pNewNode);
                    goto DESTROY_XML_TREE;
                }

                /* Copy the tag's name                                        */
                pNewNode->pName     =
                    (char *) malloc(pFileStream - pStartTag + 1);
                if (NULL == pNewNode->pName)
                {
                    fprintf(stderr, "ERROR: Failed to allocate memory\n");
                    goto DESTROY_XML_TREE;
                }
                strncpy(pNewNode->pName, pStartTag, pFileStream - pStartTag);
                pNewNode->pName[pFileStream - pStartTag] = '\0';
            }
            /* Otherwise, since this is an end tag, then we need to ensure    */
            /* it matches the tag on top of the stack and then pop the stack. */
            else
            {
                /* Verify that the end tag is not empty                       */
                if (++pStartTag == pFileStream)
                {
                    fprintf(stderr, "ERROR: Found an empty XML end tag\n");
                    goto DESTROY_XML_TREE;
                }

                /* Verify that the end tag name matches the name of the tag  */
                /* that is on top of the stack.                              */
                if (StackEmpty(stack))
                {
                    fprintf(
                        stderr,
                        "ERROR: Found an end tag, but no start tag\n");
                    goto DESTROY_XML_TREE;
                }
                if (strncmp(
                    ((XML_NODE *) StackTop(stack))->pName,
                    pStartTag,
                    pFileStream - pStartTag))
                {
                    fprintf(
                        stderr,
                        "ERROR: End tag </%.*s> doesn't match begin tag <%s>\n",
                        pFileStream - pStartTag,
                        pStartTag,
                        ((XML_NODE *) StackTop(stack))->pName);
                    goto DESTROY_XML_TREE;
                }

                /* Since we have found the matching end tag, we can pop the   */
                /* tag off of the stack                                       */
                StackPop(stack);
            }

            /* Move past the tag closing >                                    */
            --length;
            ++pFileStream;
        }
        /* Otherwise, we are at a data value                                  */
        else
        {
            const char* pStartValue = pFileStream;
            XML_NODE*   pNode;

            /* Ensure that there is actually a tag on top of the stack        */
            if (StackEmpty(stack))
            {
                fprintf(
                    stderr,
                    "ERROR: Found a tag value, but not inside of tag\n");
                goto DESTROY_XML_TREE;
            }

            /* Find the end of the value                                      */
            for ( ; length && ('<' != *pFileStream); --length, ++pFileStream);
            if (!length)
            {
                fprintf(
                    stderr,
                    "ERROR: Reached end of file in middle of tag value\n");
                goto DESTROY_XML_TREE;
            }

            /* Copy the value into the XML node                               */
            pNode           = (XML_NODE *) StackTop(stack);
            pNode->pValue   = (char *) malloc(pFileStream - pStartValue + 1);
            if (NULL == pNode->pValue)
            {
                fprintf(stderr, "ERROR: Failed to allocate memory\n");
                goto DESTROY_XML_TREE;
            }
            strncpy(pNode->pValue, pStartValue, pFileStream - pStartValue);
            pNode->pValue[pFileStream - pStartValue] = '\0';
        }
    }

    /* At this point, the stack should be empty - meaning that the file has   */
    /* been completely processed and an opening/closing tag should have been  */
    /* found.                                                                 */
    if (!StackEmpty(stack))
    {
        fprintf(stderr,
            "ERROR: Found additional non-whitespace after closing tag\n");
        goto DESTROY_XML_TREE;
    }

    goto DESTROY_STACK;

DESTROY_XML_TREE:
    DestroyXmlTree(pRoot);
    pRoot = NULL;

DESTROY_STACK:
    while (!StackEmpty(stack))
    {
        StackPop(stack);
    }
    StackDestroy(stack);

EXIT:
    return pRoot;
} /* CreateXmlTree */

void DestroyXmlTree(
    XML_NODE*   pRoot
    )
{
    if (pRoot)
    {
        DestroyXmlTree(pRoot->pFirstChild);
        DestroyXmlTree(pRoot->pNextSibling);
        if (pRoot->pName)
        {
            free(pRoot->pName);
        }
        if (pRoot->pValue)
        {
            free(pRoot->pValue);
        }
        free(pRoot);
    }
} /* DestroyXmlTree */

INT32S ProcessXmlTree(
    const XML_NODE*                 pRoot,
    RFID_NONVOLATILE_MEMORY_BLOCK** ppBlocks,
    MEMMAP_HANDLE**                 ppHandles,
    INT32U*                         pNumberBlocks
    )
{
    INT32S                          result      = -1;
    XML_NODE*                       pNode;
    RFID_NONVOLATILE_MEMORY_BLOCK*  pBlocks     = NULL;
    MEMMAP_HANDLE*                  pHandles    = NULL;
    INT32U                          numberBlocks;
    INT32U                          blockNumber;

    *ppBlocks       = NULL;
    *ppHandles      = NULL;
    *pNumberBlocks  = 0;

    /* Verify that the root of the tree is named properly.                    */
    if (strcmp(pRoot->pName, ROOT_TAG_NAME))
    {
        fprintf(
            stderr,
            "ERROR: XML root tag is named <%s> instead of <%s>\n",
            pRoot->pName,
            ROOT_TAG_NAME);
        goto EXIT;
    }

    /* Verify that there are update blocks                                    */
    if (NULL == pRoot->pFirstChild)
    {
        fprintf(
            stderr,
            "ERROR: Update file must contain at least one update block\n");
        goto EXIT;
    }

    /* Process each block to create the update block array                    */
    for (pNode = pRoot->pFirstChild, numberBlocks = 0;
        pNode != NULL;
        pNode = pNode->pNextSibling, ++numberBlocks)
    {
        RFID_NONVOLATILE_MEMORY_BLOCK*  pNewBlocks;
        MEMMAP_HANDLE*                  pNewHandles;
        XML_NODE*                       pChild;
        INT32U                          addressSet = 0;

        if (strcmp(pNode->pName, BLOCK_TAG_NAME))
        {
            fprintf(
                stderr,
                "ERROR: Found XML tag named <%s>, but expected <%s>\n",
                pNode->pName,
                BLOCK_TAG_NAME);
            goto CLEANUP;
        }

        /* Expand the array of nonvolatile memory blocks and file mapping     */
        /* handles.                                                           */
        pNewBlocks =
            (RFID_NONVOLATILE_MEMORY_BLOCK *) realloc(
            pBlocks,
            (numberBlocks + 1) * sizeof(RFID_NONVOLATILE_MEMORY_BLOCK));
        if (NULL == pNewBlocks)
        {
            fprintf(stderr, "ERROR: Failed to allocate memory\n");
            goto CLEANUP;
        }
        pBlocks = pNewBlocks;
        pBlocks[numberBlocks].pData = NULL;
        pBlocks[numberBlocks].flags = 0;

        pNewHandles =
            (MEMMAP_HANDLE *) realloc(
            pHandles,
            (numberBlocks + 1) * sizeof(MEMMAP_HANDLE));
        if (NULL == pNewHandles)
        {
            fprintf(stderr, "ERROR: Failed to allocate memory\n");
            goto CLEANUP;
        }
        pHandles = pNewHandles;
        pHandles[numberBlocks] = INVALID_MEMMAP_HANDLE;

        /* Process the children to get the address and the update file        */
        for (pChild = pNode->pFirstChild;
            NULL != pChild;
            pChild = pChild->pNextSibling)
        {
            /* Convert the address string to the update address               */
            if (!strcmp(pChild->pName, ADDRESS_TAG_NAME))
            {
                char* endString;

                if (addressSet)
                {
                    fprintf(
                        stderr,
                        "ERROR: Tag <address> appears more than once in "
                        "<block>\n");
                    goto CLEANUP;
                }

                if (NULL == pChild->pValue)
                {
                    fprintf(
                        stderr, "ERROR: Tag <address> does not have a value\n");
                    goto CLEANUP;
                }

                pBlocks[numberBlocks].address =
                    strtoul(pChild->pValue, &endString, 0);
                if (*endString)
                {
                    fprintf(
                        stderr,
                        "ERROR: \"%s\" is an invalid address\n",
                        pChild->pValue);
                    goto CLEANUP;
                }

                addressSet = 1;
            }
            /* Map the update file onto memory so we can use it               */
            else if (!strcmp(pChild->pName, FILE_TAG_NAME))
            {
                if (NULL != pBlocks[numberBlocks].pData)
                {
                    fprintf(
                        stderr,
                        "ERROR: Tag <file> appears more than once in "
                        "<block>\n");
                    goto CLEANUP;
                }

                if (NULL == pChild->pValue)
                {
                    fprintf(
                        stderr, "ERROR: Tag <file> does not have a value\n");
                    goto CLEANUP;
                }

                pHandles[numberBlocks] =
                    MapFileOntoMemory(pChild->pValue);
                if (INVALID_MEMMAP_HANDLE == pHandles[numberBlocks])
                {
                    fprintf(
                        stderr,
                        "ERROR: Failed to open update file \"%s\"\n",
                        pChild->pValue);
                    goto CLEANUP;
                }

                pBlocks[numberBlocks].pData     =
                    GetFileMappingStartAddress(pHandles[numberBlocks]);
                pBlocks[numberBlocks].length    =
                    GetFileMappingSize(pHandles[numberBlocks]);
            }
            else
            {
                fprintf(
                    stderr,
                    "ERROR: Encountered unexpected tag <%s>\n",
                    pChild->pName);
                goto CLEANUP;
            }
        }
    }

    result          = 0;
    *ppBlocks       = pBlocks;
    *ppHandles      = pHandles;
    *pNumberBlocks  = numberBlocks;

    goto EXIT;

CLEANUP:
    for (blockNumber = 0; blockNumber < numberBlocks; ++blockNumber)
    {
        if (INVALID_MEMMAP_HANDLE != pHandles[blockNumber])
        {
            UnmapFileFromMemory(pHandles[blockNumber]);
        }
    }

    free(pHandles);
    free(pBlocks);

EXIT:
    return result;
} /* ProcessXmlTree */

RFID_RADIO_ENUM* EnumerateAttachedRadios(
    void
    )
{
    RFID_STATUS         status;
    RFID_RADIO_ENUM*    pEnum;

    /* Allocate the minimal structure for the enumeration                     */
    pEnum = (RFID_RADIO_ENUM *) malloc(sizeof(RFID_RADIO_ENUM));
    if (NULL == pEnum)
    {
        fprintf(stderr, "ERROR: Failed to allocate memory\n");
        goto EXIT;
    }
    pEnum->length       =
        pEnum->totalLength  = sizeof(RFID_RADIO_ENUM);

    /* Go into a loop and keep trying to enumerate until a status other than  */
    /* buffer too small comes back                                            */
    while (RFID_ERROR_BUFFER_TOO_SMALL ==
        (status = RFID_RetrieveAttachedRadiosList(pEnum, 0)))
    {
        RFID_RADIO_ENUM* pNewEnum =
            (RFID_RADIO_ENUM *) realloc(pEnum, pEnum->totalLength);
        if (NULL == pNewEnum)
        {
            fprintf(stderr, "ERROR: Failed to allocate memory\n");
            goto DESTROY_ENUMERATION;
        }
        pEnum = pNewEnum;
    }
    if (RFID_STATUS_OK != status)
    {
        fprintf(
            stderr,
            "ERROR: Failed to enumerate the attached radios (%s)\n",
            RFIDStatusToString(status));
        goto DESTROY_ENUMERATION;
    }

    goto EXIT;

DESTROY_ENUMERATION:
    free(pEnum);
    pEnum = NULL;

EXIT:
    return pEnum;
} /* EnumerateAttachedRadios */

static INT32S UpdateRadioNonvolatileMemory(
    RFID_RADIO_INFO*                pInfo,
    INT32U                          numberBlocks,
    RFID_NONVOLATILE_MEMORY_BLOCK*  pBlocks,
    INT32U                          testModeFlag,
    UM_OPTIONS                      upgradeMode
    )
{

    INT32S              result = -1;
    BOOL32              allUpdatesSuccessful = 1;
    RFID_STATUS         status;
    RFID_RADIO_HANDLE   handle;
    INT32U              blockNumber;
    INT32U              regval = 0;

    /* If we are doing the test mode, do it a block at a time, so if an error
     * occurs in validation, it is clear which block/file is offending
    */
    if (testModeFlag == RFID_FLAG_NVMEM_UPDATE_TEST)
    {
        printf("\nTest update for radio:\n");
        PrintRadioUniqueId("",
            pInfo->idLength,
            pInfo->pUniqueId);

        /* Perform a test update on the radio first                            */
        for (blockNumber = 0; blockNumber < numberBlocks; ++blockNumber)
        {
            /* Open the radio                                                     */
            status = RFID_RadioOpen(pInfo->cookie, &handle, 0);
            if (RFID_STATUS_OK != status)
            {
                fprintf(
                    stderr,
                    "ERROR: Unable to open radio (%s)\n",
                    RFIDStatusToString(status));
                goto EXIT;
            }

            printf("  Block %d:\n", blockNumber + 1);
            printf("    Start address:\t0x%.8x\n", pBlocks[blockNumber].address);
            printf("    Number of bytes:\t%u\n", pBlocks[blockNumber].length);

            status =
                RFID_MacUpdateNonvolatileMemory(
                handle,
                1,
                pBlocks + blockNumber,
                RFID_FLAG_NVMEM_UPDATE_TEST);
            printf("    Status:\t\t%s\n", RFIDStatusToString(status));
            if (RFID_STATUS_OK != status)
            {
                allUpdatesSuccessful = 0;
            }

            /* in all case, success or failure, test mode or not, the radio resets
             * at the end of NV Update processing and the library closes the radio.
             *
             * Give the radio some time to reset.  7 seconds seems long, but the
             * radio waits 5 on its own to ensure the host receives the last status
             * so we give an extra 2 to get through reset and ready to talk again
            */
            CPL_MillisecondSleep(7000);
        } /* end block loop */

        fflush(stdout);

        if (!allUpdatesSuccessful)
        {
            fprintf(
                stderr,
                "ERROR: One or more test updates failed.\n");
            goto EXIT;
        }
    }
    else /* not test mode */
    {
        printf("NV update for radio:\n");
        PrintRadioUniqueId("",
            pInfo->idLength,
            pInfo->pUniqueId);

        /* Open the radio                                                     */
        status = RFID_RadioOpen(pInfo->cookie, &handle, 0);
        if (RFID_STATUS_OK != status)
        {
            fprintf(
                stderr,
                "ERROR: Unable to open radio (%s)\n",
                RFIDStatusToString(status));
            goto EXIT;
        }


        /* Now we are ready to make any nv update configuration changes, before we
         * kick off the update.
        */
        if (upgradeMode == UM_BOOT_LOADER)
        {
            printf("Configuring for BootLoader Update\n\n");
            /* Set unlock sector 0 pattern, and set update mode to BootLoader */
            MAC_NV_UPDATE_CONTROL_SET_UPDATE_MODE(regval, MAC_NV_UPDATE_CONTROL_UPDATE_BOOT_LOADER);
            MAC_NV_UPDATE_CONTROL_SET_UNLOCK_SECTOR_0(regval, MAC_NV_UPDATE_CONTROL_UNLOCK_SECT0_PATTERN);
            RFID_MacWriteRegister(handle, MAC_NV_UPDATE_CONTROL, regval);
        }
        else if (upgradeMode == UM_DOWNGRADE)
        {
            printf("Configuring for downgrade update\n\n");
            MAC_NV_UPDATE_CONTROL_SET_UNLOCK_SECTOR_0(regval, MAC_NV_UPDATE_CONTROL_UNLOCK_SECT0_PATTERN);
            RFID_MacWriteRegister(handle, MAC_NV_UPDATE_CONTROL, regval);
        }

        /* not doing a test mode, this is a real update.  We don't
         * break the update down by block, we treat it as one big
         * block.
        */
        printf("\n"
            "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
            "!! WARNING - DO NOT UNPLUG RADIO DURING UPDATE !!\n"
            "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
        fflush(stdout);

        status =
            RFID_MacUpdateNonvolatileMemory(
            handle,
            numberBlocks,
            pBlocks,
            0);

        if (RFID_STATUS_OK != status)
        {
            fprintf(
                stderr,
                "ERROR: Update of nonvolatile memory failed (%s)\n",
                RFIDStatusToString(status));
            goto EXIT;
        }
        else
        {
            printf("\nNonvolatile memory update on radio succeeded\n");
        }

        /* in all case, success or failure, test mode or not, the radio resets
         * at the end of NV Update processing and the library closes the radio.
        */

    }

    result = 0;

EXIT:
    return result;

} /* UpdateRadioNonvolatileMemory */


static INT32S ProcessCommandLine(int argc, char** argv)
{
    int     argidx;
    char    *option;
    char    *value;
    char    *tmp;
    INT32S  retval = 0;

    /* Validate command-line arguments                                        */
    if (argc < MINIMUM_NUM_ARGS)
    {
        goto USAGE;
    }
    for (argidx = 1; argidx < argc; argidx++)
    {
        option = strtok(argv[argidx], "=");
        value  = strtok(NULL, "=");

        /*  first check/process the option */
        if (NULL == option)
        {
            goto USAGE;
        }
        else  /* ensure option is lower case if it is a switch */
        {
            if (option[0] == '-')
            {
                tmp = option;
                while (*tmp)
                {
                    *tmp = tolower(*tmp);
                    tmp++;
                }
            }
        }


        /* if there is no = sign, it is a value less argument and value will be NULL.
         * We'll handle those arguments first.
        */
        if (value == NULL)
        {
            if ((strncmp(option, HELP1_SWITCH, strlen(HELP1_SWITCH)) == 0) ||
                (strncmp(option, HELP2_SWITCH, strlen(HELP2_SWITCH)) == 0))
            {
                goto USAGE;
            }
            else if ((strcmp(option, ALL_SWITCH) == 0)  ||
                     (strcmp(option, TEST_SWITCH) == 0)  ||
                     (strcmp(option, SKIP_SWITCH) == 0))
            {
                fprintf(stderr, "Error: %s is no longer supported\n\n", option);
                goto USAGE;
            }
            else if (option[0] != '-')  /*  if its not a switch, it must be the config file name */
            {
                configFile = option;
            }
            else /*  error */
            {
                fprintf(stderr, "Error: %s is not recognized\n\n", option);
                goto USAGE;
            }
        }
        else /* if we are here, we have a non-null value so ensure it is lower case */
        {
            tmp = value;
            while (*tmp)
            {
                *tmp = tolower(*tmp);
                tmp++;
            }

            /* now process the arguments */
            if (strncmp(option, TM_SWITCH, strlen(TM_SWITCH)) == 0)
            {
                if (strncmp(value, TM_ST, strlen(TM_ST)) == 0)
                {
                    gTestMode     = TM_SKIP_TEST;
                }
                else if (strncmp(value, TM_TO, strlen(TM_TO)) == 0)
                {
                    gTestMode     = TM_TEST_ONLY;
                }
                else if (strncmp(value, TM_TU, strlen(TM_TU)) == 0)
                {
                    gTestMode     = TM_TEST_AND_UPDATE;
                }
                else
                {
                    fprintf(stderr, "Error: Invalid test mode (-tm=%s) specified\n\n",value);
                    goto USAGE;
                }
            }
            else if (strncmp(option, UM_SWITCH, strlen(UM_SWITCH)) == 0)
            {
                if (strncmp(value, UM_SF, strlen(UM_SF)) == 0)
                {
                    gUpgradeMode     = UM_SAFE;
                }
                else if (strncmp(value, UM_DG, strlen(UM_DG)) == 0)
                {
                    gUpgradeMode     = UM_DOWNGRADE;
                }
                else if (strncmp(value, UM_BL, strlen(UM_BL)) == 0)
                {
                    gUpgradeMode     = UM_BOOT_LOADER;
                }
                else if (strncmp(value, UM_UF, strlen(UM_UF)) == 0)
                {
                    gUpgradeMode     = UM_UNSAFE;
                }
                else
                {
                    fprintf(stderr, "Error: Invalid update mode (-um=%s) specified\n\n",value);
                    goto USAGE;
                }
            }
#if 0
            else if (strncmp(option, "-f", strlen("-f")) == 0)
            {
                startupFlags = strtoul(value, NULL, 16);
            }
#endif
            else
            {
                fprintf(stderr, "Error: %s=%s is not recognized\n\n", option, value);
                goto USAGE;
            }
        }

    }

    if (configFile == NULL)
    {
        fprintf(stderr, "Error:  No config_file specified\n\n");
        goto USAGE;
    }
    /* success path */
    goto EXIT;

USAGE:
    fprintf(stderr, "usage: %s [-tm=st|to|tu] [-um=sf|dg|bl|uf] [-h|-?] config_file\n", argv[0]);
    fprintf(stderr, "  where:\n");
    fprintf(stderr, "      -tm=       - specifies the test mode control:\n");
    fprintf(stderr, "          st        - Skip test update, go straight to destructive update\n");
    fprintf(stderr, "          to        - Perform test update only, doesn't write NV memory\n");
    fprintf(stderr, "          tu        - (default) Perform test update and on success continues\n");
    fprintf(stderr, "                      to destructive update\n\n");

    fprintf(stderr, "      -um=       - specifies the update mode control:\n");
    fprintf(stderr, "          sf        - Safe Update, reset to BootLoader and update MAC firmware using\n");
    fprintf(stderr, "                      BootLoader\n");
    fprintf(stderr, "          dg        - Unsafe Downgrade, to load pre v2.4 MAC firmware.  Unlocks\n");
    fprintf(stderr, "                      BootLoader sector so it may be overwritten\n");
    fprintf(stderr, "          bl        - BootLoader Update, reset to BootLoader and update itself using\n");
    fprintf(stderr, "                      BootLoader.\n");
    fprintf(stderr, "                        NOTE: The BootLoader update is in theory unsafe, however the\n");
    fprintf(stderr, "                              window of vulnerability in this case is dramatically\n");
    fprintf(stderr, "                              narrowed by downloadig the entire BootLoader image to RAM\n");
    fprintf(stderr, "                              and validating it, before the BootLoader sector is modified\n");
    fprintf(stderr, "          uf        - (default) Unsafe Update, performs in place update of MAC firmware.\n\n");

    fprintf(stderr, "     -h          - displays this help screen\n\n");

    fprintf(stderr, "     -?          - displays this help screen\n\n");

    fprintf(stderr, "     config_file -  specifies the file that contains config info for the update.  This\n"
                    "                    file is in an XML-based format:\n"
                    "                      <blocks>\n"
                    "                          <block>\n"
                    "                            <address>starting address</address>\n"
                    "                            <file>NV memory file name</file>\n"
                    "                          </block>\n"
                    "                         [<block> ... </block>]*\n"
                    "                      </blocks>\n");

    retval = -1;

EXIT:
    return retval;
}


static INT32S EnumerateAndResetRadio (void)
{
    RFID_STATUS        status;
    INT32S             retval = -1;
    RFID_RADIO_ENUM*   pEnum = NULL;
    RFID_RADIO_HANDLE  handle;

    /* Enumerate the attached radios                                          */
    pEnum = EnumerateAttachedRadios();
    if (NULL == pEnum)
    {
        fprintf(stderr, "ERROR: Failed to enumerate radios\n");
        goto EXIT;
    }
    if (pEnum->countRadios != 1)
    {
        printf("ERROR: Only 1 radio can be attached to the system\n");
        goto CLEANUP;
    }

    /* Open the radio  */
    status = RFID_RadioOpen(pEnum->ppRadioInfo[0]->cookie, &handle, 0);
    if (RFID_STATUS_OK != status)
    {
        fprintf(
            stderr,
            "ERROR: Unable to open radio (%s)\n",
            RFIDStatusToString(status));
        goto CLEANUP;
    }

    printf("Resetting Radio\n");
    RFID_MacReset(handle, RFID_MAC_RESET_TYPE_SOFT);

    /* the library closes the radio on reset */

    /* give the radio some time to reset. */
    CPL_MillisecondSleep(3000);

    /* success */
    retval = 0;

CLEANUP:
    if (pEnum != NULL)
    {
        free(pEnum);
    }

EXIT:
    return retval;
}
