/**HEADER********************************************************************
* 
* Copyright (c) 2008 Freescale Semiconductor;
* All Rights Reserved
*
* Copyright (c) 2004-2008 Embedded Access Inc.;
* All Rights Reserved
*
* Copyright (c) 1989-2008 ARC International;
* All Rights Reserved
*
*************************************************************************** 
*
* THIS SOFTWARE IS PROVIDED BY FREESCALE "AS IS" AND ANY EXPRESSED OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
* IN NO EVENT SHALL FREESCALE OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
* THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************
*
* $FileName: udp.c$
* $Version : 3.0.8.0$
* $Date    : Jun-23-2009$
*
* Comments:
*
*   This file contains the implementation of the User
*   Datagram Protocol.  For more details, refer to RFC768.
*
*END************************************************************************/

#include <rtcs.h>
#include "rtcs_prv.h"
#include "tcpip.h"
#include "ip_prv.h"
#include "udp_prv.h"
#include "icmp_prv.h"


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_init
* Returned Values : uint_32
* Comments        :
*     Initialize the UDP layer.
*
*END*-----------------------------------------------------------------*/

uint_32 UDP_init
   (
      void
   )
{ /* Body */
   UDP_CFG_STRUCT_PTR   UDP_cfg_ptr;
   uint_32              status;

   UDP_cfg_ptr = RTCS_mem_alloc_zero(sizeof(UDP_CFG_STRUCT));
   if (UDP_cfg_ptr == NULL)  {
      return RTCSERR_OUT_OF_MEMORY;
   }
   _mem_set_type(UDP_cfg_ptr, MEM_TYPE_UDP_CFG_STRUCT);

   RTCS_LIST_INIT(UDP_cfg_ptr->BOUND_UCB_HEAD);
   RTCS_LIST_INIT(UDP_cfg_ptr->LBOUND_UCB_HEAD);
   RTCS_LIST_INIT(UDP_cfg_ptr->GROUND_UCB_HEAD);
   RTCS_LIST_INIT(UDP_cfg_ptr->OPEN_UCB_HEAD);
   UDP_cfg_ptr->LAST_PORT = IPPORT_USERRESERVED;

   RTCS_setcfg(UDP, UDP_cfg_ptr);

   IP_open(IPPROTO_UDP, UDP_service, NULL, &status);

   return status;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_openbind_internal
* Returned Values : error code
* Comments        :
*     Opens and binds a new UCB.
*
*END*-----------------------------------------------------------------*/

uint_32 UDP_openbind_internal
   (
      uint_16              localport,  /* [IN] local UDP port */
      void (_CODE_PTR_     service)(RTCSPCB_PTR, UCB_STRUCT_PTR),
      UCB_STRUCT_PTR _PTR_ ucb         /* [OUT] new UCB */
   )
{ /* Body */
   UDP_PARM    parms;
   uint_32     error;

   error = RTCSCMD_internal(parms, UDP_open);
   if (error) {
      return error;
   } /* Endif */

   parms.udpservice = service;
   parms.ipaddress  = INADDR_ANY;
   parms.udpport    = localport;
   parms.udpword    = 0;
   error = RTCSCMD_internal(parms, UDP_bind);
   if (error) {
      RTCSCMD_internal(parms, UDP_close);
      return error;
   } /* Endif */

   *ucb = parms.ucb;
   return RTCS_OK;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_close_internal
* Returned Values : error code
* Comments        :
*     Closes a UCB.
*
*END*-----------------------------------------------------------------*/

uint_32 UDP_close_internal
   (
      UCB_STRUCT_PTR    ucb         /* [IN] old UCB */
   )
{ /* Body */
   UDP_PARM    parms;
   uint_32     error;

   parms.ucb = ucb;
   error = RTCSCMD_internal(parms, UDP_close);
   if (error) {
      return error;
   } /* Endif */

   return RTCS_OK;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_open
* Parameters      :
*
*     UCB_STRUCT_PTR    ucb         [OUT] unbound UCB
*    _CODE_PTR_         udpservice  not used
*     _ip_address       ipaddress   not used
*     uint_16           udpport     not used
*     uint_16           udpflags    not used
*     pointer           udpptr      not used
*     uint_32           udpword     not used
*
* Comments        :
*     Creates an unbound UCB.
*
*END*-----------------------------------------------------------------*/

void UDP_open
   (
      UDP_PARM_PTR      parms
   )
{ /* Body */
   UDP_CFG_STRUCT_PTR   UDP_cfg_ptr;
   UCB_STRUCT_PTR       ucb;

   UDP_cfg_ptr = RTCS_getcfg(UDP);

   ucb = RTCS_mem_alloc(sizeof(UCB_STRUCT));
   if (!ucb) {
      RTCSCMD_complete(parms, RTCSERR_UDP_UCB_ALLOC);
      return;
   } /* Endif */
   
   _mem_set_type(ucb, MEM_TYPE_UCB_STRUCT);

   ucb->PORT = 0;
   ucb->REMOTE_PORT = 0;
   ucb->SOCKET      = 0;
   ucb->REMOTE_HOST = INADDR_ANY;
   ucb->BYPASS_RX   = FALSE;
   ucb->BYPASS_TX   = FALSE;
   ucb->NONBLOCK_RX = FALSE;
   ucb->NONBLOCK_TX = FALSE;

   RTCS_QUEUE_INIT(ucb->PHEAD, ucb->PTAIL);
   RTCS_QUEUE_INIT(ucb->RHEAD, ucb->RTAIL);

   /* Initialize the list of joined multicast groups */
   RTCS_LIST_INIT(ucb->MCB_PTR);
   ucb->IGMP_LEAVEALL = NULL;

   /* Add the new UCB into the chain of ground UCBs */
   RTCS_LIST_INS(UDP_cfg_ptr->GROUND_UCB_HEAD, ucb);

   parms->ucb = ucb;
   RTCSCMD_complete(parms, RTCS_OK);
   return;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_bind
* Parameters      :
*
*     UCB_STRUCT_PTR    ucb         [IN] unbound UCB
*    _CODE_PTR_         udpservice  [IN] service function for incoming datagrams
*     _ip_address       ipaddress   [IN] local IP address
*     uint_16           udpport     [IN] local port
*     uint_16           udpflags    not used
*     pointer           udpptr      not used
*     uint_32           udpword     [IN] socket handle
*
* Comments        :
*     Binds a UCB to a local port.
*
*END*-----------------------------------------------------------------*/

void UDP_bind
   (
      UDP_PARM_PTR      parms
   )
{ /* Body */
   UDP_CFG_STRUCT_PTR   UDP_cfg_ptr;
   UCB_STRUCT_PTR       ucb, search_ptr, _PTR_ tmp_ptr, _PTR_ list_ptr;
   uint_16              port;
   _ip_address          ipaddr;

   UDP_cfg_ptr = RTCS_getcfg(UDP);

   /* Initialize the UCB */
   port = parms->udpport & 0xFFFF;
   ipaddr = parms->ipaddress;

   if (port) {

      /*
      ** If port is specified, make sure its not in use with same local
      ** IP addr. We can only have one UCB per (IPADDR/port) combination,
      ** including the case were the IPADDR is INADDR_ANY.
      */
      if (ipaddr) {
         RTCS_LIST_SEARCH(UDP_cfg_ptr->LBOUND_UCB_HEAD, search_ptr) {
            if (search_ptr->PORT == port &&
                search_ptr->IPADDR == ipaddr)
            {
               RTCSCMD_complete(parms, RTCSERR_UDP_PORT_OPEN);
               return;
            } /* Endif */
         } /* End SEARCH */

         RTCS_LIST_SEARCH(UDP_cfg_ptr->OPEN_UCB_HEAD, search_ptr) {
            if (search_ptr->PORT == port &&
                search_ptr->IPADDR == ipaddr)
            {
               RTCSCMD_complete(parms, RTCSERR_UDP_PORT_OPEN);
               return;
            } /* Endif */
         } /* End SEARCH */
      } else {
         RTCS_LIST_SEARCH(UDP_cfg_ptr->BOUND_UCB_HEAD, search_ptr) {
            if (search_ptr->PORT == port) {
               RTCSCMD_complete(parms, RTCSERR_UDP_PORT_OPEN);
               return;
            } /* Endif */
         } /* End SEARCH */
      } /* Endif */


   } else {

      port = UDP_cfg_ptr->LAST_PORT;

      for (;;) {

         /* Get next port */
         if (--port < IPPORT_RESERVED) {
            port = IPPORT_USERRESERVED;
         } /* Endif */

         /* If we tested them all, return an error */
         if (port == UDP_cfg_ptr->LAST_PORT) {
            RTCSCMD_complete(parms, RTCSERR_UDP_PORT_ALLOC);
            return;
         } /* Endif */

         /*
         ** Check if it's in use
         */
         RTCS_LIST_SEARCH(UDP_cfg_ptr->BOUND_UCB_HEAD, search_ptr) {
            if (search_ptr->PORT == port)  {
               break;
            } /* Endif */
         } /* End SEARCH */

         if (!search_ptr)  {
            RTCS_LIST_SEARCH(UDP_cfg_ptr->LBOUND_UCB_HEAD, search_ptr) {
               if (search_ptr->PORT == port)  {
                  break;
               } /* Endif */
            } /* End SEARCH */
         } /* Endif */

         if (!search_ptr)  {
            RTCS_LIST_SEARCH(UDP_cfg_ptr->OPEN_UCB_HEAD, search_ptr) {
               if (search_ptr->PORT == port)  {
                  break;
               } /* Endif */
            } /* End SEARCH */
         } /* Endif */

         /* If it's not in use, keep it */
         if (!search_ptr) {
            break;
         } /* Endif */

      } /* Endfor */

      UDP_cfg_ptr->LAST_PORT = port;

   } /* Endif */

   ucb = parms->ucb;

   ucb->PORT         = port;
   ucb->PCOUNT       = 0;
   ucb->IPADDR       = ipaddr;
   ucb->KEEP_IPADDR  = (boolean) (ipaddr != INADDR_ANY);
   ucb->SOCKET       = parms->udpword;
   ucb->SERVICE      = parms->udpservice;

   /* This is the list the UCB will be moved to */
   if (ipaddr == INADDR_ANY) {
      list_ptr = &(UDP_cfg_ptr->BOUND_UCB_HEAD);
   } else {
      list_ptr = &(UDP_cfg_ptr->LBOUND_UCB_HEAD);
   } /* Endif */

   /* Move the UCB fromt the GROUND list to the BOUND list */
   RTCS_LIST_SEARCHMOD(UDP_cfg_ptr->GROUND_UCB_HEAD, tmp_ptr) {
      if (*tmp_ptr == ucb) {
         RTCS_LIST_DEL(UDP_cfg_ptr->GROUND_UCB_HEAD,tmp_ptr);
         break;
      } /* Endif */
   } /* End SEARCH */

   RTCS_LIST_INS_END((*list_ptr), ucb, tmp_ptr);

   RTCSCMD_complete(parms, RTCS_OK);
   return;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_connect
* Parameters      :
*
*     UCB_STRUCT_PTR    ucb         [IN] bound or open UCB
*    _CODE_PTR_         udpservice  not used
*     _ip_address       ipaddress   [IN] peer IP address
*     uint_16           udpport     [IN] peer port
*     uint_16           udpflags    not used
*     pointer           udpptr      not used
*     uint_32           udpword     [IN] socket level error
*
* Comments        :
*     Connects a UCB to a peer port.
*
*END*-----------------------------------------------------------------*/

void UDP_connect
   (
      UDP_PARM_PTR      parms
   )
{ /* Body */
   UDP_CFG_STRUCT_PTR   UDP_cfg_ptr;
   UCB_STRUCT_PTR       ucb = parms->ucb, search_ptr, _PTR_ tmp_ptr;
   UCB_STRUCT_PTR       _PTR_ ins_ptr, _PTR_ del_ptr;
   uint_16              port;
   uint_32              error = parms->udpword;

   UDP_cfg_ptr = RTCS_getcfg(UDP);

   if (!error && (!(parms->udpport) || parms->ipaddress == INADDR_ANY)) {
      /* Connect requires both a port AND and IP address.  */
      error = RTCSERR_UDP_PORT_ZERO;

   } else if (!error)  {
      /* Bind a local address if none is specified */
      if (ucb->IPADDR == INADDR_ANY) {
         ucb->IPADDR = IP_route_find(parms->ipaddress, 0);
         if (ucb->IPADDR == INADDR_ANY) {
            error = RTCSERR_IP_UNREACH;
         } else {
            /*
            ** Make sure this connect request does not put two UCB's into the
            ** same configuration.
            */
            port = ucb->PORT;
            RTCS_LIST_SEARCH(UDP_cfg_ptr->OPEN_UCB_HEAD, search_ptr) {
               if (search_ptr != ucb && search_ptr->PORT == port &&
                  search_ptr->IPADDR == ucb->IPADDR)
               {
                  error = RTCSERR_UDP_PORT_OPEN;
                  ucb->IPADDR = INADDR_ANY;
                  break;
               } /* Endif */
            } /* End SEARCH */
         } /* Endif */
      } /* Endif */
   }  /*Endif */

   /*
   ** Check for errors and location of UCB. If TRUE, UCB must be moved.
   ** This is an evaluates to TRUE if one of the following is true:
   ** A. There is an error, and there is a remote port  OR
   ** B. There is no error, and there is no remote port
   */
   if (!(error) == !(ucb->REMOTE_PORT)) {
      if (ucb->REMOTE_PORT)  {
         if (ucb->KEEP_IPADDR) {
            ins_ptr = &(UDP_cfg_ptr->LBOUND_UCB_HEAD);
         } else {
            ins_ptr = &(UDP_cfg_ptr->BOUND_UCB_HEAD);
         } /* Endif */
         del_ptr = &(UDP_cfg_ptr->OPEN_UCB_HEAD);
      } else {
         ins_ptr = &(UDP_cfg_ptr->OPEN_UCB_HEAD);
         if (ucb->KEEP_IPADDR) {
            del_ptr = &(UDP_cfg_ptr->LBOUND_UCB_HEAD);
         } else {
            del_ptr = &(UDP_cfg_ptr->BOUND_UCB_HEAD);
         } /* Endif */
      } /* Endif */

      /* Move the UCB from one list to the other */
      RTCS_LIST_SEARCHMOD(*del_ptr, tmp_ptr)
      {
         if (*tmp_ptr == ucb) {
            RTCS_LIST_DEL(*del_ptr, tmp_ptr);
            break;
         } /* Endif */
      } /* End SEARCH */

      RTCS_LIST_INS_END(*ins_ptr, ucb, tmp_ptr);
   } /* Endif */

   /* If there was no error, set the peer endpoint */
   if (!error)  {
      ucb->REMOTE_HOST = parms->ipaddress;
      ucb->REMOTE_PORT = parms->udpport;
   } else {
      /* Disconnect the UCB. It is in bound state now */
      if (!ucb->KEEP_IPADDR) {
         ucb->IPADDR = INADDR_ANY;
      } /* Endif */
      ucb->REMOTE_HOST = INADDR_ANY;
      ucb->REMOTE_PORT = 0;
   } /* Endif */

   RTCSCMD_complete(parms, error);
   return;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_receive
* Parameters      :
*
*     UCB_STRUCT_PTR    ucb         [IN] UCB
*    _CODE_PTR_         udpservice  not used
*     _ip_address       ipaddress   [OUT] source IP address
*     uint_16           udpport     [OUT] source UDP port
*     uint_16           udpflags    [IN] flags
*     pointer           udpptr      [IN/OUT] data buffer
*     uint_32           udpword     [IN/OUT] length of buffer
*
* Comments        :
*     Processes a receive request from the application.
*
*END*-----------------------------------------------------------------*/

void UDP_receive
   (
      UDP_PARM_PTR      parms
   )
{ /* Body */
   RTCSPCB_PTR       pcb_ptr;

   /*
   ** Search for a waiting packet on incoming queue
   */
   RTCS_QUEUE_PEEK(parms->ucb->PHEAD, parms->ucb->PTAIL, pcb_ptr);
   if (pcb_ptr) {

      if (!(parms->udpflags & RTCS_MSG_PEEK)) {
         RTCS_QUEUE_DEL_NONEMPTY(parms->ucb->PHEAD, parms->ucb->PTAIL);
         parms->ucb->PCOUNT--;
      } /* Endif */

      /*
      ** Return request to Socket Layer with packet
      */
      parms->ipaddress = IP_source(pcb_ptr);
      parms->udpport   = UDP_source(pcb_ptr);

      if (parms->udpword > RTCSPCB_SIZE(pcb_ptr)) {
         parms->udpword = RTCSPCB_SIZE(pcb_ptr);
      } /* Endif */
      RTCSPCB_memcopy(pcb_ptr, parms->udpptr, 0, parms->udpword);

      ((SOCKET_STRUCT_PTR)parms->ucb->SOCKET)->LINK_OPTIONS.RX = pcb_ptr->LINK_OPTIONS.RX;

      if (!(parms->udpflags & RTCS_MSG_PEEK)) {
         RTCSLOG_PCB_FREE(pcb_ptr, RTCS_OK);
         RTCSPCB_free(pcb_ptr);
      } /* Endif */

      RTCSCMD_complete(parms, RTCS_OK);
      return;

   } else if (parms->ucb->NONBLOCK_RX || (parms->udpflags & RTCS_MSG_PEEK)) {

      /*
      ** Return request to Socket Layer with zero bytes
      */
      parms->ipaddress = INADDR_ANY;
      parms->udpport   = 0;
      parms->udpword   = 0;
      RTCSCMD_complete(parms, RTCS_OK);
      return;

   } else {

      /*
      ** Otherwise, enqueue the request
      */
      RTCS_QUEUE_INS(parms->ucb->RHEAD, parms->ucb->RTAIL, parms);

   } /* Endif */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_send
* Parameters      :
*
*     UCB_STRUCT_PTR    ucb         [IN] UCB
*    _CODE_PTR_         udpservice  not used
*     _ip_address       ipaddress   [IN] destination IP address
*     uint_16           udpport     [IN] destination UDP port
*     uint_16           udpflags    [IN] flags
*     pointer           udpptr      [IN] data to send
*     uint_32           udpword     [IN] length of data to send
*
* Comments        :
*     Sends a UDP packet.
*
*END*-----------------------------------------------------------------*/

void UDP_send
   (
      UDP_PARM_PTR      parms
   )
{ /* Body */
   UDP_CFG_STRUCT_PTR   UDP_cfg_ptr;
   RTCSPCB_PTR          pcb;
   register boolean     nonblock;
   uint_32              error;

   UDP_cfg_ptr = RTCS_getcfg(UDP);

   if (parms->udpport == 0) {
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_TOTAL++);
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_DISCARDED++);
      RTCSCMD_complete(parms, RTCSERR_UDP_PORT_ZERO);
      return;
   } /* Endif */

   pcb = RTCSPCB_alloc_send();
   if (pcb == NULL) {
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_TOTAL++);
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_MISSED++);
      RTCSCMD_complete(parms, RTCSERR_PCB_ALLOC);
      return;
   } /* Endif */

   //RTCSLOG_PCB_ALLOC(pcb);

   if (parms->udpflags & RTCS_MSG_O_NONBLOCK) {
      nonblock = parms->udpflags & RTCS_MSG_S_NONBLOCK;
   } else {
      nonblock = parms->ucb->NONBLOCK_TX;
   } /* Endif */

   if (nonblock) {
      uchar_ptr udpbuffer;

      udpbuffer = RTCS_mem_alloc_system(parms->udpword);
      if (!udpbuffer) {
         IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_TOTAL++);
         IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_MISSED++);
         RTCSLOG_PCB_FREE(pcb, RTCSERR_PCB_ALLOC);
         RTCSPCB_free(pcb);
         RTCSCMD_complete(parms, RTCSERR_PCB_ALLOC);
         return;
      } /* Endif */

       _mem_set_type(udpbuffer, MEM_TYPE_UDP_BUFFER);


      error = RTCSPCB_append_fragment_autofree(pcb, parms->udpword, udpbuffer);
      if (error) {
         IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_TOTAL++);
         IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_MISSED++);
         IF_UDP_STATS_ENABLED(RTCS_seterror(&UDP_cfg_ptr->STATS.ERR_TX, error, (uint_32)pcb));
         _mem_free(udpbuffer);
         RTCSLOG_PCB_FREE(pcb, error);
         RTCSPCB_free(pcb);
         RTCSCMD_complete(parms, error);
         return;
      } /* Endif */

      _mem_copy(parms->udpptr, udpbuffer, parms->udpword);

   } else {

      error = RTCSPCB_append_fragment(pcb, parms->udpword, parms->udpptr);
      if (error) {
         IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_TOTAL++);
         IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_MISSED++);
         IF_UDP_STATS_ENABLED(RTCS_seterror(&UDP_cfg_ptr->STATS.ERR_TX, error, (uint_32)pcb));
         RTCSLOG_PCB_FREE(pcb, error);
         RTCSPCB_free(pcb);
         RTCSCMD_complete(parms, error);
         return;
      } /* Endif */

      pcb->UDP_REQUEST = parms;

   } /* Endif */

   pcb->LINK_OPTIONS.TX = ((SOCKET_STRUCT_PTR)parms->ucb->SOCKET)->LINK_OPTIONS.TX;

   parms->COMMON.ERROR = RTCS_OK;
   error = UDP_send_internal(parms->ucb, parms->ucb->IPADDR, parms->ipaddress, parms->udpport, pcb, parms->udpflags);
   if (nonblock) {
      RTCSCMD_complete(parms, error);
   } else if (error) {
      parms->COMMON.ERROR = error;  /* Assumes application is lower priority */
   } /* Endif */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_send_internal
* Returned Values : error code
* Comments        :
*     Sends a UDP packet.
*
*END*-----------------------------------------------------------------*/

uint_32 UDP_send_internal
   (
      UCB_STRUCT_PTR    ucb,        /* [IN] UDP layer context      */
      _ip_address       srcaddr,    /* [IN] source IP address      */
      _ip_address       destaddr,   /* [IN] destination IP address */
      uint_16           destport,   /* [IN] destination UDP port   */
      RTCSPCB_PTR       pcb_ptr,    /* [IN] packet to send         */
      uint_32           flags       /* [IN] optional flags         */
   )
{ /* Body */
   UDP_CFG_STRUCT_PTR   UDP_cfg_ptr;
   UDP_HEADER_PTR       packet;
   register boolean     nochksum;
   uint_32              error;

   UDP_cfg_ptr = RTCS_getcfg(UDP);
   IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_TOTAL++);

   error = RTCSPCB_insert_header(pcb_ptr, sizeof(UDP_HEADER));
   if (error) {
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_MISSED++);
      IF_UDP_STATS_ENABLED(RTCS_seterror(&UDP_cfg_ptr->STATS.ERR_TX, error, (uint_32)pcb_ptr));
      RTCSLOG_PCB_FREE(pcb_ptr, error);
      RTCSPCB_free(pcb_ptr);
      return error;
   } /* Endif */

   RTCSLOG_PCB_WRITE(pcb_ptr, RTCS_LOGCTRL_PROTO(IPPROTO_UDP), 0);

   packet = (UDP_HEADER_PTR)RTCSPCB_DATA(pcb_ptr);
   RTCSPCB_DATA_TRANSPORT(pcb_ptr) = RTCSPCB_DATA(pcb_ptr);
   htons(packet->SRC_PORT, ucb->PORT);
   htons(packet->DEST_PORT, destport);
   htons(packet->LENGTH, RTCSPCB_SIZE(pcb_ptr));
   htons(packet->CHECKSUM, 0);

   if (flags & RTCS_MSG_O_NOCHKSUM) {
      nochksum = flags & RTCS_MSG_S_NOCHKSUM;
   } else {
      nochksum = ucb->BYPASS_TX;
   } /* Endif */

   if (!nochksum) {
      pcb_ptr->IP_SUM     = IP_Sum_PCB(RTCSPCB_SIZE(pcb_ptr), pcb_ptr);
      pcb_ptr->IP_SUM_PTR = packet->CHECKSUM;
   } /* Endif */

   return IP_send(pcb_ptr, IPPROTO_UDP, srcaddr, destaddr, flags);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_send_IF
* Returned Values : uint_32 (error code)
* Comments        :
*     Sends a UDP packet through a specific IP interface.
*
*END*-----------------------------------------------------------------*/

uint_32 UDP_send_IF
   (
      UCB_STRUCT_PTR    ucb,           /* [IN/OUT] UDP layer context   */
      pointer           dest_if,       /* [IN]     dest interface      */
      uint_16           dest_port,     /* [IN]     dest port           */
      RTCSPCB_PTR       pcb_ptr        /* [IN/OUT] packet to send      */
   )
{ /* Body */
   UDP_CFG_STRUCT_PTR   UDP_cfg_ptr;
   UDP_HEADER_PTR packet;
   uint_32        error;

   UDP_cfg_ptr = RTCS_getcfg(UDP);

   IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_TOTAL++);

   if (dest_port == 0) {
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_DISCARDED++);
      RTCSLOG_PCB_FREE(pcb_ptr, RTCSERR_UDP_PORT_ZERO);
      RTCSPCB_free(pcb_ptr);
      return RTCSERR_UDP_PORT_ZERO;
   } /* Endif */

   error = RTCSPCB_insert_header(pcb_ptr, sizeof(UDP_HEADER));
   if (error) {
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_TX_MISSED++);
      IF_UDP_STATS_ENABLED(RTCS_seterror(&UDP_cfg_ptr->STATS.ERR_TX, error, (uint_32)pcb_ptr));
      RTCSLOG_PCB_FREE(pcb_ptr, error);
      RTCSPCB_free(pcb_ptr);
      return error;
   } /* Endif */

   RTCSLOG_PCB_WRITE(pcb_ptr, RTCS_LOGCTRL_PROTO(IPPROTO_UDP), 0);

   packet = (UDP_HEADER_PTR)RTCSPCB_DATA(pcb_ptr);
   RTCSPCB_DATA_TRANSPORT(pcb_ptr) = RTCSPCB_DATA(pcb_ptr);
   htons(packet->SRC_PORT, ucb->PORT);
   htons(packet->DEST_PORT, dest_port);
   htons(packet->LENGTH, RTCSPCB_SIZE(pcb_ptr));
   htons(packet->CHECKSUM, 0);

   pcb_ptr->IP_SUM     = IP_Sum_PCB(RTCSPCB_SIZE(pcb_ptr), pcb_ptr);
   pcb_ptr->IP_SUM_PTR = packet->CHECKSUM;

   return IP_send_IF(pcb_ptr, IPPROTO_UDP, dest_if);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_service
* Returned Values : void
* Comments        :
*     Process incoming UDP packets.  Called from IP_service.
*
*END*-----------------------------------------------------------------*/

void UDP_service
   (
      RTCSPCB_PTR    pcb_ptr,          /* [IN/OUT] incoming packet */
      pointer        dummy             /* [IN]     not used        */
   )
{ /* Body */
   UDP_CFG_STRUCT_PTR   UDP_cfg_ptr;
   UDP_HEADER_PTR       packet;
   uint_16              port, src_port, len, sum;
   UCB_STRUCT_PTR       ucb_ptr = NULL;
   UCB_STRUCT_PTR       _PTR_ search_ptr;
   _ip_address          ipdest, ipsource;
   uint_32              error;

   UDP_cfg_ptr = RTCS_getcfg(UDP);

   IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_RX_TOTAL++);
   packet = (UDP_HEADER_PTR)RTCSPCB_DATA(pcb_ptr);
   RTCSPCB_DATA_TRANSPORT(pcb_ptr) = RTCSPCB_DATA(pcb_ptr);
   port = ntohs(packet->DEST_PORT);
   len = ntohs(packet->LENGTH);

   /*
   ** Make sure that
   **    sizeof(UDP_HEADER) <= pktlen <= RTCSPCB_SIZE(pcb)
   **
   ** Note: If RTCSPCB_SIZE(pcb) is too small, then pktlen is
   **       undefined.  However, if the following conditions pass,
   **       then RTCSPCB_SIZE(pcb) >= sizeof(UDP_HEADER), and
   **       therefore, pktlen is valid.
   */
   if (len < sizeof(UDP_HEADER)) {
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_RX_DISCARDED++);
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.ST_RX_SMALL_DGRAM++);
      RTCSLOG_PCB_FREE(pcb_ptr, RTCSERR_UDP_BAD_HEADER);
      RTCSPCB_free(pcb_ptr);
      return;
   } /* Endif */
   if (RTCSPCB_SIZE(pcb_ptr) < len) {
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_RX_DISCARDED++);
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.ST_RX_SMALL_PKT++);
      RTCSLOG_PCB_FREE(pcb_ptr, RTCSERR_UDP_BAD_HEADER);
      RTCSPCB_free(pcb_ptr);
      return;
   } /* Endif */

   RTCSLOG_PCB_READ(pcb_ptr, RTCS_LOGCTRL_PROTO(IPPROTO_UDP), 0);

   /*
   ** Port zero is an illegal destination
   */
   if (port == 0) {
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_RX_DISCARDED++);
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.ST_RX_BAD_PORT++);
      RTCSLOG_PCB_FREE(pcb_ptr, RTCSERR_UDP_BAD_HEADER);
      RTCSPCB_free(pcb_ptr);
      return;
   } /* Endif */

   /*
   ** Find a listening UCB, and move it to the front.
   ** There are multiple lists to search. If the UNICAST bit in the pcb is not
   ** set, we will only check the BOUND list, because this is the only list
   ** that can receive Multicast or Broadcast packets. No ucb that is bound to
   ** a specific local IP can receive a multicast or broadcast packet.
   */
   src_port = ntohs(packet->SRC_PORT);
   ipsource = IP_source(pcb_ptr);
   ipdest = IP_dest(pcb_ptr);

   /*
   ** Only search this list if the packet might be unicast.
   ** The search criterion is matching host PORT/IP and matching peer PORT/IP
   */
   if (pcb_ptr->TYPE & RTCSPCB_TYPE_UNICAST) {
      RTCS_LIST_PEEK(UDP_cfg_ptr->OPEN_UCB_HEAD, ucb_ptr);
      if (ucb_ptr &&
         (ucb_ptr->PORT != port ||
          ucb_ptr->REMOTE_HOST != ipsource ||
          ucb_ptr->REMOTE_PORT != src_port ||
          ucb_ptr->IPADDR != ipdest))
      {
         ucb_ptr = NULL;
         RTCS_LIST_SEARCHMOD_NOHEAD(UDP_cfg_ptr->OPEN_UCB_HEAD, search_ptr) {
            if ((*search_ptr)->PORT == port &&
            (*search_ptr)->REMOTE_HOST == ipsource &&
            (*search_ptr)->REMOTE_PORT == src_port &&
            (*search_ptr)->IPADDR == ipdest)
            {
               ucb_ptr = *search_ptr;
               RTCS_LIST_DEL(UDP_cfg_ptr->OPEN_UCB_HEAD, search_ptr);
               RTCS_LIST_INS(UDP_cfg_ptr->OPEN_UCB_HEAD, ucb_ptr);
               break;
            } /* Endif */
         } /* End SEARCH */
      } /* Endif */
   } /* Endif */

#if RTCSCFG_UDP_ENABLE_LBOUND_MULTICAST && RTCSCFG_ENABLE_IGMP
   /*
   ** If no OPEN ucb matches, search the LBOUND ucb list, 
   ** allowing multicast.  The local port must match the 
   ** packet destination port.
   */
   if (!ucb_ptr && UDP_cfg_ptr->LBOUND_UCB_HEAD)  {
      RTCS_LIST_PEEK(UDP_cfg_ptr->LBOUND_UCB_HEAD, ucb_ptr);
      if (ucb_ptr && ucb_ptr->PORT != port)  {
         ucb_ptr = NULL;
         RTCS_LIST_SEARCHMOD_NOHEAD(UDP_cfg_ptr->LBOUND_UCB_HEAD, search_ptr)  {
            if ((*search_ptr)->PORT == port)  {
               ucb_ptr = *search_ptr;
               RTCS_LIST_DEL(UDP_cfg_ptr->LBOUND_UCB_HEAD, search_ptr);
               RTCS_LIST_INS(UDP_cfg_ptr->LBOUND_UCB_HEAD, ucb_ptr);
               break;
            } /* Endif */
         } /* End SEARCH */
      }/* Endif */

      /* Check the message was multicast, and if the ucb is a member */
      if (ucb_ptr && IN_MULTICAST(ipdest) &&
         !IGMP_is_member(&ucb_ptr->MCB_PTR, pcb_ptr->IFSRC, ipdest))
      {
         ucb_ptr = NULL;
      } /* Endif */
   } /* Endif */
#else

   /*
   ** If no OPEN ucb matches, search the LBOUND ucb list.
   ** Only search this list if the packet could be unicast.
   ** The search criterion is a matching local IP/PORT
   */
   if (!ucb_ptr && UDP_cfg_ptr->LBOUND_UCB_HEAD &&
       (pcb_ptr->TYPE & RTCSPCB_TYPE_UNICAST)) {
      RTCS_LIST_PEEK(UDP_cfg_ptr->LBOUND_UCB_HEAD, ucb_ptr);
      if (ucb_ptr && ucb_ptr->PORT != port ||
          ucb_ptr->IPADDR != ipdest)
      {
         ucb_ptr = NULL;
         RTCS_LIST_SEARCHMOD_NOHEAD(UDP_cfg_ptr->LBOUND_UCB_HEAD, search_ptr)  {
            if ((*search_ptr)->PORT == port &&
                (*search_ptr)->IPADDR == ipdest)
            {
               ucb_ptr = *search_ptr;
               RTCS_LIST_DEL(UDP_cfg_ptr->LBOUND_UCB_HEAD, search_ptr);
               RTCS_LIST_INS(UDP_cfg_ptr->LBOUND_UCB_HEAD, ucb_ptr);
               break;
            } /* Endif */
         } /* End SEARCH */
      }/* Endif */
   } /* Endif */

#endif

   /*
   ** If no LBOUND ucb matches, search the BOUND ucb list
   ** Always search this list, regardless of packet type.
   ** The local port must match the packet destination port
   */
   if (!ucb_ptr && UDP_cfg_ptr->BOUND_UCB_HEAD)  {
      RTCS_LIST_PEEK(UDP_cfg_ptr->BOUND_UCB_HEAD, ucb_ptr);
      if (ucb_ptr && ucb_ptr->PORT != port)  {
         ucb_ptr = NULL;
         RTCS_LIST_SEARCHMOD_NOHEAD(UDP_cfg_ptr->BOUND_UCB_HEAD, search_ptr)  {
            if ((*search_ptr)->PORT == port)  {
               ucb_ptr = *search_ptr;
               RTCS_LIST_DEL(UDP_cfg_ptr->BOUND_UCB_HEAD, search_ptr);
               RTCS_LIST_INS(UDP_cfg_ptr->BOUND_UCB_HEAD, ucb_ptr);
               break;
            } /* Endif */
         } /* End SEARCH */
      }/* Endif */

      /* Check the message was multicast, and if the ucb is a member */
      if (ucb_ptr && IN_MULTICAST(ipdest)) {
#if RTCSCFG_ENABLE_IGMP
         if (!IGMP_is_member(&ucb_ptr->MCB_PTR, pcb_ptr->IFSRC, ipdest)) {
            ucb_ptr = NULL;
         }
#else
         ucb_ptr = NULL;
#endif
      } /* Endif */
   } /* Endif */

   /*
   ** If no UCB was found on the destination port, discard packet and generate
   ** a ICMP error packet
   */
   if (!ucb_ptr) {
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_RX_DISCARDED++);
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.ST_RX_NO_PORT++);
      #if RTCSCFG_ENABLE_ICMP
      ICMP_send_error(ICMPTYPE_DESTUNREACH, ICMPCODE_DU_PORT_UNREACH, 0, pcb_ptr, -1);
      #endif
      RTCSLOG_PCB_FREE(pcb_ptr, RTCS_OK);
      RTCSPCB_free(pcb_ptr);
      return;
   } /* Endif */


   /*
   ** Verify the checksum if present
   */
   if (ntohs(packet->CHECKSUM) && !ucb_ptr->BYPASS_RX) {
      sum = IP_Sum_pseudo(len, pcb_ptr, -1);
      sum = IP_Sum_PCB(sum, pcb_ptr);
      if (sum != 0xFFFF) {
         IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_RX_DISCARDED++);
         IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.ST_RX_BAD_CHECKSUM++);
         RTCSLOG_PCB_FREE(pcb_ptr, RTCSERR_UDP_BAD_CHECKSUM);
         RTCSPCB_free(pcb_ptr);
         return;
      } /* Endif */
   } /* Endif */

   /*
   ** Although RTCSPCB_shrink() always shrinks the first fragment,
   ** this is OK, because if RTCSPCB_SIZE(pcb_ptr) != len, then the
   ** datagram must have originated from another host, in which
   ** case the PCB only has one fragment.
   **
   ** If the packet originated on this host (and was looped back),
   ** the datagram could have several fragments, but in this case,
   ** RTCSPCB_SIZE(pcb_ptr) == len, so we'll always shrink zero bytes.
   */
   error = RTCSPCB_shrink(pcb_ptr, RTCSPCB_SIZE(pcb_ptr) - len);
   if (error) {
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_RX_ERRORS++);
      IF_UDP_STATS_ENABLED(RTCS_seterror(&UDP_cfg_ptr->STATS.ERR_RX, error, (uint_32)pcb_ptr));
      RTCSLOG_PCB_FREE(pcb_ptr, error);
      RTCSPCB_free(pcb_ptr);
      return;
   } /* Endif */
   error = RTCSPCB_next(pcb_ptr, sizeof(UDP_HEADER));
   if (error) {
      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_RX_ERRORS++);
      IF_UDP_STATS_ENABLED(RTCS_seterror(&UDP_cfg_ptr->STATS.ERR_RX, error, (uint_32)pcb_ptr));
      RTCSLOG_PCB_FREE(pcb_ptr, error);
      RTCSPCB_free(pcb_ptr);
      return;
   } /* Endif */

   ucb_ptr->SERVICE(pcb_ptr, ucb_ptr);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_process
* Returned Values : void
* Comments        :
*     Process valid incoming UDP packets.  Called from UDP_service.
*
*END*-----------------------------------------------------------------*/

void UDP_process
   (
      RTCSPCB_PTR    pcb_ptr,          /* [IN/OUT] incoming packet */
      UCB_STRUCT_PTR ucb_ptr           /* [IN]     target UCB      */
   )
{ /* Body */
   UDP_CFG_STRUCT_PTR   UDP_cfg_ptr;
   UDP_PARM_PTR         req_ptr;

   UDP_cfg_ptr = RTCS_getcfg(UDP);

   /*
   ** If there is a pending request, unblock it
   */
   RTCS_QUEUE_PEEK(ucb_ptr->RHEAD, ucb_ptr->RTAIL, req_ptr);
   if (req_ptr) {

      RTCS_QUEUE_DEL_NONEMPTY(ucb_ptr->RHEAD, ucb_ptr->RTAIL);

      req_ptr->ipaddress = IP_source(pcb_ptr);
      req_ptr->udpport   = UDP_source(pcb_ptr);

      if (req_ptr->udpword > RTCSPCB_SIZE(pcb_ptr)) {
         req_ptr->udpword = RTCSPCB_SIZE(pcb_ptr);
      } /* Endif */

      RTCSPCB_memcopy(pcb_ptr, req_ptr->udpptr, 0, req_ptr->udpword);

      ((SOCKET_STRUCT_PTR)ucb_ptr->SOCKET)->LINK_OPTIONS.RX = pcb_ptr->LINK_OPTIONS.RX;

      RTCSLOG_PCB_FREE(pcb_ptr, RTCS_OK);
      RTCSPCB_free(pcb_ptr);
      RTCSCMD_complete(req_ptr, RTCS_OK);
      return;

   } else if (ucb_ptr->PCOUNT >= _UDP_max_queue_size) {

      IF_UDP_STATS_ENABLED(UDP_cfg_ptr->STATS.COMMON.ST_RX_MISSED++);
      RTCSLOG_PCB_FREE(pcb_ptr, RTCS_OK);
      RTCSPCB_free(pcb_ptr);
      return;

   /*
   ** Otherwise, enqueue the packet
   */
   } else {

      RTCS_QUEUE_INS(ucb_ptr->PHEAD, ucb_ptr->PTAIL, pcb_ptr);
      ucb_ptr->PCOUNT++;

      /*
      ** Notify socket layer in case some task is blocked on select()
      ** waiting for this data.
      */
      SOCK_select_signal(ucb_ptr->SOCKET, UDP_SOCKET_ENQUEUED);
   } /* Endif */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_close
* Parameters      :
*
*     UCB_STRUCT_PTR    ucb         [IN] UCB
*    _CODE_PTR_         udpservice  not used
*     _ip_address       ipaddress   not used
*     uint_16           udpport     not used
*     uint_16           udpflags    not used
*     pointer           udpptr      not used
*     uint_32           udpword     not used
*
* Comments        :
*     Closes a UCB.
*
*END*-----------------------------------------------------------------*/

void UDP_close
   (
      UDP_PARM_PTR      parms
   )
{ /* Body */
   UDP_CFG_STRUCT_PTR   UDP_cfg_ptr;
   UCB_STRUCT_PTR       ucb, _PTR_ tmp;
   UCB_STRUCT_PTR _PTR_ search_ptr;       /* Temporary UCB pointer */
   UDP_PARM_PTR         req, nextreq;     /* Temporary request pointers */
   RTCSPCB_PTR          pcb, nextpcb;     /* Temporary packet pointers */
   uint_32              error;


   UDP_cfg_ptr = RTCS_getcfg(UDP);
   ucb = parms->ucb;

   /*
   ** Remove the UCB from the chain of UCBs
   */
   if (!ucb->PORT) {
      tmp = &(UDP_cfg_ptr->GROUND_UCB_HEAD);
   } else if (!ucb->REMOTE_HOST) {
      if (ucb->IPADDR) {
         tmp = &(UDP_cfg_ptr->LBOUND_UCB_HEAD);
      } else {
         tmp = &(UDP_cfg_ptr->BOUND_UCB_HEAD);
      } /* Endif */
   } else {
      tmp = &(UDP_cfg_ptr->OPEN_UCB_HEAD);
   } /* Endif */

   RTCS_LIST_SEARCHMOD(*tmp, search_ptr) {
      if (*search_ptr == ucb) {
         break;
      } /* Endif */
   } /* End SEARCH */

   if (!*search_ptr) {
      RTCSCMD_complete(parms, RTCSERR_UDP_UCB_CLOSE);
      return;
   } /* Endif */

   RTCS_LIST_DEL(tmp, search_ptr);

   /*
   ** Free all queued packets
   */
   RTCS_QUEUE_DELALL(ucb->PHEAD, ucb->PTAIL, pcb, nextpcb) {
      RTCSLOG_PCB_FREE(pcb, RTCS_OK);
      RTCSPCB_free(pcb);
   } /* End DELALL */

   /*
   ** Release pending receive requests from host
   */
   RTCS_QUEUE_DELALL(ucb->RHEAD, ucb->RTAIL, req, nextreq) {
      RTCSCMD_complete(req, RTCSERR_READ_ABORTED);
   } /* End DELALL */

   /*
   ** Notify socket layer in case some task is blocked on select()
   ** waiting for data on this socket.
   */
   SOCK_select_signal(ucb->SOCKET, UDP_SOCKET_CLOSE);


   /*
   ** Leave all joined multicast groups
   */
   if (ucb->IGMP_LEAVEALL) {
      ucb->IGMP_LEAVEALL(&ucb->MCB_PTR);
   } /* Endif */

   /*
   ** Free the UCB memory
   */
   error = _mem_free(ucb);
   if (error) {
      RTCSCMD_complete(parms, RTCSERR_UDP_UCB_FREE);
      return;
   } /* Endif */

   RTCSCMD_complete(parms, RTCS_OK);
   return;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : UDP_source
* Returned Value  : source UDP port
* Comments        :
*     Return source UDP port of packet.
*
*END*-----------------------------------------------------------------*/

uint_16 UDP_source
   (
      RTCSPCB_PTR    pcb      /* [IN] packet to find source of */
   )
{ /* Body */
   uchar_ptr srcptr = ((UDP_HEADER_PTR)RTCSPCB_DATA_TRANSPORT(pcb))->SRC_PORT;

   return ntohs(srcptr);

} /* Endbody */


/* EOF */
