/**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: rip.c$
* $Version : 3.5.10.0$
* $Date    : Jan-22-2010$
*
* Comments:
*
*   This file contains the implementation of RIPv1 and v2,
*   the Routing Information Protocol.  For more details,
*   refer to RFC2453.
*   KNOWN BUGS:
*   - just after the shutdown advertise all the routes with an max (no infinite)
*   metric aka 15. thus the other routers doesnt have to wait for the end of
*   the timeout. question: how do i know when the board shutdown?
*   it seems impossible :( perhaps with a network call... snmp set
*   - how to do the router administration ? etc.. snmp set? and the security ?
*   - how to advertize particular route such as default. currently i dont
*   adertize it.
*   - passwd and md5 authentification extensions are not handled.
*   - i missed something in the nexthop field and the reverse poison, the
*   metric must be advertized with a infinite metric on the interface
*   from which it has been learned.
*   ok BUT only if this route has been learned with RIP. not if it is a external
*   route/no static route (because there is not source interface).
*   and perhaps other cases... like on demand circuit.
*   > > in which case the nexthop field is usefull ?
*   >
*   > One obvious case -- exporting from a foreign routing protocol (like
*   > OSPF or BGP).
*   >
*   > Another very useful case for the next hop field in RIPv2 is to allow
*   > for better handling of routes on demand circuits.  In the Annex, we
*   > put the next hop into the advertisement even if it wasn't directly
*   > reachable on that same subnet.  I think Gary wrote an applicability
*   > statement concerning this use afterwards.
*   >
*   > Suppose you have nodes A and B which can both dial into C on demand.
*   > A dials in and learns a number of routes from C and advertises them on
*   > a local network that B sees.  A hangs up from C and then B dials in.
*   >
*   > When B learns routes from C, it advertises C as the next hop.  When A
*   > sees this, it immediately sets its local copies of those routes to
*   > infinity and purges them from the world by doing a triggered
*   > advertisement.  That way, other hosts don't have to wait for A's old
*   > routes to age out and B's to become preferred.
*   TODO:
*   - the icmp/redirect route must be modified to.
*   - handle the case of removing the interface or the device.
*   - must handle several route for a given destination with the same metric.
*   - check response validity.
*   - if a route metric reaches the max, the route is down.
*   - the debian routed send the packet to the network broadcast and not
*   the limited broadcast, what is the proper behaviour ? gated routed do
*   the same. DONE i have decided it was the good one.
*   - if an static route is removed, start the deletion process. dont simply
*   remove it. thus the route is advertized with an infinite metric.
*   - implement the POLL unofficial/gated command. i.e. a response
*   without splithorizon/reverse poison.
*   - DONE send/received multicast for ripv2 (and ospf) (change igmp to do
*   it without socket. directly in ucb) seb said ok
*
*END************************************************************************/


/* local include */
#include <rtcs.h>
#include "rtcs_prv.h"
#include "tcpip.h"
#include "ip_prv.h"
#include "udp_prv.h"
#include "rip.h"
#include "route.h"
#if RTCSCFG_ENABLE_RIP

#define RND_RANGE(min,max) ((min) + RTCS_rand()%(((max)-(min))?((max)-(min)):1))
#ifndef min
#define min(a,b) ((a)<(b)?(a):(b))
#endif

#define RIP_PKT_MAXLEN      500 /* find the right number */
#define RIP_AF_INET     2   /* MUST be 2. rtcs AF_INET is 1! */
#define DEFAULT_ROUTE_TAG       0
#define NETWORK_COST            1   /* must be at least 1 */
#define SPLIT_HORIZON_WITHOUT_POISONNED_REVERSE 0
#define ADV_VERSION     RIP_V2


static void RIP_trig_upd(void);
static boolean RIP_expire_periodic(TCPIP_EVENT_PTR);
static uint_32 RIP_send_req(void);
void RIP_service(RTCSPCB_PTR, UCB_STRUCT_PTR);

IP_ROUTE_FN RIP_routefn = {
   NULL,
   RIP_new_bindif,
   RIP_init_static_rt,
   RIP_remove_rt
};

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

uint_32 RIP_init (void)
{ /* Body */
    RIP_CFG_STRUCT_PTR  cfg = RTCS_getcfg(RIP);
    uint_32 err;

    if (RTCS_getcfg(RIP)) return RTCS_OK;
    /* allocate the memory */
    cfg = RTCS_mem_alloc_zero(sizeof(*cfg));
    if (!cfg)  return RTCSERR_OUT_OF_MEMORY;
    /* bind the udp port */
    err = UDP_openbind_internal(IPPORT_RIP, RIP_service, &cfg->UCB);
    if (err){
        _mem_free(cfg);
        return err;
    }

    RTCS_setcfg(RIP, cfg);
    ROUTE_register(&RIP_routefn);

    /* Start the retransmission timer to start sending immediately */
    cfg->TIMER_PERIODIC.TIME    = 0;
    cfg->TIMER_PERIODIC.EVENT   = RIP_expire_periodic;
    cfg->TIMER_PERIODIC.PRIVATE = NULL;
    TCPIP_Event_add(&cfg->TIMER_PERIODIC);

    /* probe all the interfaces now to build the route table faster */
    err = RIP_send_req();

    return RTCS_OK;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_cpu_metric
* Returned Values :
* Comments        : rfc2453.3.9.2
*
*END*-----------------------------------------------------------------*/

static uint_32 RIP_cpu_metric(
    RTCSPCB_PTR     pcb,          /* [IN] incoming packet */
    uint_32     oldmetric
)
{ /* Body */
    uint_32 metric = NETWORK_COST + oldmetric;
/* if a cost per interface is required, do that here. But before that,
** think about the unit problem between the routing protocols.
*/
    return min(metric, RIP_MAX_METRIC);
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_build_header
* Returned Values : uint_32
* Comments        :
*
*END*-----------------------------------------------------------------*/

static void RIP_build_header(
    uchar_ptr   buf,        /* [OUT] the rip header */
    uint_8      cmd,        /* [IN] the commande */
    uint_8      version     /* [IN] the version */
)
{ /* Body */
    RIP_HEADER_PTR  hd = (RIP_HEADER_PTR)buf;
    _mem_zero(buf, sizeof(RIP_HEADER));
    htonc(hd->COMMAND, cmd);
    htonc(hd->VERSION, version);
    htons(hd->MBZ, 0);
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_build_rte
* Returned Values : uint_32
* Comments        :
*
*END*-----------------------------------------------------------------*/

static void RIP_build_rte(
    uchar_ptr   buf,    /* [OUT] the route entry */
    uint_16     family,
    uint_16     rt_tag,
    _ip_address net_addr,
    _ip_address net_mask,
    _ip_address next_hop,
    uint_32     metric,
    uint_32     rip_vers
)
{ /* Body */
    RIP_ENTRY_PTR   rte = (RIP_ENTRY_PTR)buf;
    if (rip_vers == RIP_V2){
        htons(rte->RT_TAG, rt_tag);
        htonl(rte->NETMASK, net_mask);
        htonl(rte->NEXTHOP, next_hop);
    }else{
        _mem_zero(buf, sizeof(RIP_ENTRY));
    }
    htons(rte->FAMILY, family);
    htonl(rte->NETADDR, net_addr);
    htonl(rte->METRIC, metric);
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_send_pkt
* Returned Values :
* Comments        :
*
*END*-----------------------------------------------------------------*/

static uint_32 RIP_send_pkt(
   IP_IF_PTR   ipif,   /* [IN] the outgoing interface */
   _ip_address ipdst,  /* [IN] the destination ip */
   uint_16     portdst,/* [IN] the destination port */
   uchar_ptr   data,   /* [IN] the data to send */
   uint_32     len /* [IN] the length of data to send */
)
{ /* Body */
   RTCSPCB_PTR         pcb;
   RIP_CFG_STRUCT_PTR  ripcfg = RTCS_getcfg(RIP);
   RIP_HEADER_PTR  hd = (RIP_HEADER_PTR)data;
   uint_32         err;
   _ip_address     ipsrc = IP_get_ipif_addr(ipif);

   if (ipsrc == INADDR_ANY) {
       _mem_free(data);
       return RTCS_OK;
   } /* Endif */

   /* Allocate a PCB */
   pcb = RTCSPCB_alloc_send();
   if (pcb == NULL) {
       _mem_free(data);
       return RTCSERR_PCB_ALLOC;
   } /* Endif */

   //RTCSLOG_PCB_ALLOC(pcb);

   /* add my data in the pcb */
   err = RTCSPCB_append_fragment_autofree(pcb, len, data);
   if (err) {
       _mem_free(data);
       RTCSLOG_PCB_FREE(pcb, err);
       RTCSPCB_free(pcb);
       return err;
   } /* Endif */

   RTCSLOG_PCB_WRITE(pcb, RTCS_LOGCTRL_PORT(IPPORT_RIP), ntohc(hd->VERSION));

   /* dont advertize in limited broadcast but in ip broadcast */
   if (ipdst == INADDR_BROADCAST){
       _ip_address  netmask;

       ipdst = IP_get_ipif_addr(ipif);        /* Get IP address of interface */
       IP_get_netmask(ipif, ipdst, &netmask); /* Get netmask */

       ipdst = ipdst | ~netmask;
   } /* Endif */

   /* send it */
   /* use a flag to prevent the broadcast/multicast packet from
   ** looping back to us.
   */
   return UDP_send_internal(ripcfg->UCB, ipsrc, ipdst, portdst, pcb, RTCS_MSG_NOLOOP);
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_is_valid_rte
* Returned Values :
* Comments        : rfc2453.3.9.2
*
*END*-----------------------------------------------------------------*/

static uint_32 RIP_is_valid_rte(
    RIP_ENTRY_PTR   rte
)
{ /* Body */
    uint_32 metric = ntohl(rte->METRIC);
    _ip_address netaddr = ntohl(rte->NETADDR);
    if (ntohs(rte->FAMILY) != RIP_AF_INET) return 0;
    if (metric > RIP_MAX_METRIC || metric < RIP_MIN_METRIC) return 0;
    if (IN_MULTICAST(netaddr) || IN_EXPERIMENTAL(netaddr)) return 0;
    if (IN_LOOPBACK(netaddr))  return 0;

    {
      _ip_address   nexthop = ntohl(rte->NEXTHOP);
      
      if (nexthop!= 0) {
         if (IP_is_local(NULL, nexthop)) {
            return 0;
         }
      }
    }

    return 1;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_is_valid_resp
* Returned Values :
* Comments        : rfc2453.3.9.2
*
*END*-----------------------------------------------------------------*/

static uint_32 RIP_is_valid_resp(
      RTCSPCB_PTR    pcb          /* [IN/OUT] incoming packet */
)
{ /* Body */
   uint_32 ipsrc = IP_source(pcb);
   uint_16 srcport = UDP_source(pcb);
   /*
   ** check the source port is IPPORT_ROUTE, just a weak security.
   ** On BSD-like network stack, accessing to the ports < 1024
   ** requires privileges.
   */
   if (srcport != IPPORT_RIP) return FALSE;
   /*
   ** if the ipsrc is NOT on a directly connected network which
   ** received the packet, it is not valid
   */
   if (IP_is_direct(pcb->IFSRC,ipsrc) == FALSE) return FALSE;
   /*
   ** If this packet is one of mine, it is not valid.
   ** It can happen in case of more than one interfaces are
   ** directly connected to the same network.
   */
   if (IP_is_local(NULL,ipsrc) == TRUE) return FALSE;
   return 1;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_send_resp_ipif
* Returned Values : boolean
* Comments        :
*
*END*-----------------------------------------------------------------*/

struct RIP_send_resp_ipif_struct  {
   IP_IF_PTR      ipif;
    _ip_address   ipdest;
    uint_32       rip_vers;
    uint_32       onlyChangeF;
    uchar_ptr     pkt;
    uchar_ptr     curs;
    uint_16       portdst;
};

static boolean RIP_send_resp_ipif_test
   (
      _ip_address node_ip,
      _ip_address node_mask,
      pointer     node_data,
      pointer     data
   )
{ /* Body */
   struct RIP_send_resp_ipif_struct _PTR_ testdata = data;
   IP_ROUTE_PTR                           route = node_data;
   IP_ROUTE_INDIRECT_PTR                  gate, end;
   _ip_address                            nexthop, iplist[2];
   uint_32                                metric, rtype, vcount;
   boolean                                complete = FALSE;
   pointer                                rstart[3];

   /* If this node has no entries, return */
   if (!route) {
      return FALSE;
   } /* Endif */

   rstart[0] = route->DIRECT;
   rstart[1] = route->VIRTUAL;
   rstart[2] = route->INDIRECT;

   /*
   ** If there are no connected networks, return. If the node_mask is other than
   ** 255.255.255.255 there is a connected network. There can be a virutal
   ** network even when all the netmask bits are set if the node_ip is different
   ** than the VIRTUAL ADDRESS (like in the case of PPP)
   */
   if (!~node_mask) {
      if (!(route->VIRTUAL && node_ip != route->VIRTUAL->ADDRESS)) {
         return FALSE;
      } /* Endif */

      /* Only check the VIRTUAL routes */
      rstart[0] = NULL;
      rstart[2] = NULL;
   } /* Endif */

   for (rtype = 0, gate = NULL; rtype <= 2 && !gate; rtype++) {
      gate = rstart[rtype];
   } /* Endfor */

   if (!gate) {
      return FALSE;
   } /* Endif */

   end = gate;
   iplist[1] = node_ip;
   do {
      do {
         vcount = 2;
         do {
            /* Don't gather information about tunnels */
            if (gate == rstart[1] && !route->VIRTUAL->ADDRESS) {
               break;
            } /* Endif */

            if (vcount == 1) {
               iplist[0] = route->VIRTUAL->ADDRESS;
            } /* Endif */

            /* allocate the packet memory */
            if (!testdata->pkt)  {
               testdata->pkt = testdata->curs = RTCS_mem_alloc_system(RIP_MAX_PKTLEN);
               if (!testdata->pkt) return RTCSERR_OUT_OF_MEMORY;

               /* build the header */
               RIP_build_header(testdata->curs, RIPCMD_RESPONSE, testdata->rip_vers);
               testdata->curs += sizeof(RIP_HEADER);
            } /* Endif */

            if (testdata->onlyChangeF && !gate->RIP.CHANGED_F) {
               break;
            } /* Endif */

            /* dont advertize the icmp redirect route */
            if (gate->FLAGS & RTF_REDIRECT)  {
               break;
            } /* Endif */

            metric = gate->RIP.METRIC;

#if SPLIT_HORIZON_WITHOUT_POISONNED_REVERSE
            /* split horizon. rfc2453.3.4.3.
            ** DONT advertize a route on the interface on
            ** which you received it.
            */
            if (gate->RIP.IPIFSRC == testdata->ipif)  {
               break;
            } /* Endif */
#else
            /* split horizon with poisoned reverse. rfc2453.3.4.3
            ** DO advertize a route on the interface on
            ** which you received it BUT with a infinit metric.
            */
            if (gate->RIP.IPIFSRC == testdata->ipif)  {
                metric = RIP_MAX_METRIC;
            } /* Endif */
#endif
            /* the outgoing nexthop. rfc2453.4.4. */
            nexthop = 0;
            if (IP_is_direct(testdata->ipif, gate->GATEWAY)) {
               if (end == route->INDIRECT) {
                  nexthop = gate->GATEWAY;
               } /* Endif */
            } /* Endif */


            /* build the route entry */
            RIP_build_rte(testdata->curs, RIP_AF_INET, gate->RIP.RT_TAG
                    , iplist[vcount - 1], node_mask
                    , nexthop, metric, testdata->rip_vers);

            /* check if it is valid before sending it */
            if (!RIP_is_valid_rte((RIP_ENTRY_PTR)testdata->curs)) {
               break;
            } /* Endif */


            testdata->curs += sizeof(RIP_ENTRY);

            /* if the packet is full, send it and come back later */
            if (testdata->curs-testdata->pkt >= sizeof(RIP_HEADER)+RIP_MAX_PKTLEN) {
               RIP_send_pkt(testdata->ipif, testdata->ipdest, testdata->portdst,
                  testdata->pkt, testdata->curs-testdata->pkt);
               /* pkt is freed, reset pointer to NULL */
               testdata->pkt = NULL;
            } /* Endif */
            vcount--;
         } while (end && end == (pointer)route->VIRTUAL && vcount);
         gate = gate->NEXT;
      } while (gate != end);

      for (gate = NULL; rtype <= 2 && !gate; rtype++) {
         gate = rstart[rtype];
      } /* Endfor */
      end = gate;
   } while(gate);
   return FALSE;
} /* Endbody */

static uint_32 RIP_send_resp_ipif (
    IP_IF_PTR   ipif,    /* [IN] the destination interface  */
    _ip_address ipdest,  /* [IN] the destination ip address */
    uint_16     portdst, /* [IN] the destination port */
    uint_32     rip_vers,/* [IN] the rip version */
    uint_32     onlyChangeF /* [IN] TRUE if only the changed routes are sent */
)
{ /* Body */
   IP_CFG_STRUCT_PTR                IP_cfg_ptr = RTCS_getcfg(IP);
   struct RIP_send_resp_ipif_struct testdata;

   testdata.ipif = ipif;
   testdata.ipdest = ipdest;
   testdata.portdst = portdst;
   testdata.onlyChangeF = onlyChangeF;
   testdata.rip_vers = rip_vers;
   testdata.pkt = NULL;
   testdata.curs = NULL;

   IPRADIX_walk(&IP_cfg_ptr->ROUTE_ROOT.NODE, RIP_send_resp_ipif_test, &testdata);

   /* if the packet is empty, dont send it */
   if (testdata.curs-testdata.pkt == sizeof(RIP_HEADER)){
       _mem_free(testdata.pkt);
   } else  {
      /* send the packet */
      RIP_send_pkt(ipif, ipdest, portdst, testdata.pkt,
         testdata.curs - testdata.pkt);
   } /* Endif */

   return RTCS_OK;
} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_send_resp
* Returned Values : boolean
* Comments        :
*
*END*-----------------------------------------------------------------*/
struct RIP_send_resp_struct  {
   IP_IF_PTR   localhost;
   uint_32     onlyChangeF;
   _ip_address ipdest;
   uint_32     rip_vers;
   uint_32     nb_try;
   uint_32     nb_fail;
};

static boolean RIP_send_resp_test
   (
      _ip_address node_ip,
      _ip_address node_mask,
      pointer     node_data,
      pointer     data
   )
{ /* Body */
   IP_ROUTE_PTR                        route = node_data;
   struct RIP_send_resp_struct _PTR_   testdata = data;
   uint_32                             error;

   if (route && route->DIRECT) {
      /* Don't advertise on local interface */
      if (route->DIRECT->DESTIF != testdata->localhost)  {
         testdata->nb_try++;
         error = RIP_send_resp_ipif(route->DIRECT->DESTIF, testdata->ipdest,
            IPPORT_RIP, testdata->rip_vers, testdata->onlyChangeF);
         if (error != RTCS_OK)  {
            testdata->nb_fail++;
         } /* Endif */
      } /* Endif */
   } /* Endif */

   if (route && route->VIRTUAL && route->VIRTUAL->ADDRESS) {
      /* Don't advertise on local interface */
      if (route->VIRTUAL->DESTIF != testdata->localhost)  {
         testdata->nb_try++;
         error = RIP_send_resp_ipif(route->VIRTUAL->DESTIF, testdata->ipdest,
            IPPORT_RIP, testdata->rip_vers, testdata->onlyChangeF);
         if (error != RTCS_OK)  {
            testdata->nb_fail++;
         } /* Endif */
      } /* Endif */
   } /* Endif */

   return FALSE;
} /* Endbody */

static boolean RIP_send_resp_test2
   (
      _ip_address node_ip,
      _ip_address node_mask,
      pointer     node_data,
      pointer     data
   )
{ /* Body */
   IP_ROUTE_PTR                        route = node_data;
   IP_ROUTE_INDIRECT_PTR               indirect;
   pointer                             p[3];
   uint_32                             i;

   if (!route) {
      return FALSE;
   } /* Endif */

   p[0] = route->DIRECT;
   p[1] = route->INDIRECT;
   p[2] = route->VIRTUAL;

   for (i = 0; i <= 2; i++) {
      if (!p[i]) {
         continue;
      } /* Endif */

      indirect = p[i];
      do {
         indirect->RIP.CHANGED_F = FALSE;
         indirect = indirect->NEXT;
      } while(indirect != p[i]);
   } /* Endfor */

   return FALSE;
} /* Endbody */

static uint_32 RIP_send_resp(
    uint_32 rip_vers,   /* [IN] the rip version */
    uint_32 onlyChangeF /* [IN] TRUE if only the changed routes are sent */
)
{ /* Body */
   RIP_CFG_STRUCT_PTR           ripcfg = RTCS_getcfg(RIP);
   IP_CFG_STRUCT_PTR            IP_cfg_ptr = RTCS_getcfg(IP);
   struct RIP_send_resp_struct  testdata;

   testdata.nb_try = 0;
   testdata.nb_fail = 0;
   testdata.localhost = IP_cfg_ptr->IF_LOCALHOST;
   testdata.rip_vers = rip_vers;
   testdata.onlyChangeF = onlyChangeF;

   if (rip_vers == RIP_V1) testdata.ipdest = INADDR_BROADCAST;
   else                    testdata.ipdest = INADDR_RIP_GROUP;

   IPRADIX_walk(&IP_cfg_ptr->ROUTE_ROOT.NODE, RIP_send_resp_test, &testdata);

   /* test if the routes has been all advertized (nbFail==0 && nbTry > 0)*/
   if (testdata.nb_fail)        return (uint_32)RTCS_ERROR;
   if (testdata.nb_try == 0)    return RTCS_OK;

   /* clear the flags only if required */
   if (ripcfg->RT_CHANGED_F == FALSE) return RTCS_OK;

   /* update the flag */
   ripcfg->RT_CHANGED_F = FALSE;

   /* clear the flag in all changed route entries */
   IPRADIX_walk(&IP_cfg_ptr->ROUTE_ROOT.NODE, RIP_send_resp_test2, NULL);

   return RTCS_OK;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_process_inreq
* Returned Values : void
* Comments        : check only the header
*
*END*-----------------------------------------------------------------*/

static uint_32 RIP_process_inreq(
      RTCSPCB_PTR    pcb          /* [IN/OUT] incoming packet */
)
{ /* Body */
   RIP_HEADER_PTR  hd = (RIP_HEADER_PTR)RTCSPCB_DATA(pcb);
   RIP_ENTRY_PTR   rte = (RIP_ENTRY_PTR)((uchar_ptr)hd
                   + sizeof(RIP_HEADER));
   uint_32     pktlen = RTCSPCB_SIZE(pcb);
   uint_32     err = RTCS_OK;

   /* handle the general query. rfc2453.3.9.1 */
   if (pktlen == sizeof(RIP_ENTRY) + sizeof(RIP_HEADER)
         && ntohs(rte->FAMILY) == 0
         && ntohl(rte->METRIC) == RIP_MAX_METRIC){
      _ip_address ipsrc = IP_source(pcb);
      uint_16     srcport = UDP_source(pcb);
      uint_8      version = ntohc(hd->VERSION);
      return RIP_send_resp_ipif (pcb->IFSRC,ipsrc,srcport,version,0);
   }
/* WORK: here handle particular routes request */

   return err;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_expire_gc
* Returned Values : boolean
* Comments        : rfc2453.3.8
*
*END*-----------------------------------------------------------------*/

static boolean RIP_expire_gc(
      TCPIP_EVENT_PTR   event      /* [IN/OUT] the resend event */
)
{ /* Body */
    IP_ROUTE_INDIRECT_PTR     gate = event->PRIVATE;

    if (!gate->IS_DIRECT) {
       ROUTE_remove(gate);
    } /* Endif */

    return FALSE;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_expire_rte
* Returned Values : boolean
* Comments        : rfc2453.3.8
*
*END*-----------------------------------------------------------------*/

static boolean RIP_expire_rte(
      TCPIP_EVENT_PTR   event      /* [IN/OUT] the resend event */
)
{ /* Body */
    RIP_CFG_STRUCT_PTR     ripcfg = RTCS_getcfg(RIP);
    IP_ROUTE_INDIRECT_PTR  gate = event->PRIVATE;

    /* update the route entry */
    gate->FLAGS &= ~RTF_UP;
    gate->RIP.METRIC = RIP_MAX_METRIC;
    ripcfg->RT_CHANGED_F = gate->RIP.CHANGED_F = TRUE;

    RIP_trig_upd();

    /* start the garbage collector timer. */
    gate->RIP.TIMEOUT.TIME    = RIP_TIMEOUT_GC;
    gate->RIP.TIMEOUT.EVENT   = RIP_expire_gc;
    return TRUE;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_init_timer
* Returned Values :
* Comments        : rfc2453.3.9.2
*
*END*-----------------------------------------------------------------*/

static void RIP_init_timer(
    IP_ROUTE_INDIRECT_PTR     gate,
    uint_32                   metric
)
{ /* Body */

    if (metric < RIP_MAX_METRIC){  /* init the timeout */
        gate->RIP.TIMEOUT.TIME    = RIP_TIMEOUT_DEL;
        gate->RIP.TIMEOUT.EVENT   = RIP_expire_rte;
    }else{  /* init the garbage collection */
        gate->RIP.TIMEOUT.TIME    = RIP_TIMEOUT_GC;
        gate->RIP.TIMEOUT.EVENT   = RIP_expire_gc;
    }
    gate->RIP.TIMEOUT.PRIVATE = gate;
    TCPIP_Event_add(&gate->RIP.TIMEOUT);
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_adopt_rt
* Returned Values :
* Comments        : rfc2453.3.9.2
*
*END*-----------------------------------------------------------------*/

static void RIP_adopt_rt(
    RTCSPCB_PTR            pcb,          /* [IN] incoming packet */
    IP_ROUTE_INDIRECT_PTR  gate,
    RIP_ENTRY_PTR          rte,
    uint_32                metric,
    _ip_address _PTR_      network_ptr,
    _ip_address _PTR_      netmask_ptr
)
{ /* Body */
   RIP_HEADER_PTR  hd = (RIP_HEADER_PTR)RTCSPCB_DATA(pcb);
   uint_16     rip_vers = ntohc(hd->VERSION);
   _ip_address ipsrc = IP_source(pcb);

   /* init the common part */
   *network_ptr = ntohl(rte->NETADDR);
   gate->GATEWAY = ipsrc;
   if (rip_vers == RIP_V2){
      _ip_address nexthop = ntohl(rte->NEXTHOP);
      *netmask_ptr = ntohl(rte->NETMASK);
      /* incoming nexthop. rfc2453.4.4 */
      if (nexthop && IP_is_direct(pcb->IFSRC, nexthop))
         gate->GATEWAY = nexthop;
   }else{
      *netmask_ptr = IN_DEFAULT_NET(*network_ptr);
   }

   /* if the metric is >= than the RIP_MAX_METRIC, the ourte is down */
   if (metric < RIP_MAX_METRIC) {
      gate->FLAGS |= RTF_UP;
   } else {
      gate->FLAGS &= ~RTF_UP;
   } /* Endif */

   /* init the RIP part */
   gate->RIP.METRIC = metric;
   gate->RIP.RT_TAG = ntohs(rte->RT_TAG);

   gate->RIP.CHANGED_F = TRUE;

   gate->RIP.IPIFSRC = pcb->IFSRC;

   RIP_init_timer(gate, gate->RIP.METRIC);
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_update_rt
* Returned Values :
* Comments        : rfc2453.3.9.2
*
*END*-----------------------------------------------------------------*/

static void RIP_update_rt(
    IP_ROUTE_INDIRECT_PTR     gate,
    RTCSPCB_PTR               pcb,          /* [IN] incoming packet */
    RIP_ENTRY_PTR             rte
)
{ /* Body */
   RIP_CFG_STRUCT_PTR  ripcfg = RTCS_getcfg(RIP);
   uint_32         nmetric= RIP_cpu_metric(pcb,ntohl(rte->METRIC));
   _ip_address     ipsrc = IP_source(pcb);


   /* dont modify a static route */
   if (gate->FLAGS & RTF_STATIC)
      return;

   /*
   ** if the source isnt "authoritative" (i.e. not the current
   ** gateway) and have a bigger metric, ignore the route.
   */
   if (ipsrc != gate->GATEWAY && nmetric > gate->RIP.METRIC)
      return;

   /*
   ** if the source isnt "authoritative" (i.e. not the current
   ** gateway) and have a equal metric and will "soon" timed out,
   ** update the route entry.
   */
   if (ipsrc != gate->GATEWAY && nmetric == gate->RIP.METRIC){
      uint_32 timeout = TCPIP_Event_expire(&gate->RIP.TIMEOUT);
      /* if the both metrics are infinite, dont update the route. */
      if (nmetric >= RIP_MAX_METRIC)     return;
      if (timeout > RIP_ALMOST_EXPIRED_TIME) return;
   }

   /* cancel the timeout/gc timer */
   TCPIP_Event_cancel(&gate->RIP.TIMEOUT);

   /* If the sources and the metrics are equal, just reinit the timer. */
   if (ipsrc == gate->GATEWAY && gate->RIP.METRIC == nmetric){
      if (nmetric < RIP_MAX_METRIC){
         RIP_init_timer(gate, gate->RIP.METRIC);
      }
      return;
   }

   /*
   ** Reinit the route and the timer. ipsrc no longer needed, just used
   ** to fill all of the function parameters
   */
   RIP_adopt_rt(pcb, gate, rte, nmetric, &ipsrc, &ipsrc);

   /* flag it as changed */
   ripcfg->RT_CHANGED_F = TRUE;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_create_rt
* Returned Values :
* Comments        : rfc2453.3.9.2
*
*END*-----------------------------------------------------------------*/

static void RIP_create_rt(
    RTCSPCB_PTR    pcb,          /* [IN] incoming packet */
    RIP_ENTRY_PTR   rte,
    uint_8      rip_vers
)
{ /* Body */
    uint_32                nmetric= RIP_cpu_metric(pcb,ntohl(rte->METRIC));
    RIP_CFG_STRUCT_PTR     ripcfg = RTCS_getcfg(RIP);
    IP_CFG_STRUCT_PTR      IP_cfg_ptr = RTCS_getcfg(IP);
    IP_ROUTE_INDIRECT_PTR  gate;
    _ip_address            network, netmask;

    gate = RTCS_part_alloc(IP_cfg_ptr->GATE_PARTID);
    if (!gate)  return;
    _mem_zero(gate, sizeof(*gate));

    /* init the route */
    RIP_adopt_rt(pcb, gate, rte, nmetric, &network, &netmask);

    /* insert it in the table */
    ROUTE_insert(gate, network, netmask);

    /* flag it as changed */
    ripcfg->RT_CHANGED_F = TRUE;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_process_inresp
* Returned Values :
* Comments        : rfc2453.3.9.2
*
*END*-----------------------------------------------------------------*/

static void RIP_process_inresp(
      RTCSPCB_PTR    pcb          /* [IN/OUT] incoming packet */
)
{ /* Body */
    RIP_CFG_STRUCT_PTR     ripcfg = RTCS_getcfg(RIP);
    uchar_ptr              pkt = RTCSPCB_DATA(pcb);
    RIP_HEADER_PTR         hd = (RIP_HEADER_PTR)RTCSPCB_DATA(pcb);
    RIP_ENTRY_PTR          rte = (RIP_ENTRY_PTR)(pkt + sizeof(RIP_HEADER));
    uint_32                pktlen = RTCSPCB_SIZE(pcb);
    uint_16                rip_vers = ntohc(hd->VERSION);
    IP_ROUTE_INDIRECT_PTR  gate;
    _ip_address            netaddr, netmask;

    if (pktlen < sizeof(RIP_HEADER)) {
        return;
    } /* Endif */

    pktlen -= sizeof(RIP_HEADER);

    if (RIP_is_valid_resp(pcb) == FALSE){
        return;
    } /* Endif */

    for (; pktlen >= sizeof(RIP_ENTRY); pktlen -= sizeof(RIP_ENTRY), rte++){
        if (!RIP_is_valid_rte(rte))
            continue;
        netaddr = ntohl(rte->NETADDR);
        if (rip_vers == RIP_V2) netmask = ntohl(rte->NETMASK);
        else            netmask = IN_DEFAULT_NET(netaddr);
        gate = ROUTE_get(netaddr, netmask);
        if (!gate) RIP_create_rt(pcb, rte, rip_vers);
        else        RIP_update_rt(gate, pcb, rte);
    }
    /* if this incoming response has changed routes, do a triggered update*/
    if (ripcfg->RT_CHANGED_F)
        RIP_trig_upd();

} /* Endbody */

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

void RIP_service(
      RTCSPCB_PTR    pcb,        /* [IN/OUT] incoming packet */
      UCB_STRUCT_PTR ucb_ptr     /* [IN]     target UCB      */
)
{ /* Body */
   RIP_HEADER_PTR  hd = (RIP_HEADER_PTR)RTCSPCB_DATA(pcb);


   /* check the version number */
   if (ntohc(hd->VERSION) != RIP_V1 && ntohc(hd->VERSION) != RIP_V2) {
      RTCSLOG_PCB_FREE(pcb, RTCS_OK);
      RTCSPCB_free(pcb);
   } /* Endif */

   RTCSLOG_PCB_READ(pcb, RTCS_LOGCTRL_PORT(IPPORT_RIP), ntohc(hd->VERSION));

   switch (ntohc(hd->COMMAND)) {
   case RIPCMD_REQUEST:
      RIP_process_inreq(pcb);
      break;
   case RIPCMD_RESPONSE:
      RIP_process_inresp(pcb);
      break;
   default:
      break;
   } /* Endswitch */

   RTCSLOG_PCB_FREE(pcb, RTCS_OK);
   RTCSPCB_free(pcb);

} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_send_req_ipif
* Returned Values : boolean
* Comments        :
*
*END*-----------------------------------------------------------------*/

static uint_32 RIP_send_req_ipif (
    IP_IF_PTR   ipif
)
{ /* Body */
    uchar_ptr   pkt;
    uint_32     err;
    _ip_address ipdest;

    /* allocate the packet memory */
    pkt = RTCS_mem_alloc_system(RIP_MAX_PKTLEN);
    if (!pkt) return RTCSERR_OUT_OF_MEMORY;

    /* build the header */
    RIP_build_header(pkt, RIPCMD_REQUEST, ADV_VERSION);

    /* build handle the general query. rfc2453.3.9.1 */
    RIP_build_rte(pkt+sizeof(RIP_HEADER), 0, DEFAULT_ROUTE_TAG, INADDR_ANY
                , INADDR_ANY,INADDR_ANY, RIP_MAX_METRIC
                , ADV_VERSION);

    if (ADV_VERSION == RIP_V1) ipdest = INADDR_BROADCAST;
    else                       ipdest = INADDR_RIP_GROUP;

    /* send the packet */
    err = RIP_send_pkt(ipif, ipdest, IPPORT_RIP, pkt
                , sizeof(RIP_HEADER) + sizeof(RIP_ENTRY));
    if (err != RTCS_OK){
        return err;
    }
    return RTCS_OK;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_send_req
* Returned Values : boolean
* Comments        :
*
*END*-----------------------------------------------------------------*/
struct RIP_send_req_struct  {
   IP_IF_PTR   localhost;
};

static boolean RIP_send_req_test
   (
      _ip_address node_ip,
      _ip_address node_mask,
      pointer     node_data,
      pointer     data
   )
{ /* Body */
   IP_ROUTE_PTR                     route = node_data;
   struct RIP_send_req_struct _PTR_ testdata = data;

   if (route && route->DIRECT && !~node_mask) {
      /* Don't advertise on local interface */
      if (route->DIRECT->DESTIF != testdata->localhost)  {
         RIP_send_req_ipif(route->DIRECT->DESTIF);
      } /* Endif */
   } else if (route && route->VIRTUAL && route->VIRTUAL->ADDRESS) {
      /* Don't advertise on local interface */
      if (route->VIRTUAL->DESTIF != testdata->localhost)  {
         RIP_send_req_ipif(route->VIRTUAL->DESTIF);
      } /* Endif */
      
   } /* Endif */

   return FALSE;
} /* Endbody */

static uint_32 RIP_send_req(
    void
)
{ /* Body */
   IP_CFG_STRUCT_PTR            IP_cfg_ptr = RTCS_getcfg(IP);
   struct RIP_send_req_struct   testdata;

   testdata.localhost = IP_cfg_ptr->IF_LOCALHOST;
   IPRADIX_walk(&IP_cfg_ptr->ROUTE_ROOT.NODE, RIP_send_req_test, &testdata);

   return RTCS_OK;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_expire_periodic
* Returned Values : boolean
* Comments        :
*
*END*-----------------------------------------------------------------*/

static boolean RIP_expire_periodic(
      TCPIP_EVENT_PTR   event      /* [IN/OUT] the resend event */
)
{ /* Body */

    RIP_send_resp(ADV_VERSION, FALSE);

    event->TIME = RND_RANGE(RIP_TIME_MIN_PERIODIC,
                    RIP_TIME_MAX_PERIODIC);
    return TRUE;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_expire_trig_upd
* Returned Values : boolean
* Comments        :
*
*END*-----------------------------------------------------------------*/

static boolean RIP_expire_trig_upd(
      TCPIP_EVENT_PTR   event      /* [IN/OUT] the resend event */
)
{ /* Body */
    RIP_CFG_STRUCT_PTR       ripcfg = event->PRIVATE;

    /* if no route has been during the timer run, just leave */
    if (ripcfg->RT_CHANGED_F == FALSE) {
        /* update the internal flag to know if the timer is running or not */
        ripcfg->TIMER_TRIG_UPD.PRIVATE = NULL;
        return FALSE;
    }

    /* send the changed routes */
    RIP_send_resp(ADV_VERSION, TRUE);

    /* restart the timer */
    event->TIME = RND_RANGE(RIP_TIME_MIN_TRIG_UPD, RIP_TIME_MAX_TRIG_UPD);
    return TRUE;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_trig_udp
* Returned Values : boolean
* Comments        :
*
*END*-----------------------------------------------------------------*/

static void RIP_trig_upd(
    void
)
{ /* Body */
    RIP_CFG_STRUCT_PTR     ripcfg = RTCS_getcfg(RIP);

    if (RTCS_getcfg(RIP) == NULL) return;

    /* if the timer is currently running, dont send the data */
    if (ripcfg->TIMER_TRIG_UPD.PRIVATE)
        return;

    /* send the changed routes */
    RIP_send_resp(ADV_VERSION, TRUE);

    /* start the timer */
    ripcfg->TIMER_TRIG_UPD.TIME = RND_RANGE(RIP_TIME_MIN_TRIG_UPD,
                        RIP_TIME_MAX_TRIG_UPD);
    ripcfg->TIMER_TRIG_UPD.EVENT = RIP_expire_trig_upd;
    ripcfg->TIMER_TRIG_UPD.PRIVATE = ripcfg;
    TCPIP_Event_add(&ripcfg->TIMER_TRIG_UPD);
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
*  Function Name  : RIP_new_bindif
*  Parameters     :
*  Comments       :
*                 : - can be called before RIP_init
*
*END*-----------------------------------------------------------------*/

void RIP_new_bindif (
    IP_ROUTE_DIRECT_PTR bindif /* [IN] the outgoing binded interface */
)
{ /* Body */
    RIP_CFG_STRUCT_PTR  cfg = RTCS_getcfg(RIP);
    IP_CFG_STRUCT_PTR   IP_cfg_ptr = RTCS_getcfg(IP);
    ip_mreq         mreq;

    if (!cfg) {
       return;
    } /* Endif */

    /* do not init the routing on the local interface */
    if (bindif->NETIF == IP_cfg_ptr->IF_LOCALHOST) {
       return;
    } /* Endif */

    /* init the multicast group */
    mreq.imr_multiaddr.s_addr = INADDR_RIP_GROUP;
    mreq.imr_interface.s_addr = bindif->ADDRESS;
    IGMP_join_group(&cfg->UCB->MCB_PTR, &mreq, &cfg->UCB->IGMP_LEAVEALL);

    if (bindif->NETIF) {
       RIP_send_req_ipif(bindif->NETIF);
    } else {
       RIP_send_req_ipif(bindif->DESTIF);
    } /* Endif */
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_init_static_rt
* Returned Values : void
* Comments        : - called to init the RIP part of a static route
*                     created by ipif_add()
*                 : - can be called before RIP_init
*
*END*-----------------------------------------------------------------*/

void RIP_init_static_rt
(
    IP_ROUTE_INDIRECT_PTR gate,   /* [IN] the route to init */
    uint_16 metric                /* [IN] metric of this route. [0,65535] */
)
{ /* Body */
    RIP_CFG_STRUCT_PTR  ripcfg = RTCS_getcfg(RIP);

    _mem_zero(&gate->RIP, sizeof(gate->RIP));
    gate->RIP.METRIC = metric / 4096;
    /* sanity check */
    if (gate->RIP.METRIC < RIP_MIN_METRIC) {
        gate->RIP.METRIC = RIP_MIN_METRIC;
    } /* Endif */
    if (gate->RIP.METRIC > RIP_MAX_METRIC) {
        gate->RIP.METRIC = RIP_MAX_METRIC;
    } /* Endif */
    /* no src interface for a static route */
    gate->RIP.IPIFSRC = NULL;
    gate->RIP.CHANGED_F = TRUE;
    if (ripcfg){
        ripcfg->RT_CHANGED_F = TRUE;
        RIP_trig_upd();
    }
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : RIP_remove_rt
* Returned Values : void
* Comments        : - called to close the RIP part of a route
*                     created by ROUTE_remove()
*
*END*-----------------------------------------------------------------*/

void RIP_remove_rt(IP_ROUTE_INDIRECT_PTR gate)
{ /* Body */
    /* cancel the timeout/gc timer */
    TCPIP_Event_cancel(&gate->RIP.TIMEOUT);
    _mem_zero(&gate->RIP, sizeof(gate->RIP));
} /* Endbody */

#endif
/* EOF */
