/*
 * $Id: sap.c,v 1.1 1995/01/05 09:14:58 root Exp root $
 */
#include "sap.h"

SAPInfo *deleteService(SAPInfo *todel,SAPInfo *sap) {
  SAPInfo *prev = NULL, *p = sap;
  while (p) {
    if (todel == p) {
      if (prev) {
	prev->next = p->next;
	return sap;
      }
      else
	return sap->next;
    }
    prev = p;
    p = p->next;
  }
  return sap;
}

SAPInfo *findUniqueService(SAPReqEnt *sap,SAPInfo *p) {
  unsigned long net = UPKT_LONG(sap->network);
  unsigned short sock = ntohs(sap->socket);
  unsigned char *node = sap->node;
  while (p) {
    if (sock == p->socket && net == p->network &&
	!memcmp(p->node,node,sizeof p->node)) return p;
    p = p->next;
  }
  return NULL;
}

SAPInfo *addService(SAPReqEnt *nw,SAPInfo *sap,unsigned long sknet,
		    IPXRouteEnt *rp) {
  SAPInfo *p = (SAPInfo *)malloc(sizeof(SAPInfo));
  p->next = sap;
  sap = p;
  p->type = ntohs(nw->type);
  memcpy(p->name,nw->name,sizeof p->name);
  p->network = UPKT_LONG(nw->network);
  memcpy(p->node,nw->node,sizeof p->node);
  p->socket = ntohs(nw->socket);
  p->hops = rp->hops;
  p->snetwork = sknet;
  p->age = time(NULL);
  return p;
}

void read_sap(int sock,IPXRouteEnt *rtes) {
  unsigned char pkt[MAXNETBUF];
  struct sockaddr_ipx sk;
  int frlen = sizeof sk;
  SAPInfo *delSaps=NULL;

  int read_ret=recvfrom(sock,pkt,sizeof pkt,0,(struct sockaddr *)&sk,&frlen);

  if (read_ret < 0) { sysMsg(MSG_WARN,ERR_ERRNO,"recv-sap"); return; }

  if (read_ret > 0) {
    int pktype = pkt[0] * 256 + pkt[1], i = 2;
    IPXRouteEnt *netinfo = findRoute(ntohl(sk.sipx_network),net_table);

    switch (pktype) {
    case SAP_NEAR_RESP:	/* Should never get near response but just in case...*/
    case SAP_RESP:
      if (!netinfo) return;

      while (i< read_ret) {
	SAPReqEnt *sap = (SAPReqEnt *)(pkt+i);
	SAPInfo *service = findUniqueService(sap,sap_table);
	IPXRouteEnt *rp;
	i += sizeof(SAPReqEnt);

	/* Make sure that we can get to the server's network */
	rp = findRoute(UPKT_LONG(sap->network),net_table);
	if (!rp) rp = findRoute(UPKT_LONG(sap->network),rtes);

	/* skip if info comes from *WRONG* network... */
	if (rp && ntohl(sk.sipx_network) !=
	    (rp->rnetwork ? rp->rnetwork : rp->network)) continue;

	if (service) sap_table = deleteService(service,sap_table);

	if (rp && ntohs(sap->hops) < IPX_HOP_LIMIT-1) {
	  sap_table = addService(sap,sap_table,ntohl(sk.sipx_network),rp);
	  if (service)
	    free(service);
	  else { /* New server, must advertise it! */
	    service = (SAPInfo *)malloc(sizeof(SAPInfo));
	    *service = *sap_table;
	    service->next = delSaps;
	    delSaps = service;
	  }
	}
	else if (service) {
	  service->next = delSaps;
	  service->hops = IPX_HOP_LIMIT;
	  delSaps = service;
	}
      }
      break;
    case SAP_REQ:
      process_sapreq(&sk,pkt[2]*256+pkt[3]);
      break;
    case SAP_NEAR_REQ:
      process_nearreq(&sk,pkt[2]*256+pkt[3],rtes);
      break;
    default:
      fprintf(stderr,"Unrecognized IPX/SAP packet: %04x\n",pktype);
    }
  }
  if (delSaps) {
    SAPInfo *p, *next;
    broadcast_sap(delSaps);
    for (p = delSaps ; p ; p = next) {
      next = p->next;
      free(p);
    }
  }
  dump_saptab();
}

void age_sap(void) {
  SAPInfo *rmved = NULL, *p = sap_table;
  unsigned long now = time(NULL);

  while (p) {
    if (now - p->age > IPX_AGE_LIMIT) {
      SAPInfo *rp = p;
      p = p->next;
      sap_table = deleteService(rp,sap_table);
      rp->next = rmved;
      rp->hops = IPX_HOP_LIMIT;
      rmved =rp;
    }
    else p = p->next;
  }
  if (rmved) {
    SAPInfo *next;
    broadcast_sap(rmved);
    /* and free SAP table */
    for (p = rmved; p ; p = next) {
      next = p->next;
      free(p);
    }
    alarm(1);
    signal(SIGALRM,general_request);
  }
  dump_saptab();
}

void send_sapinfo(SAPInfo *saps,unsigned long net) {
  unsigned char iobuf[MAXNETBUF];
  SAPReqEnt *sap = (SAPReqEnt *)(&iobuf[2]);
  int i=0;
  iobuf[0] = 0; iobuf[1] = SAP_RESP;	/* Header info */

  while (saps) {
    if (saps->snetwork != net) {
      unsigned long snet = htonl(saps->network);
      sap[i].type = htons(saps->type);
      memcpy(sap[i].name,saps->name,sizeof sap[i].name);
      memcpy(sap[i].network,&snet,sizeof sap[i].network);
      memcpy(sap[i].node,saps->node,sizeof sap[i].node);
      sap[i].socket = htons(saps->socket);
      sap[i].hops = htons(saps->hops);
      if (++i >= MAX_SAP_INFO)  {
	broadcast_ipx(iobuf,net,IPXS_SAP,IPXT_SAP,(sizeof *sap)*i+2);
	i = 0;
      }
    }
    saps = saps->next;
  }
  if (i) broadcast_ipx(iobuf,net,IPXS_SAP,IPXT_SAP,(sizeof *sap)*i+2);
}

void broadcast_sap(SAPInfo *saps) {
  IPXRouteEnt *nets =net_table;
  while (nets) {
    send_sapinfo(saps,nets->network);
    nets = nets->next;
  }
}

void process_sapreq(struct sockaddr_ipx *sk,int type) {
  unsigned char pkt[MAXNETBUF];
  SAPReqEnt *op = (SAPReqEnt *)&pkt[2];
  int j=0;
  unsigned long srcnet = ntohl(sk->sipx_network);
  SAPInfo *sap = sap_table;

  pkt[0] = 0; pkt[1] = SAP_RESP;

  while (sap) {
    if (srcnet != sap->snetwork && (type == SAP_ANY || type == sap->type)) {
      unsigned long no = ntohl(sap->network);
      op[j].type = htons(sap->type);
      strcpy(op[j].name,sap->name);
      memcpy(op[j].network,&no,sizeof op[j].network);
      op[j].socket = htons(sap->socket);
      op[j].hops = htons(sap->hops);
      if (j >= MAX_IPX_INFO) {
	write_ipx(sk,pkt,j*sizeof(SAPReqEnt)+2);
	j = 0;
      }
    }
    sap = sap->next;
  }
  if (j) write_ipx(sk,pkt,j*sizeof(SAPReqEnt)+2);
}

void process_nearreq(struct sockaddr_ipx *sk,int type,IPXRouteEnt *rtes) {
  unsigned char pkt[2+sizeof(SAPReqEnt)];
  SAPReqEnt *op = (SAPReqEnt *)&pkt[2];
  unsigned long srcnet = ntohl(sk->sipx_network);
  unsigned short b_hops = 0xffff;
  unsigned short b_ticks = 0xffff;
  SAPInfo *sap = sap_table;

  pkt[0] = 0; pkt[1] = SAP_NEAR_RESP;

  while (sap) {
    if (srcnet != sap->snetwork && type == sap->type) {
      IPXRouteEnt *r = findRoute(sap->network,net_table);
      if (!r) r = findRoute(sap->network,rtes);
      if (r &&
	  (r->ticks < b_ticks || (r->ticks == b_ticks && r->hops < b_hops))) {
	unsigned long no = ntohl(sap->network);
	b_hops = r->hops;
	b_ticks = r->ticks;
	op->type = htons(sap->type);
	strcpy(op->name,sap->name);
	memcpy(op->network,&no,sizeof op->network);
	memcpy(op->node,sap->node,sizeof op->node);
	op->socket = htons(sap->socket);
	op->hops = htons(sap->hops);
      }
    }
    sap = sap->next;
  }
  if (b_hops != 0xffff) write_ipx(sk,pkt,sizeof(SAPReqEnt)+2);
}

void dump_saptab(void) {
  SAPInfo *p = sap_table;
  FILE *fp = fopen(SAPDUMP_FILE,"w");
  if (!fp) return;
  fprintf(fp,"IPXSAPD PID=%d\n",getpid()); 
  fputs("Type Network address            Name                 Hops Age\n",fp);
  fputs("==== ========================== ==================== ==== ===============\n",fp);

  while (p) {
    fprintf(fp,"%4X %08lX:%s:%04X %-20s %4d %s",
	    p->type,p->network,ipx_ntoa(p->node),p->socket,p->name,
	    p->hops,ctime(&p->age));
    p = p->next;
  }
  fclose(fp);
}
