/*!\file ip4_out.c
 * IPv4 output routines.
 */

/*  Copyright (c) 1997-2002 Gisle Vanem <giva@bgnett.no>
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. All advertising materials mentioning features or use of this software
 *     must display the following acknowledgement:
 *       This product includes software developed by Gisle Vanem
 *       Bergen, Norway.
 *
 *  THIS SOFTWARE IS PROVIDED BY ME (Gisle Vanem) AND CONTRIBUTORS ``AS IS''
 *  AND ANY EXPRESS 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 I OR 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.
 *
 *  Version
 *
 *  1.0 : Apr 18, 1999 : G. Vanem - created
 *  1.1 : Jan 29, 2000 : G. Vanem - added functions for
 *                       handling list of IP-fragments.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>

#include "wattcp.h"
#include "misc.h"
#include "chksum.h"
#include "pcdbug.h"
#include "pcsed.h"
#include "pctcp.h"
#include "pcstat.h"
#include "pcicmp.h"
#include "socket.h"

BYTE _default_tos      = 0;      /* Type-Of-Service */
int  _default_ttl      = 254;    /* TTL on outgoing packets */
int  _ip4_id_increment = 1;      /* Some stacks uses 5 */
BOOL _ip4_dont_frag    = FALSE;  /* Don't set DF bit on every packet */

static WORD ip4_id = 0;

/**
 * Increment IPv4-identifier before returning it.
 *
 * Note: Use _get_this_ip4_id() for sending IP fragments.
 */
WORD _get_ip4_id (void)
{
  ip4_id += _ip4_id_increment;
  return intel16 (ip4_id);
}

/**
 * Return IPv4-identifier but don't increment it.
 */
WORD _get_this_ip4_id (void)
{
  return intel16 (ip4_id);
}

/**
 * The IP4 packet transmitter.
 *
 * \retval  'data_len + sizeof(*ip)' on success.
 * \retval  0 on fail.
 * \todo Handle IP-fragments here (call ip4_send_fragments() when needed)
 */
int _ip4_output (in_Header  *ip,        /* ip-structure to fill in */
                 DWORD       src_ip,    /* from address (network order!) */
                 DWORD       dst_ip,    /* dest address (network order!) */
                 BYTE        protocol,  /* IP-protocol number */
                 BYTE        ttl,       /* Time To Live */
                 BYTE        tos,       /* Type Of Service, 0 unspecified */
                 WORD        ip_id,     /* IP identification, normally 0 */
                 int         data_len,  /* length of data after ip header */
                 const void *sock,      /* which socket is this */
                 const char *file,      /* Debug: from what file */
                 unsigned    line)      /*  and line was _ip4_output called */
{
  int len = sizeof(*ip) + data_len;
 
 /*
  * Note: the 'ip->frag_ofs' field isn't normally set here (it's
  *       cleared in eth_formatpacket() causing IP_DF bit to off).
  *       If sending fragments it's set in _ip4_send_fragments() below.
  */

  if (src_ip == 0)
      src_ip = intel (my_ip_addr);

  if (_ip4_dont_frag)
     ip->frag_ofs |= intel16 (IP_DF);

  ip->ver            = 4;
  ip->hdrlen         = sizeof(*ip) / 4;
  ip->length         = intel16 (len);
  ip->tos            = tos & IP_TOSMASK;
  ip->ttl            = ttl   ? ttl   : _default_ttl;
  ip->identification = ip_id ? ip_id : _get_ip4_id();
  ip->proto          = protocol;
  ip->source         = src_ip;
  ip->destination    = dst_ip;
  ip->checksum       = 0;
  ip->checksum       = ~CHECKSUM (ip, sizeof(*ip));

  return _eth_send (len, sock, file, line);
}

#if defined(USE_FRAGMENTS)
/*
 * Note: host is on network order.
 * No IP options supported here.
 */
static __inline in_Header *make_ip_pkt (DWORD host, BOOL first, char **data)
{
  static eth_address dest;
  in_Header *ip;

  if (first && !_arp_resolve(intel(host),&dest))
  {
    if (debug_on)
       outsnl (_LANG("make_ip_pkt(): ARP failed"));
    SOCK_DEBUGF ((", EHOSTUNREACH"));
    SOCK_ERRNO (EHOSTUNREACH);
    return (NULL);
  }

  ip = (in_Header*) _eth_formatpacket (&dest, IP4_TYPE);
  *data = (char*) (ip+1);
  return (ip);
}

/*
 * \todo Handle IP/TCP options.
 */
static in_Header *make_tcp_pkt (const _tcp_Socket *s,
                                BOOL first, char **data)
{
  in_Header *ip = (in_Header*) _eth_formatpacket (&s->his_ethaddr, IP4_TYPE);

  if (first)
  {
    tcp_Header      *tcp = (tcp_Header*) (ip+1);
    tcp_PseudoHeader ph;
    WORD tcp_len = sizeof(*tcp);

    tcp->srcPort  = intel16 (s->myport);
    tcp->dstPort  = intel16 (s->hisport);
    tcp->seqnum   = intel (s->send_next + s->send_una);
    tcp->acknum   = intel (s->recv_next);
    tcp->window   = intel16 (s->max_rx_data - s->rx_datalen);
    tcp->flags    = (BYTE) s->flags;
    tcp->unused   = 0;
    tcp->checksum = 0;
    tcp->urgent   = 0;
    tcp->offset   = tcp_len / 4;

    memset (&ph, 0, sizeof(ph));
    ph.src      = intel (s->myaddr);
    ph.dst      = intel (s->hisaddr);
    ph.protocol = TCP_PROTO;
    ph.length   = intel16 (tcp_len);
    ph.checksum = CHECKSUM (tcp, tcp_len);
    tcp->checksum = ~CHECKSUM (&ph, sizeof(ph));
    *data = (char*)tcp + sizeof(*tcp);
  }
  else
    *data = (char*)(ip+1);

  return (ip);
}

/*
 * Note: We assume 's->his_ethaddr' is filled by udp_open() or udp_demux().
 *       I.e. socket is active and not a broadcast socket.
 */
static __inline in_Header *make_udp_pkt (const _udp_Socket *s, BOOL first,
                                         unsigned len, char **data)
{
  in_Header *ip = (in_Header*) _eth_formatpacket (&s->his_ethaddr, IP4_TYPE);

  if (first)
  {
    udp_Header      *udp = (udp_Header*) (ip+1);
    tcp_PseudoHeader ph;

    udp->srcPort  = intel16 (s->myport);
    udp->dstPort  = intel16 (s->hisport);
    udp->checksum = 0;
    udp->length   = intel16 (sizeof(*udp) + len);
    memset (&ph, 0, sizeof(ph));
    ph.src = intel (s->myaddr);
    ph.dst = intel (s->hisaddr);

    if (s->sockmode & SOCK_MODE_UDPCHK)
    {
      ph.protocol = UDP_PROTO;
      ph.length   = udp->length;
      ph.checksum = CHECKSUM (udp, sizeof(*udp)) + CHECKSUM (*data, len);
      udp->checksum = ~CHECKSUM (&ph, sizeof(ph));
    }
    *data = (char*)udp + sizeof(*udp);
  }
  else
    *data = (char*)(ip+1);

  return (ip);
}


/*
 * Sends a chain of IPv4 fragments for proto. `buf' is higher-level
 * protocol (ICMP/UDP/TCP).
 * Note: `dest' is on network order. This function is only called
 *       when we're sure fragment are needed; num_frags >= 1.
 */
int _ip4_send_fragments (sock_type *s, BYTE proto, DWORD dest,
                         const void *buf, unsigned len,
                         const char *file, unsigned line)
{
  int  i, num_frags, rc = 0;
  int  frag_size, frag_ofs;
  WORD ip_id;
  WORD ip_max = _mtu - sizeof(in_Header);

  num_frags = len / ip_max;

  WATT_ASSERT (num_frags >= 1);

  if (len % ip_max)
     num_frags++;

  /* Increment IP-identifier. Use same value for all fragments
   */
  ip_id = _get_ip4_id();

  /** \todo Maybe send highest offset fragment first?
   */
  for (frag_ofs = i = 0; i < num_frags; i++)
  {
    in_Header *ip   = NULL;
    char      *data = (char*) buf;

    switch (proto)
    {
#if 0 /** \todo support sending raw IPv4 fragments */
      case IP4_TYPE:       /* raw IP type (from ip4_transmit) */
           proto = ((in_Header*)buf)->proto;
           /* fall through */
#endif
      case ICMP_PROTO:
           ip = make_ip_pkt (dest, frag_ofs == 0, &data);
           if (!ip)
              return (-1);
           break;

      case UDP_PROTO:
           ip = make_udp_pkt (&s->udp, frag_ofs == 0, len, &data);
           break;

      case TCP_PROTO:
           if (s->tcp.state != tcp_StateESTAB)
              return (0);
           ip = make_tcp_pkt (&s->tcp, frag_ofs == 0, &data);
           break;

      default:
           TCP_CONSOLE_MSG (0, ("%s (%d): Illegal protocol %04X\n",
                            __FILE__, __LINE__, proto));
           return (0);
    }

    if (i == num_frags-1)    /* last fragment */
    {
      frag_size    = len;
      ip->frag_ofs = intel16 (frag_ofs/8);
   /* memset (data+frag_size-8, 0, len); */  /* padding */
    }
    else
    {
      frag_size    = ip_max;
      ip->frag_ofs = intel16 (frag_ofs/8 | IP_MF);
    }

    memcpy (data, (const char*)buf + frag_ofs, frag_size);

    if (_ip4_output (ip, 0, dest, proto, 0, 0, ip_id, frag_size,
                     s, file, line) <= 0)
    {
      SOCK_DEBUGF ((", ENETDOWN"));
      SOCK_ERRNO (ENETDOWN);
      rc = -1;
      break;
    }
    STAT (ip4stats.ips_ofragments++);

    frag_ofs += frag_size;
    len      -= frag_size;
    rc       += frag_size;
  }

  tcp_tick (NULL);  /* !! for viewing loopback trace */
  return (rc);
}
#endif /* USE_FRAGMENTS */

