/**************************************************************************************************
  Filename:       nwk.c
  Revised:        $Date: 2009-03-11 15:29:07 -0700 (Wed, 11 Mar 2009) $
  Revision:       $Revision: 19382 $
  Author          $Author: lfriedman $

  Description:    This file supports the SimpliciTI network layer.

  Copyright 2007-2009 Texas Instruments Incorporated. All rights reserved.

  IMPORTANT: Your use of this Software is limited to those specific rights granted under
  the terms of a software license agreement between the user who downloaded the software,
  his/her employer (which must be your employer) and Texas Instruments Incorporated (the
  "License"). You may not use this Software unless you agree to abide by the terms of the
  License. The License limits your use, and you acknowledge, that the Software may not be
  modified, copied or distributed unless embedded on a Texas Instruments microcontroller
  or used solely and exclusively in conjunction with a Texas Instruments radio frequency
  transceiver, which is integrated into your product. Other than for the foregoing purpose,
  you may not use, reproduce, copy, prepare derivative works of, modify, distribute,
  perform, display or sell this Software and/or its documentation for any purpose.

  YOU FURTHER ACKNOWLEDGE AND AGREE THAT THE SOFTWARE AND DOCUMENTATION ARE PROVIDED “AS IS”
  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION, ANY
  WARRANTY OF MERCHANTABILITY, TITLE, NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE.
  IN NO EVENT SHALL TEXAS INSTRUMENTS OR ITS LICENSORS BE LIABLE OR OBLIGATED UNDER CONTRACT,
  NEGLIGENCE, STRICT LIABILITY, CONTRIBUTION, BREACH OF WARRANTY, OR OTHER LEGAL EQUITABLE
  THEORY ANY DIRECT OR INDIRECT DAMAGES OR EXPENSES INCLUDING BUT NOT LIMITED TO ANY
  INCIDENTAL, SPECIAL, INDIRECT, PUNITIVE OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST
  DATA, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY, SERVICES, OR ANY CLAIMS BY
  THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF), OR OTHER SIMILAR COSTS.

  Should you have any questions regarding your right to use this Software,
  contact Texas Instruments Incorporated at www.TI.com.
**************************************************************************************************/

/******************************************************************************
 * INCLUDES
 */
#include <string.h>
#include "bsp.h"
#include "mrfi.h"
#include "nwk_types.h"
#include "nwk_frame.h"
#include "nwk.h"
#include "nwk_app.h"
#include "nwk_globals.h"
#include "nwk_QMgmt.h"

/******************************************************************************
 * MACROS
 */
/************************* NETWORK MANIFEST CONSTANT SANITY CHECKS ****************************/
#if !defined(ACCESS_POINT) && !defined(RANGE_EXTENDER) && !defined(END_DEVICE)
#error ERROR: No SimpliciTI device type defined
#endif

#if defined(END_DEVICE) && !defined(RX_POLLS)
#define RX_USER
#endif

#ifndef MAX_HOPS
#define MAX_HOPS  3
#elif MAX_HOPS > 4
#error ERROR: MAX_HOPS must be 4 or fewer
#endif

#ifndef MAX_APP_PAYLOAD
#error ERROR: MAX_APP_PAYLOAD must be defined
#endif

#if ( MAX_PAYLOAD < MAX_FREQ_APP_FRAME )
#error ERROR: Application payload size too small for Frequency frame
#endif

#if ( MAX_PAYLOAD < MAX_JOIN_APP_FRAME )
#error ERROR: Application payload size too small for Join frame
#endif

#if ( MAX_PAYLOAD < MAX_LINK_APP_FRAME )
#error ERROR: Application payload size too small for Link frame
#endif

#if ( MAX_PAYLOAD < MAX_MGMT_APP_FRAME )
#error ERROR: Application payload size too small for Management frame
#endif

#if ( MAX_PAYLOAD < MAX_SEC_APP_FRAME )
#error ERROR: Application payload size too small for Security frame
#endif

#if ( MAX_PAYLOAD < MAX_PING_APP_FRAME )
#error ERROR: Application payload size too small for Ping frame
#endif

#if NWK_FREQ_TBL_SIZE < 1
#error ERROR: NWK_FREQ_TBL_SIZE must be > 0
#endif

/************************* END NETWORK MANIFEST CONSTANT SANITY CHECKS ************************/

/******************************************************************************
 * CONSTANTS AND DEFINES
 */
#define SYS_NUM_CONNECTIONS   (NUM_CONNECTIONS+1)

/* Increment this if the persistentContext_t structure is changed. It will help
 * detect the upgrade context: any saved values will have a version with a
 * lower number.
 */
#define  CONNTABLEINFO_STRUCTURE_VERSION   1

#define  SIZEOF_NV_OBJ   sizeof(sPersistInfo)

/******************************************************************************
 * TYPEDEFS
 */
/* This structure aggregates eveything necessary to save if we want to restore
 * the connection information later.
 */
typedef struct
{
  const uint8_t    structureVersion; /* to dectect upgrades... */
        uint8_t    numConnections;   /* count includes the UUD port/link ID */
/* The next two are used to detect overlapping port assignments. When _sending_ a
 * link frame the local port is assigned from the top down. When sending a _reply_
 * the assignment is bottom up. Overlapping assignments are rejected. That said it
 * is extremely unlikely that this will ever happen. If it does the test implemented
 * here is overly cautious (it will reject assignments when it needn't). But we leave
 * it that way on the assumption that it will never happen anyway.
 */
        uint8_t    curNextLinkPort;
        uint8_t    curMaxReplyPort;
        linkID_t   nextLinkID;
#ifdef ACCESS_POINT
        sfInfo_t   sSandFContext;
#endif
/* Connection table entries last... */
        connInfo_t connStruct[SYS_NUM_CONNECTIONS];
} persistentContext_t;

/******************************************************************************
 * LOCAL VARIABLES
 */

/* This will be overwritten if we restore the structure from NV for example.
 * Note that restoring will not permit overwriting the version element as it
 * is declared 'const'.
 */
static persistentContext_t sPersistInfo = {CONNTABLEINFO_STRUCTURE_VERSION};

/******************************************************************************
 * LOCAL FUNCTIONS
 */
static uint8_t map_lid2idx(linkID_t, uint8_t *);
static void    initializeConnection(connInfo_t *);

/******************************************************************************
 * GLOBAL VARIABLES
 */

/******************************************************************************
 * GLOBAL FUNCTIONS
 */

/******************************************************************************
 * @fn          nwk_nwkInit
 *
 * @brief       Initialize NWK conext.
 *
 * input parameters
 *
 * output parameters
 *
 * @return   Status of operation.
 */
smplStatus_t nwk_nwkInit(uint8_t (*f)(linkID_t))
{
  /* Truly ugly initialization because CCE won't initialize properly. Must
   * skip first const element. Yuk.
   */
  memset((((uint8_t *)&sPersistInfo)+1), 0x0, (sizeof(sPersistInfo)-1));
  /* OK. The zeroed elements are set. Now go back and do fixups...  */

  sPersistInfo.numConnections   = SYS_NUM_CONNECTIONS;
  sPersistInfo.curNextLinkPort  = SMPL_PORT_USER_MAX;
  sPersistInfo.curMaxReplyPort  = PORT_BASE_NUMBER;
  sPersistInfo.nextLinkID       = 1;

  /* initialize globals */
  nwk_globalsInit();

  /* initialize frame processing */
  nwk_frameInit(f);

  /* initialize queue manager */
  nwk_QInit();
	
  /* initialize each network application. */
  nwk_freqInit();
  nwk_pingInit();
  nwk_joinInit(f);
  nwk_mgmtInit();
  nwk_linkInit();
  nwk_securityInit();

  /* set up the last connection as the broadcast port mapped to the broadcast Link ID */
  if (CONNSTATE_FREE == sPersistInfo.connStruct[NUM_CONNECTIONS].connState)
  {
    sPersistInfo.connStruct[NUM_CONNECTIONS].connState   = CONNSTATE_CONNECTED;
    sPersistInfo.connStruct[NUM_CONNECTIONS].hops2target = MAX_HOPS;
    sPersistInfo.connStruct[NUM_CONNECTIONS].portRx      = SMPL_PORT_USER_BCAST;
    sPersistInfo.connStruct[NUM_CONNECTIONS].portTx      = SMPL_PORT_USER_BCAST;
    sPersistInfo.connStruct[NUM_CONNECTIONS].thisLinkID  = SMPL_LINKID_USER_UUD;
    /* set peer address to broadcast so it is used when Application sends to the broadcast Link ID */
    memcpy(sPersistInfo.connStruct[NUM_CONNECTIONS].peerAddr, nwk_getBCastAddress(), NET_ADDR_SIZE);
  }

  return SMPL_SUCCESS;
}

/******************************************************************************
 * @fn          nwk_getNextConnection
 *
 * @brief       Return the next free connection structure if on is available.
 *
 * input parameters
 *
 * output parameters
 *      The returned structure has the Rx port number populated based on the
 *      free strucure found. This is the port queried when the app wants to
 *      do a receive.
 *
 * @return   pointer to the new connInfo_t structure. NULL if there is
 *           no room in connection structure array.
 */
connInfo_t *nwk_getNextConnection()
{
  uint8_t  i;

  for (i=0; i<SYS_NUM_CONNECTIONS; ++i)
  {
    if (sPersistInfo.connStruct[i].connState == CONNSTATE_CONNECTED)
    {
      continue;
    }
    break;
  }

  if (SYS_NUM_CONNECTIONS == i)
  {
    return (connInfo_t *)0;
  }

  initializeConnection(&sPersistInfo.connStruct[i]);

  return &sPersistInfo.connStruct[i];
}

/************************************************************************************
 * @fn          initializeConnection
 *
 * @brief       Initialize some elements of a Connection table entry.
 *
 * input parameters
 * @param   pCInfo  - pointer to Connection Table entry to initialize. The file
 *                    scope variable holding the next link ID value is also updated.
 *
 * output parameters
 * @param   pCInfo  - certain elements are set to specific values.
 *
 *
 * @return   void
 */
static void initializeConnection(connInfo_t *pCInfo)
{
  linkID_t *locLID = &sPersistInfo.nextLinkID;
  uint8_t   tmp;

    /* this element will be populated during the exchange with the peer. */
  pCInfo->portTx = 0;

  pCInfo->connState  =  CONNSTATE_CONNECTED;
  pCInfo->thisLinkID = *locLID;

  /* Generate the next Link ID. This isn't foolproof. If the count wraps
   * we can end up with confusing duplicates. We can protect aginst using
   * one that is already in use but we can't protect against a stale Link ID
   * remembered by an application that doesn't know its connection has been
   * torn down. The test for 0 will hopefully never be true (indicating a wrap).
   */
  (*locLID)++;

  while (!*locLID || (*locLID == SMPL_LINKID_USER_UUD) || map_lid2idx(*locLID, &tmp))
  {
    (*locLID)++;
  }

  return;
}


/******************************************************************************
 * @fn          nwk_freeConnection
 *
 * @brief       Return the connection structure to the free pool. Currently
 *              this routine is only called when a link freame is sent and
 *              no reply is received so the freeing steps are pretty simple.
 *              But eventually this will be more complex so this place-holder
 *              is introduced.
 *
 * input parameters
 * @param   pCInfo    - pointer to entry to be freed
 *
 * output parameters
 *
 * @return   None.
 */
void nwk_freeConnection(connInfo_t *pCInfo)
{
#if NUM_CONNECTIONS > 0
  pCInfo->connState = CONNSTATE_FREE;
#endif
}

/******************************************************************************
 * @fn          nwk_getConnInfo
 *
 * @brief       Return the connection info structure to which the input Link ID maps.
 *
 * input parameters
 * @param   port    - port for which mapping desired
 *
 * output parameters
 *
 * @return   pointer to connInfo_t structure found. NULL if no mapping
 *           found or entry not valid.
 */
connInfo_t *nwk_getConnInfo(linkID_t linkID)
{
  uint8_t idx, rc;

  rc = map_lid2idx(linkID, &idx);

  return (rc && (CONNSTATE_CONNECTED == sPersistInfo.connStruct[idx].connState)) ? &sPersistInfo.connStruct[idx] : (connInfo_t *)0;
}

/******************************************************************************
 * @fn          nwk_isLinkDuplicate
 *
 * @brief       Help determine if the link has already been established.. Defense
 *              against duplicate link frames. This file owns the data structure
 *              so the comparison is done here.
 *
 * input parameters
 * @param   addr       - pointer to address of linker in question
 * @param   remotePort - remote port number provided by linker
 *
 * output parameters
 *
 * @return   Returns pointer to connection entry if the address and remote Port
 *           match an existing entry, otherwise 0.
 */
connInfo_t *nwk_isLinkDuplicate(uint8_t *addr, uint8_t remotePort)
{
#if NUM_CONNECTIONS > 0
  uint8_t       i;
  connInfo_t   *ptr = sPersistInfo.connStruct;

  for (i=0; i<NUM_CONNECTIONS; ++i,++ptr)
  {
    if (CONNSTATE_CONNECTED == ptr->connState)
    {
      if (!(memcmp(ptr->peerAddr, addr, NET_ADDR_SIZE)) &&
          (ptr->portTx == remotePort))
      {
        return ptr;
      }
    }
  }
#endif

  return (connInfo_t *)NULL;
}

/******************************************************************************
 * @fn          nwk_findAddressMatch
 *
 * @brief       Used to look for an address match in the Connection table.
 *              Match is based on source address in frame.
 *
 * input parameters
 * @param   frame    - pointer to frame in question
 *
 * output parameters
 *
 * @return   Returns non-zero if a match is found, otherwise 0.
 */
uint8_t nwk_findAddressMatch(mrfiPacket_t *frame)
{
#if NUM_CONNECTIONS > 0
  uint8_t       i;
  connInfo_t   *ptr = sPersistInfo.connStruct;

  for (i=0; i<NUM_CONNECTIONS; ++i,++ptr)
  {

    if (CONNSTATE_CONNECTED == ptr->connState)
    {
      if (!(memcmp(ptr->peerAddr, MRFI_P_SRC_ADDR(frame), NET_ADDR_SIZE)))
      {
        return 1;
      }
    }
  }
#endif

  return 0;
}

#ifdef ACCESS_POINT
/******************************************************************************
 * @fn          nwk_getSFInfoPtr
 *
 * @brief       Get pointer to store-and-forward information object kept in the
 *              NV object aggregate.
 *
 * input parameters
 *
 * output parameters
 *
 * @return   Returns pointer to the store-nad-forward object.
 */
sfInfo_t *nwk_getSFInfoPtr(void)
{
  return &sPersistInfo.sSandFContext;
}

#if defined(AP_IS_DATA_HUB)
/***************************************************************************************
 * @fn          nwk_saveJoinedDevice
 *
 * @brief       Save the address of a joining device on the Connection Table expecting
 *              a Link frame to follow. Only for when AP is a data hub. We want to
 *              use the space already allocated for a connection able entry instead
 *              of having redundant arrays for alread-joined devices in the data hub
 *              case.
 *
 * input parameters
 * @param   frame  - pointer to frame containing address or joining device.
 *
 * output parameters
 *
 * @return   Returns non-zero if this is a new device and it is saved. Returns
 *           0 if device already there or there is no room in the Connection
 *           Table.
 */
uint8_t nwk_saveJoinedDevice(mrfiPacket_t *frame)
{
  uint8_t     i;
  connInfo_t *avail = 0;
  connInfo_t *ptr   = sPersistInfo.connStruct;

  for (i=0; i<NUM_CONNECTIONS; ++i, ++ptr)
  {
    if ((ptr->connState == CONNSTATE_CONNECTED) || (ptr->connState == CONNSTATE_JOINED))
    {
      if (!memcmp(ptr->peerAddr, MRFI_P_SRC_ADDR(frame), NET_ADDR_SIZE))
      {
        return 0;
      }
    }
    else
    {
      avail = ptr;
    }
  }

  if (!avail)
  {
    return 0;
  }

  avail->connState = CONNSTATE_JOINED;
  memcpy(avail->peerAddr, MRFI_P_SRC_ADDR(frame), NET_ADDR_SIZE);

  return 1;
}

/***********************************************************************************
 * @fn          nwk_findAlreadyJoined
 *
 * @brief       Used when AP is a data hub to look for an address match in the
 *              Connection table for a device that is already enterd in the joined
 *              state. This means that the Connection Table resource is already
 *              allocated so the link-listen doesn't have to do it again. Match is
 *              based on source address in frame. Thsi shoudl only be called from
 *              the Link-listen context during the link frame reply.
 *
 *              If found the Connection Table entry is initialized as if it were
 *              found using the nwk_getNextConnection() method.
 *
 * input parameters
 * @param   frame    - pointer to frame in question
 *
 * output parameters
 *
 * @return   Returns pointer to Connection Table entry if match is found, otherwise
 *           0. This call will only fail if the Connection Table was full when the
 *           device tried to join initially.
 */
connInfo_t *nwk_findAlreadyJoined(mrfiPacket_t *frame)
{
  uint8_t     i;
  connInfo_t *ptr = sPersistInfo.connStruct;

  for (i=0; i<NUM_CONNECTIONS; ++i,++ptr)
  {
    /* Look for an entry in the JOINED state */
    if (CONNSTATE_JOINED == ptr->connState)
    {
      /* Is this it? */
      if (!(memcmp(&ptr->peerAddr, MRFI_P_SRC_ADDR(frame), NET_ADDR_SIZE)))
      {
        /* Yes. Initilize tabel entry and return the pointer. */
        initializeConnection(ptr);
        return ptr;
      }
    }
  }

  /* Nothing found... */
  return (connInfo_t *)NULL;
}
#endif  /* AP_IS_DATA_HUB */
#endif  /* ACCESS_POINT */

/******************************************************************************
 * @fn          nwk_checkConnInfo
 *
 * @brief       Do a sanity/validity check on the connection info
 *
 * input parameters
 * @param   ptr     - pointer to a valid connection info structure to validate
 * @param   which   - Tx or Rx port checked
 *
 * output parameters
 *
 * @return   Status of operation.
 */
smplStatus_t nwk_checkConnInfo(connInfo_t *ptr, uint8_t which)
{
  uint8_t  port;

  /* make sure port isn't null and that the entry is active */
  port = (CHK_RX == which) ? ptr->portRx : ptr->portTx;
  if (!port || (CONNSTATE_FREE == ptr->connState))
  {
    return SMPL_BAD_PARAM;
  }

  /* validate port number */
  if (port < PORT_BASE_NUMBER)
  {
    return SMPL_BAD_PARAM;
  }

  return SMPL_SUCCESS;
}

/******************************************************************************
 * @fn          nwk_isConnectionValid
 *
 * @brief       Do a sanity/validity check on the frame target address by
 *              validating frame against connection info
 *
 * input parameters
 * @param   frame   - pointer to frame in question
 *
 * output parameters
 * @param   lid   - link ID of found connection
 *
 * @return   0 if connection specified in frame is not valid, otherwise non-zero.
 */
uint8_t nwk_isConnectionValid(mrfiPacket_t *frame, linkID_t *lid)
{
  uint8_t       i;
  connInfo_t   *ptr  = sPersistInfo.connStruct;
  uint8_t       port = GET_FROM_FRAME(MRFI_P_PAYLOAD(frame), F_PORT_OS);

  for (i=0; i<SYS_NUM_CONNECTIONS; ++i,++ptr)
  {
    if (CONNSTATE_CONNECTED == ptr->connState)
    {
      /* check port first since we're done if the port is the user bcast port. */
      if (port == ptr->portRx)
      {
        /* yep...ports match. */
        if ((SMPL_PORT_USER_BCAST == port) || !(memcmp(ptr->peerAddr, MRFI_P_SRC_ADDR(frame), NET_ADDR_SIZE)))
        {
          uint8_t rc = 1;

          /* we're done. */
          *lid = ptr->thisLinkID;
#ifdef APP_AUTO_ACK
          /* can't ack the broadcast port... */
          if (!(SMPL_PORT_USER_BCAST == port))
          {
            if (GET_FROM_FRAME(MRFI_P_PAYLOAD(frame), F_ACK_REQ))
            {
              /* Ack requested. Send ack now */
              nwk_sendAckReply(frame, ptr->portTx);
            }
            else if (GET_FROM_FRAME(MRFI_P_PAYLOAD(frame), F_ACK_RPLY))
            {
              /* This is a reply. Signal that it was received by resetting the
               * saved transaction ID in the connection object if they match. The
               * main thread is polling this value. The setting here is in the
               * Rx ISR thread.
               */
              if (ptr->ackTID == GET_FROM_FRAME(MRFI_P_PAYLOAD(frame), F_TRACTID_OS))
              {
                ptr->ackTID = 0;
              }
              /* This causes the frame to be dropped. All ack frames are
               * dropped.
               */
              rc = 0;
            }
          }
#endif  /* APP_AUTO_ACK */
          /* Unconditionally kill the reply delay semaphore. This used to be done
           * unconditionally in the calling routine.
           */
          MRFI_PostKillSem();
          return rc;
        }
      }
    }
  }

  /* no matches */
  return 0;
}

/******************************************************************************
 * @fn          nwk_allocateLocalRxPort
 *
 * @brief       Allocate a local port on which to receive frames from a peer.
 *
 *              Allocation differs depending on whether the allocation is for
 *              a link reply frame or a link frame. In the former case we
 *              know the address of the peer so we can ensure allocating a
 *              unique port number for that address. The same port number can be
 *              used mulitple times for distinct peers. Allocations are done from
 *              the bottom of the namespace upward.
 *
 *              If allocation is for a link frame we do not yet know the peer
 *              address so we must ensure the port number is unique now.
 *              Allocations are done from the top of the namespace downward.
 *
 *              The two allocation methods track the extreme values used in each
 *              case to detect overlap, i.e., exhausted namespace. This can only
 *              happen if the number of connections supported is greater than the
 *              total namespace available.
 *
 * input parameters
 * @param   which   - Sending a link frame or a link reply frame
 * @param   newPtr  - pointer to connection info structure to be populated
 *
 * output parameters
 * @param   newPtr->portRx  - element is populated with port number.
 *
 * @return   Non-zero if port number assigned. 0 if no port available.
 */
uint8_t nwk_allocateLocalRxPort(uint8_t which, connInfo_t *newPtr)
{
#if NUM_CONNECTIONS > 0
  uint8_t     num, i;
  uint8_t     marker[NUM_CONNECTIONS];
  connInfo_t *ptr = sPersistInfo.connStruct;

  memset(&marker, 0x0, sizeof(marker));

  for (i=0; i<NUM_CONNECTIONS; ++i,++ptr)
  {
    /* Mark the port number as used unless it's a statically allocated port */
    if ((ptr != newPtr) && (CONNSTATE_CONNECTED == ptr->connState) && (ptr->portRx <= SMPL_PORT_USER_MAX))
    {
      if (LINK_SEND == which)
      {
        if (ptr->portRx > sPersistInfo.curNextLinkPort)
        {
          marker[SMPL_PORT_USER_MAX - ptr->portRx] = 1;
        }
      }
      else if (!memcmp(ptr->peerAddr, newPtr->peerAddr, NET_ADDR_SIZE))
      {
          marker[ptr->portRx - PORT_BASE_NUMBER] = 1;
      }
    }
  }

  num = 0;
  for (i=0; i<NUM_CONNECTIONS; ++i)
  {
    if (!marker[i])
    {
      if (LINK_REPLY == which)
      {
        num = PORT_BASE_NUMBER + i;
      }
      else
      {
        num = SMPL_PORT_USER_MAX - i;
      }
      break;
    }
  }

  if (LINK_REPLY == which)
  {
    /* if the number we have doesn't overlap the assignment of ports used
     * for sending link frames, use it.
     */
    if (num <= sPersistInfo.curNextLinkPort)
    {
      if (num > sPersistInfo.curMaxReplyPort)
      {
        /* remember maximum port number used */
        sPersistInfo.curMaxReplyPort = num;
      }
    }
    else
    {
      /* the port number we need has already been used in the other context. It may or
       * may not have been used for the same address but we don't bother to check...we
       * just reject the asignment. This is the overly cautious part but is extermely
       * unlikely to ever occur.
       */
      num = 0;
    }
  }
  else
  {
    /* if the number we have doesn't overlap the assignment of ports used
     * for sending link frame replies, use it.
     */
    if (num >= sPersistInfo.curMaxReplyPort)
    {
      if (num == sPersistInfo.curNextLinkPort)
      {
        sPersistInfo.curNextLinkPort--;
      }
    }
    else
    {
      /* the port number we need has already been used in the other context. It may or
       * may not have been used for the same address but we don't bother to check...we
       * just reject the asignment. This is the overly cautious part but is extermely
       * unlikely to ever occur.
       */
      num = 0;
    }
  }

  newPtr->portRx = num;

  return num;
#else
  return 0;
#endif  /* NUM_CONNECTIONS > 0 */

}

/*******************************************************************************
 * @fn          nwk_isValidReply
 *
 * @brief       Examine a frame to see if it is a valid reply when compared with
 *              expected parameters.
 *
 * input parameters
 * @param   frame      - pointer to frmae being examined
 * @param   tid        - expected transaction ID in application payload
 * @param   infoOffset - offset to payload information containing reply hint
 * @param   tidOffset  - offset to transaction ID in payload
 *
 * output parameters
 *
 * @return   reply category:
 *               SMPL_NOT_REPLY: not a reply
 *               SMPL_MY_REPLY : a reply that matches input parameters
 *               SMPL_A_REPLY  : a reply but does not match input parameters
 */
uint8_t nwk_isValidReply(mrfiPacket_t *frame, uint8_t tid, uint8_t infoOffset, uint8_t tidOffset)
{
  uint8_t rc = SMPL_NOT_REPLY;

  if ((*(MRFI_P_PAYLOAD(frame)+F_APP_PAYLOAD_OS+infoOffset) & NWK_APP_REPLY_BIT))
  {
    if ((*(MRFI_P_PAYLOAD(frame)+F_APP_PAYLOAD_OS+tidOffset) == tid) &&
        !memcmp(MRFI_P_DST_ADDR(frame), nwk_getMyAddress(), NET_ADDR_SIZE))
    {
      rc = SMPL_MY_REPLY;
    }
    else
    {
      rc = SMPL_A_REPLY;
    }
  }

  return rc;
}

/******************************************************************************
 * @fn          map_lid2idx
 *
 * @brief       Map link ID to index into connection table.
 *
 * input parameters
 * @param   lid   - Link ID to be matched
 *
 * output parameters
 * @param   idx   - populated with index into connection table
 *
 * @return   Non-zero if Link ID found and output is valid else 0.
 */
static uint8_t map_lid2idx(linkID_t lid, uint8_t *idx)
{
  uint8_t     i;
  connInfo_t *ptr = sPersistInfo.connStruct;

  for (i=0; i<SYS_NUM_CONNECTIONS; ++i, ++ptr)
  {
    if ((CONNSTATE_CONNECTED == ptr->connState) && (ptr->thisLinkID == lid))
    {
      *idx = i;
      return 1;
    }
  }

  return 0;
}

/******************************************************************************
 * @fn          nwk_findPeer
 *
 * @brief       Find connection entry for a peer
 *
 * input parameters
 * @param   peerAddr   - address of peer
 * @param   peerPort   - port on which this device was sending to peer.
 *
 * output parameters
 *
 * @return   Pointer to matching connection table entry else 0.
 */
connInfo_t *nwk_findPeer(addr_t *peerAddr, uint8_t peerPort)
{
  uint8_t     i;
  connInfo_t *ptr = sPersistInfo.connStruct;

  for (i=0; i<SYS_NUM_CONNECTIONS; ++i, ++ptr)
  {
    if (CONNSTATE_CONNECTED == ptr->connState)
    {
      if (!memcmp(peerAddr, ptr->peerAddr, NET_ADDR_SIZE))
      {
        if (peerPort == ptr->portTx)
        {
          return ptr;
        }
      }
    }
  }

  return (connInfo_t *)NULL;
}

/******************************************************************************
 * @fn          nwk_checkAppMsgTID
 *
 * @brief       Compare received TID to last-seen TID to decide whether the
 *              received message is a duplicate or we missed some.
 *
 * input parameters
 * @param   lastTID   - last-seen TID
 * @param   appMsgTID - TID from current application payload.
 *
 * output parameters
 *
 * @return   Returns zero if message with supplied TID should be discarded.
 *           Otherwise returns non-zero. In this case the message should be
 *           processed. The last-seen TID should be updated with the current
 *           application payload TID.
 *
 */
uint8_t nwk_checkAppMsgTID(appPTid_t lastTID, appPTid_t appMsgTID)
{
  uint8_t rc = 0;

  /* If the values are equal this is a duplicate. We're done. */
  if (lastTID != appMsgTID)
  {
    /* Is the new TID bigger? */
    if (appMsgTID > lastTID)
    {
      /* In this case the current payload is OK unless we've received a late
       * (duplicate) message that occurred just before the TID wrapped. This is
       * considered a duplicate and we should discard it.
       */
      if (!(DUP_TID_AFTER_WRAP(lastTID, appMsgTID)))
      {
        rc = 1;
      }
    }
    else
    {
      /* New TID is smaller. Accept the payload if this is the wrap case or we missed
       * the specific wrap frame but are still within the range in which we assume
       * we missed it. Otherwise is a genuine late frame so we should ignore it.
       */
      if (CHECK_TID_WRAP(lastTID, appMsgTID))
      {
        rc = 1;
      }
    }
  }

  return rc;
}

/******************************************************************************
 * @fn          nwk_getNumObjectFromMsg
 *
 * @brief       Get a numeric object from a message buffer. Take care of
 *              alignment and endianess issues.
 *
 * input parameters
 * @param   src     - pointer to object location in message buffer
 * @param   objSize - size of numeric object
 *
 * output parameters
 * @param   dest - pointer to numeric type variable receiving the object
 *                 contains aligned number in correct endian order on return.
 *
 * @return   void. There is no warning if there is no case for the supplied
 *                 object size. A simple copy is then done. Alignment is
 *                 guaranteed only for object size cases defined (and
 *                 vacuously size 1).
 *
 */
void nwk_getNumObjectFromMsg(void *src, void *dest, uint8_t objSize)
{
  /* Take care of alignment */
  memmove(dest, src, objSize);

  /* Take care of endianess */
  switch(objSize)
  {
    case 2:
      *((uint16_t *)dest) = ntohs(*((uint16_t *)dest));
      break;

    case 4:
      *((uint32_t *)dest) = ntohl(*((uint32_t *)dest));
      break;
  }

  return;
}

/******************************************************************************
 * @fn          nwk_putNumObjectIntoMsg
 *
 * @brief       Put a numeric object into a message buffer. Take care of
 *              alignment and endianess issues.
 *
 * input parameters
 * @param   src     - pointer to numeric type variable providing the object
 * @param   objSize - size of numeric object. Fuction works for object size 1.
 *
 * output parameters
 * @param   dest - pointer to object location in message buffer where the
 *                 correct endian order representation will be placed.
 *
 * @return   void. There is no warning if there is no case for the supplied
 *                 object size. A simple copy is then done.
 *
 */
void nwk_putNumObjectIntoMsg(void *src, void *dest, uint8_t objSize)
{

  uint8_t *ptr;
  uint16_t u16;
  uint32_t u32;

  /* Take care of endianess */
  switch(objSize)
  {
    case 1:
      ptr = (uint8_t *)src;
      break;

    case 2:
      u16 = htons(*((uint16_t *)src));
      ptr = (uint8_t *)&u16;
      break;

    case 4:
      u32 = htonl(*((uint32_t *)src));
      ptr = (uint8_t *)&u32;
      break;

    default:
      ptr = (uint8_t *)src;
      break;
  }

  /* Take care of alignment */
  memmove(dest, ptr, objSize);

  return;
}
/******************************************************************************
 * @fn          nwk_NVObj
 *
 * @brief       GET and SET support for NV object (connection context).
 *
 * input parameters
 * @param   action  - GET or SET
 * @param   val     - (GET/SET) pointer to NV IOCTL object.
 *                    (SET) NV length and version values to be used for sanity
 *                    checks.
 *
 * output parameters
 * @param   val     - (GET) Version number of NV object, size of NV object and
 *                          pointer to the connection context memory.
 *                  - (SET) Pointer to the connection context memory.
 *
 * @return   SMPL_SUCCESS
 *           SMPL_BAD_PARAM   Object version or size do not conform on a SET call
 *                            or illegal action specified.
 */
smplStatus_t nwk_NVObj(ioctlAction_t action, ioctlNVObj_t *val)
{
#ifdef NVOBJECT_SUPPORT
  smplStatus_t rc = SMPL_SUCCESS;

  if (IOCTL_ACT_GET == action)
  {
    /* Populate helper objects */
    val->objLen     = SIZEOF_NV_OBJ;
    val->objVersion = sPersistInfo.structureVersion;
    /* Set pointer to connection context if address of pointer is not null */
    if (val->objPtr)
    {
      *(val->objPtr) = (uint8_t *)&sPersistInfo;
    }
  }
  else
  {
    rc = SMPL_BAD_PARAM;
  }

  return rc;
#else  /* NVOBJECT_SUPPORT */
  return SMPL_BAD_PARAM;
#endif
}
