/* emutli.c 04-Apr-00 */
/*
 * One short try to emulate TLI with SOCKETS.
 */

/* (C)opyright (C) 1993,1996 Martin Stover, Marburg, Germany
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "net.h"
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>

#include <errno.h>

#ifndef DO_IPX_SEND_TEST 
# define DO_IPX_SEND_TEST 0
#endif

/* DO_IPX_SEND_TEST is only needed if the ipx sendmsg() bug is not patched
*
*  In linux/net/ipx/af_ipx.c routine ipx_create one line is missing.
*
*  static int ipx_create(struct socket *sock, int protocol)
*  {
*  ...
*	  sk->rcvbuf=SK_RMEM_MAX;
*	  sk->sndbuf=SK_WMEM_MAX;
*	  sk->allocation=GFP_KERNEL;
*  .......^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
*/

static int locipxdebug=0;
#ifdef FREEBSD
static struct ipx_addr fbsd_ipx;
#endif

void set_locipxdebug(int debug)
{
  locipxdebug = debug;
}

void set_sock_debug(int sock)
{
  if (setsockopt(sock, SOL_SOCKET, SO_DEBUG, &locipxdebug, sizeof(int))==-1){
    errorp(0, "setsockopt SO_DEBUG", NULL);
  }
}

#define sock2ipxadr(i, so) \
{  \
   memcpy((i)->net, &(so)->sipx_network, IPX_NET_SIZE + IPX_NODE_SIZE); \
   (i)->sock[0] = ((uint8*)(&(so)->sipx_port))[0]; \
   (i)->sock[1] = ((uint8*)(&(so)->sipx_port))[1]; \
}

#define ipx2sockadr(so, i) \
{  \
   memcpy(&(so)->sipx_network, (i)->net, IPX_NET_SIZE + IPX_NODE_SIZE); \
   ((uint8*)(&(so)->sipx_port))[0]  = (i)->sock[0]; \
   ((uint8*)(&(so)->sipx_port))[1]  = (i)->sock[1]; \
}

void set_emu_tli(void)
{
  int i = get_ini_int(100);
  if (i > -1) locipxdebug = i;
#ifdef FREEBSD
  {
    uint32  net = 0;
    uint8 buff[500];

    if( (get_ini_entry(NULL, 3, buff, sizeof(buff)))!=3 ){
      errorp(10, "set_emu_tli", "Missing ini entry 3");
      return;
    }
    sscanf((char*)buff, "%x", &net);
    ipx_netlong(fbsd_ipx)=htonl(net);
    if( ipx_iffind(NULL,&fbsd_ipx) )
      errorp(10, "set_emu_tli", "Can't find ipx interface for net=%x", net);
  }
#endif
}

int t_open(char *name, int open_mode, char * p)
{
  int opt=1;
#ifdef FREEBSD
  int sock = socket(AF_IPX, SOCK_DGRAM, 0);
#else
  int sock = socket(AF_IPX, SOCK_DGRAM, AF_IPX);
#endif  
  if (sock < 0) return(sock);
  set_sock_debug(sock);   /* debug switch */

  /* Now allow broadcast */
  if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &opt,sizeof(opt))==-1){
    errorp(0, "setsockopt SO_BROADCAST", NULL);
    close(sock);
    return(-1);
  }
  return(sock);
}

int t_bind(int sock, struct t_bind *a_in, struct t_bind *a_out)
{
   struct sockaddr_ipx ipxs;
   socklen_t maxplen=sizeof(struct sockaddr_ipx);
   memset((char*)&ipxs, 0, sizeof(struct sockaddr_ipx));
   ipxs.sipx_family=AF_IPX;

   if (a_in != (struct t_bind*) NULL
          && a_in->addr.len == sizeof(ipxAddr_t))
     ipx2sockadr(&ipxs, (ipxAddr_t*) (a_in->addr.buf));


#ifndef FREEBSD
   ipxs.sipx_network = 0L;                    /* allways default net */

   memset(ipxs.sipx_node, 0, IPX_NODE_SIZE);  /* allways default node */
                                              /* Hi Volker  :)        */
#else
   ipx_netlong(ipxs.sipx_addr)=ipx_netlong(fbsd_ipx);
   memcpy(ipxs.sipx_node, &fbsd_ipx.x_host, IPX_NODE_SIZE);
#endif
   
   if (bind(sock, (struct sockaddr*)&ipxs, sizeof(struct sockaddr_ipx))==-1) {
     errorp(0, "TLI-BIND", "socket Nr:0x%x", (int)GET_BE16(&(ipxs.sipx_port)));
     return(-1);
   }
#ifdef FREEBSD
   { 
   int on=1;
   if (setsockopt(sock, 0, SO_HEADERS_ON_INPUT, &on, sizeof(on))) {
     errorp(0, "setsockopt SO_HEADERS_ON_INPUT", NULL);
     return (-1);
   }
   if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &on,sizeof(on))==-1){
     errorp(0, "setsockopt SO_BROADCAST", NULL);
     return(-1);
   }
   }
#endif
   if (a_out != (struct t_bind*) NULL) {
     if (getsockname(sock, (struct sockaddr*)&ipxs, &maxplen) == -1){
       errorp(0, "TLI-GETSOCKNAME", NULL);
       return(-1);
     }
     sock2ipxadr((ipxAddr_t*) (a_out->addr.buf), &ipxs);
     XDPRINTF((2,0,"T_BIND ADR=%s", visable_ipx_adr((ipxAddr_t *) a_out->addr.buf ) ));
   }
   return(0);
}

int t_unbind(int sock)
{
  return(0);
}


int t_errno=0;
void t_error(char *s)
{
  errorp(0, "t_error", s);
  t_errno=0;
}

int t_close(int fd)
{
   return(close(fd));
}


int poll( struct pollfd *fds, unsigned long nfds, int timeout)
/* only POLL-IN */
{
   fd_set  readfs;
   /*
   fd_set  writefs;
   fd_set  exceptfs;
   */
   struct pollfd *p = fds;
   struct timeval time_out;
   int  k  = (int)nfds;
   int  result=-1;
   int  high_f=0;
   FD_ZERO(&readfs);
   /*
   FD_ZERO(&writefs);
   FD_ZERO(&exceptfs);
   */
   while (k--){
     if (p->fd > -1 && (p->events & POLLIN)) {
       FD_SET(p->fd, &readfs);
       if (p->fd > high_f) high_f = p->fd;
     }
     p->revents=0;
     p++;
   }

   /* mst:04-Apr-00, patch from (Jukka Ukkonen) */
   time_out.tv_sec  = timeout / 1000;
   time_out.tv_usec = (timeout % 1000) * 1000;
   if (0 > (result = select(high_f+1, &readfs, NULL, NULL, 
                        (timeout == -1) ? NULL : &time_out)))
     return(-1);

   if (result) {
     int rest=result;
     k = (int)nfds;
     p = fds;
     while (k--){
       if (p->fd > -1 && FD_ISSET(p->fd, &readfs)){
         p->revents = POLLIN;
         if (! --rest) break; /* ready */
       }
       p++;
     }
   }
   return(result);
}


inline int t_rcvudata(int fd, struct t_unitdata *ud, int *flags)
{
   struct sockaddr_ipx ipxs;
   socklen_t sz    = sizeof(struct sockaddr_ipx);
   int result;
   ipxs.sipx_family=AF_IPX;
   if (ud->addr.maxlen < sizeof(ipxAddr_t)) return(-1);
   
#ifdef FREEBSD
   {
     unsigned char tmpbuf[IPX_MAX_DATA+sizeof(struct ipx)];
     
     struct ipx *ipxhdr=(struct ipx*)tmpbuf;
     fd_set rfd;
     FD_ZERO(&rfd);
     FD_SET(fd,&rfd);
     result=select(fd+1, &rfd, NULL, NULL, NULL);
     if(result<0) return(-1);
     result = recvfrom(fd, tmpbuf, 
          min(sizeof(tmpbuf), ud->udata.maxlen+sizeof(struct ipx)), 0,
                       (struct sockaddr *) &ipxs, &sz);
     result-=sizeof(struct ipx);   
     if( result<0 ) return(-1);
     memcpy(ud->udata.buf,tmpbuf+sizeof(struct ipx),result);
     if (ud->opt.maxlen) {
        *((uint8*)ud->opt.buf) = ipxhdr->ipx_pt;
        ud->opt.len            = 1;
     }
   }
#else
   result = recvfrom(fd, ud->udata.buf, ud->udata.maxlen, 0,
                         (struct sockaddr *) &ipxs, &sz);

   if (result < 0) return(result);
   if (ud->opt.maxlen) {
      *((uint8*)ud->opt.buf) = ipxs.sipx_type;
      ud->opt.len            = 1;
   }
#endif
   
   ud->udata.len=result;
   sock2ipxadr((ipxAddr_t*) (ud->addr.buf), &ipxs);
   ud->addr.len = sizeof(ipxAddr_t);
   return(result);
}


#if DO_IPX_SEND_TEST
static int new_try;
static int anz_tries;
static struct t_unitdata *test_ud;

static void sig_alarm(int rsig)
{
  signal(rsig, SIG_IGN);
  XDPRINTF((0, 0,"GOT ALARM try=%d, sendto=%s",
        anz_tries+1, visable_ipx_adr((ipxAddr_t *) test_ud->addr.buf) ));
  if (anz_tries++ < 10) new_try++;
}
#endif


inline int t_sndudata(int fd, struct t_unitdata *ud)
{
   int result;
   struct sockaddr_ipx ipxs;
   if (ud->addr.len != sizeof(ipxAddr_t)) return(-1);

#if DO_IPX_SEND_TEST
    {
    anz_tries=1;
    test_ud  =ud;
    do {
      void (*old_sig)(int rsig) = signal(SIGALRM, sig_alarm);
      new_try  = 0;
      alarm(DO_IPX_SEND_TEST+anz_tries);
#endif
   
   memset(&ipxs, 0, sizeof(struct sockaddr_ipx));
   ipxs.sipx_family=AF_IPX;
   ipx2sockadr(&ipxs, (ipxAddr_t*) (ud->addr.buf));
#ifdef FREEBSD
   {
     struct ipx ipxdp;
     ipxdp.ipx_pt = (ud->opt.len) ? (uint8) *((uint8*)(ud->opt.buf)) : 0;
     setsockopt(fd, 0, SO_DEFAULT_HEADERS, &ipxdp, sizeof(ipxdp));
   }
#else
   ipxs.sipx_type    = (ud->opt.len) ? (uint8) *((uint8*)(ud->opt.buf)) : 0;
#endif   
   result = sendto(fd,(void *)ud->udata.buf,
          ud->udata.len, 0, (struct sockaddr *) &ipxs, sizeof(ipxs));

#if DO_IPX_SEND_TEST
     alarm(0);
     signal(SIGALRM, old_sig);
   } while (new_try);
   }
#endif

   return(result);
}


int t_rcvuderr(int fd, struct t_uderr *ud)
{
   return(0);
}

