#include "ipxtund.h"

/*
  Functions for sending / receiving data to / from other routers by UDP or TCP
*/

int read_ipx_from_udp_tcp(struct Data_packet *packet, struct Local *local,
                          struct Router *router, boolean *more_data)
{
  int rec, bytes, header_compr_len;
  u_int frametype;
  u_short *usp;
  u_short len;
  u_long exp_len;
  static int from_fd;
  static u_short flags, anz, cnt;
  size_t ipx_length, ipx_off;
  static size_t carry_off, carry_len = 0;
  char ipx_dest_node[6];
  static char rec_buf[BUFSIZE], c_buf[BUFSIZE], *p_ptr;
  static enum Comtype from_com;
  
  if (debuglevel > 1) {
    fprintf(log_fp,"\n");
  }
  
  if (! *more_data) {
    /* A read returns one or more frames, which was packed into one packet by
       the other router. This functions have to returns exactly one frame
       from this packages.
       Therefore, we need two distinguish two cases:
       1. There are remaining frames in the packet not processed and delivered
          (*more_data == TRUE).
       2. There are no remaining frames (*more_data == FALSE).
   */
#ifdef DELAY_LINE
  /* DELAY_LINE msecs delay for each packet */
  usleep(1000 * DELAY_LINE);
#endif
    rec = 0;
    memset((void *)rec_buf, '\0', UDP_TCP_HDR_LEN);
    do {
      if (carry_len == 0) {
	if (router == (struct Router *)0) {
	  /* router == 0 -> UDP */
	  packet->from_sin_len = sizeof(packet->from_sin);
	  bytes = recvfrom(local->udp_fd, &rec_buf[rec], BUFSIZE - rec, 0,
		           (struct sockaddr *)&packet->from_sin,
		           &packet->from_sin_len);
	  if (bytes < 0) {
#ifdef IGNORE_UDP_CONNECT_REFUSED
            if (errno == ECONNREFUSED) {
              sleep(1);
              return -1;
            }
#endif
	    errormsg(E_LOG,"read_ipx_from_udp_tcp","recvfrom: %m");
	    local->stat.errors_r++;
	    return -1;
	  }
	  from_fd = local->udp_fd;
	  from_com = com_udp;
	}
	else {
	  /* router != 0 -> TCP */
	  bytes = read(router->tcp_fd, &rec_buf[rec], BUFSIZE - rec);
	  if (bytes < 0) {
	    errormsg(E_LOG,"read_ipx_from_udp_tcp","read: %m");
	    local->stat.errors_r++;
	    return -1;
	  }
	  from_fd = router->tcp_fd;
	  from_com = com_tcp;
	}
      }
      else {
        memmove(rec_buf, &rec_buf[carry_off], carry_len);
	bytes = carry_len;
	carry_len = 0;
	/* from_fd and from_com are correct due to previous reading */
      }
      
      if (bytes == 0) {
        /* this happens especially for a TCP connection closed by peer */
        if (router != (struct Router *)0) {
	  errormsg(E_LOG,"read_ipx_from_udp_tcp",
	           "read 0 bytes (EOF), closing TCP connection to router %s",
		   router->name);
	  close_tcp_connection(router);
	}
	return 0;
      }
      
      /* the received (not "ping") data has the format:
         | length of packet exclusive header |  (2 bytes)
	 | flags                             |  (2 bytes)
	 | no. of frames                     |  (2 bytes)
	 | magic                             |  (4 bytes)
	 | compressed data                   |  (length bytes)
      */
      len = 0;
      rec += bytes;
      if (rec >= UDP_TCP_HDR_LEN) {
	usp = (u_short *)rec_buf;
	len = ntohs(*usp);
	usp++;
	flags = ntohs(*usp);
	usp++;
	anz = ntohs(*usp);
	
#ifndef NO_TCP
	/* check if it is a "ping" by comparing magic */
	if (router != (struct Router *)0 &&
	    memcmp(&rec_buf[6], TCP_PING_MAGIC, 4) == 0) {
	  /* "ping" -> send a reply back to sender */
	  echo_tcp_ping(rec_buf, router);
	  if (debuglevel > 1) {
	    log("got ping from router %s, send reply back", router->name);
	  }
	  carry_len = rec - (len + UDP_TCP_HDR_LEN);
	  carry_off = rec - carry_len;
	  return 0;
	}
#endif
      
	/* check if it is a dummy packet */
	if (memcmp(&rec_buf[6], DUMMY_MAGIC, 4) == 0) {
	  carry_len = rec - (len + UDP_TCP_HDR_LEN);
	  carry_off = rec - carry_len;
	  return 0;
	}
            
	/* check if it is a data packet */
	if (memcmp(&rec_buf[6], UDP_TCP_MAGIC, 4) != 0) {
	  errormsg(E_LOG,"read_ipx_from_udp_tcp",
		   "packet (%d bytes) dropped: wrong magic", bytes);
	  local->stat.errors_r++;
	  return -1;
	}
      }
      /* we want the complete packet */
    } while (rec < len + UDP_TCP_HDR_LEN);

    if (rec != len + UDP_TCP_HDR_LEN) {
      if (rec > len + UDP_TCP_HDR_LEN) {
        carry_len = rec - (len + UDP_TCP_HDR_LEN);
	carry_off = rec - carry_len;
      }
      else {
        errormsg(E_LOG,"read_ipx_from_udp_tcp",
        "packet dropped: udp/tcp packet length mismatch (%d to %d)",
	        len + UDP_TCP_HDR_LEN, rec);
        local->stat.errors_r++;
        return -1;
      }
    }

    /* expand data if compressed and put it into the static buffer c_buf */
    if (flags & COMPRESSED) {
      exp_len = BUFSIZE;
      if (uncompr(c_buf, &exp_len, rec_buf +UDP_TCP_HDR_LEN, len) != Z_OK) {
        errormsg(E_LOG,"read_ipx_from_udp_tcp","uncompress failed");
        return -1;
      }
      if (debuglevel > 1) {
        fprintf(log_fp,"uncompressing data (%d -> %lu bytes)\n", len,
                        exp_len);
      }
    }
    else {
      memcpy(c_buf, rec_buf + UDP_TCP_HDR_LEN, len);
      exp_len = len;
    }
    p_ptr = c_buf;	/* pointer to next frame */
    cnt = 0;		/* no. of already from packet extracted frames */

    local->stat.packets_r++;
    local->stat.bytes_r += rec;
    if (rec > local->stat.max_bytes_r) {
      local->stat.max_bytes_r = rec;
    }
  } /* more_data */
  
  /* the data packet stored in c_buf has the format:
       | frame 0 length |  (2 bytes)
       | frame 0 data   |  (legth bytes)
       | frame 1 length |  (2 bytes)
       | frame 1 data   |  (legth bytes)
       | ...            |
     there are "no. of frames" (see above) pairs
     p_ptr points to the legth field of the actual frame
     cnt counts the already processed frames
   */

#ifdef TRACE_PACKET_TIME
  if (debuglevel > 1) {
    fprintf(log_fp,"udp/tcp read time: %ld msec\n", msec_from_start());
  }
#endif
  *more_data = FALSE;
  usp = (u_short *)p_ptr;
  len = ntohs(*usp);
  /* check p_ptr */
  if (p_ptr - c_buf + len + 2 >= BUFSIZE) {
    errormsg(E_LOG,"read_ipx_from_udp_tcp","wrong access");
    return -1;
  }
  
  /* copy frame data into buf */
  packet->data = packet->buf + 30;
  memcpy(packet->data, p_ptr+2, len);
  header_compr_len = 0;
#if MAX_HDR_CACHE > 0
  if (router != (struct Router *)0 && router->com == com_tcp) {
    if ((header_compr_len = header_decompress(packet, router)) < 0) {
      errormsg(E_LOG,"read_ipx_from_udp_tcp",
          "closing TCP connection to %s due to header decompressing error",
	  router->name);
      close_tcp_connection(router);
      return -1;
    }
  }
  else {
    if ((header_compr_len = header_decompress(packet, router)) < 0) {
      return -1;
    }
  }
#endif
  /* advance p_ptr and cnt for next frame */
  p_ptr += len + 2;
  cnt++;
  /* *more_data is true if there are further frames available */
  if (cnt < anz) {
    *more_data = TRUE;
  }
  
  if (flags & IPX_PACKET) {
    /* frames have no Ethernet header */
    ipx_off = 0;
    packet->from_frametype = ETHER_NONE;
  }
  else {
    /* frames have Ethernet header, therefore get frame type and offset to
       start of IPX header */
    if ( ! get_ipx_to_ether_offset(packet->data, &frametype, &ipx_off)) {
      errormsg(E_LOG,"read_ipx_from_udp_tcp","unknown ether frame");
      return -1;
    }
    /* check if frame type is ok */
    if (! (frametype & local->r_frametypes)) {
      errormsg(E_LOG,"read_ipx_from_udp_tcp","unconfigured ether frame");
      return -1;
    }
    packet->from_frametype = frametype;
  }
  
  /* get IPX length and check for minimum size (i.e. IPX header size) */
  ipx_length = get_ipx_length(packet->data + ipx_off);
  if (ipx_length < 30) {
    errormsg(E_LOG,"read_ipx_from_udp_tcp",
             "packet dropped: ipx length too small (%d bytes)", ipx_length);
    local->stat.errors_r++;
    return -1;
  }

  /* check frame length to IPX length */
  if (ipx_length != len - ipx_off + header_compr_len &&
      ipx_length + 1 != len - ipx_off + header_compr_len) {
    errormsg(E_LOG,"read_ipx_from_udp_tcp",
       "packet dropped: ipx packet length doesnt match ipx_length (%d to %d)",
       len - ipx_off + header_compr_len, ipx_length);
    local->stat.errors_r++;
    fprintf(log_fp,"flags %u, cnt %u, ipx_off %u, hlen %d\n", flags, cnt,
            ipx_off, header_compr_len);
    return 0;
  }

  if (ipx_off > 0) {
    /* save Ether addresses */
    save_ether_addr(packet->data, ipx_off);
  }
  
  /* "packet" shall hold one IPX packet */
  packet->data = packet->data + ipx_off;
  packet->len = ipx_length;
  packet->from_fd = from_fd;
  packet->from_com = from_com;

  if (debuglevel > 1) {
    get_ipx_dest_node(ipx_dest_node, packet->data);
    if (memcmp(ipx_dest_node, IPX_BROADCAST_NODE, 6) != 0) {
      fprintf(log_fp,"Packet received (%d bytes) from udp for peer ",
              packet->len);
      fprint_as_hex(log_fp, ipx_dest_node, 6);
    }
    else {
      fprintf(log_fp,"Packet (Broadcast) received by %s ",
              router == NULL || router->com == com_udp ? "UDP" : "TCP");
    }
    fprintf(log_fp," [frametype ");
    log_frametypes(packet->from_frametype);
    fprintf(log_fp,"]:\n");
    if (debuglevel > 2) {
      log_ipx_header(packet->data);
    }
  }
  
  return ipx_length;  
}


int write_ipx_to_udp_tcp(struct Data_packet *packet, struct Router *router)
{
  u_int frametype;
  u_short *usp;
  size_t len, ipx_off;
  
#ifdef SLOW_LINE
  /* SLOW_LINE kBytes / sec */
  usleep((packet->len * 1000) / SLOW_LINE);
#endif
/* for DOOM */
/*
usleep(packet->len / 60);
*/
  /* the data format of the data is the above described but without the
     leading 10 bytes header */
  /* set c_buf.p to begin of buffer if it is empty */
  if (router->c_buf.anz == 0) {
    router->c_buf.p = router->c_buf.buf;
  }
  
#if MAX_HDR_CACHE > 0
  /* delete buffer if switched back from TCP to UDP */
  if (router->flush && router->com == com_udp) {
    router->c_buf.anz = 0;
    router->c_buf.p = router->c_buf.buf;
    router->flush = FALSE;
  }
#endif

  /* check if there is enought space in buffer, if not flush it */
  if (router->c_buf.p - router->c_buf.buf + packet->len + 20 >
      MAX_CBUF_ACCUMULATION) {
    /* send concatbuf */
    flush_to_udp_tcp(router);
  }

#ifdef TRACE_PACKET_TIME
  if (debuglevel > 1) {
    fprintf(log_fp,"udp/tcp write (buffered) time: %ld msec\n",
	    msec_from_start());
  }
#endif
  /* if remote router wants frames with Ethernet header, add it
     append frame to the buffer "c_buf" */
  if ((router->s_frametype == ETHER_TRANS) &&
      (packet->from_frametype != ETHER_NONE)) {
    frametype = packet->from_frametype;
    if (build_ether_packet(router->c_buf.p+2, &len, &ipx_off, frametype,
                           packet->data, packet->len) < 0) {
      errormsg(E_LOG,"write_ipx_to_udp_tcp","unknown frametype");
      router->stat.errors_s++;
      return -1;
    }
  }
  else {
#if MAX_HDR_CACHE > 0
    if (router->com == com_tcp) {
      if (header_compress(packet, router) < 0) {
	errormsg(E_LOG,"write_ipx_to_udp_tcp",
	    "closing TCP connection to %s due to header compressing error",
	    router->name);
	close_tcp_connection(router);
	return -1;
      }
    }
#endif
    len = packet->len;
    memcpy(router->c_buf.p+2, packet->data, len);
  }
  /* store frame size in buffer */
  usp = (u_short *)router->c_buf.p;
  *usp = htons(len);
  router->c_buf.p += len + 2;
  /* increment frame no. counter */
  router->c_buf.anz++;
  if (debuglevel > 1) {
    fprintf(log_fp,"Packet added (%d bytes) for router %s\n", len,
            router->name);
  }

  /* flush if too many packets accumulated */
  if (router->c_buf.anz >= MAX_CBUF_ANZ) {
    flush_to_udp_tcp(router);
  }
  
  return len;
}


int flush_to_udp_tcp(struct Router *router)
{
  u_int n;
  u_short flags, *usp;
  u_long cpr_len, t;
  size_t len;
  ssize_t snd;
  char buf[BUFSIZE];
  
  t = msec_from_start();
  if (router->c_buf.anz > 0) {
    /* if there are frames: send it */
    /* build 10 byte header (length, flags, no. of frames, magic), compress
       data and append it */
    flags = 0;
    if (router->s_frametype != ETHER_TRANS) {
      flags |= IPX_PACKET;
    }
    
    len = router->c_buf.p - router->c_buf.buf;
    if (compresslevel > 0) {
      cpr_len = BUFSIZE - UDP_TCP_HDR_LEN;
      if (compr(buf+UDP_TCP_HDR_LEN, &cpr_len, router->c_buf.buf, len) != Z_OK) {
	errormsg(E_LOG,"flush_to_udp_tcp","compression failed");
	return -1;
      }
    }
    else {
      cpr_len = len;
    }
    if (cpr_len < len) {
      flags |= COMPRESSED;
      len = cpr_len;
    }
    else {
      memcpy(buf + UDP_TCP_HDR_LEN, router->c_buf.buf, len);
      flags |= UNCOMPRESSED;
    }
    
    usp = (u_short *)buf;
    *usp = htons(len);
    usp++;
    *usp = htons(flags);
    usp++;
    *usp = htons(router->c_buf.anz);
    memcpy(&buf[6], UDP_TCP_MAGIC, 4);
    router->dummycnt = DUMMY_COUNT;
  }
  else {
    /* if there are no frames: check if a dummy packet should be send */
    n = 1 + DUMMY_COUNT - router->dummycnt;
    if (router->dummycnt > 0 &&
	t - router->last_send_time > (n * n) *  MAX_SEND_DELAY) {
      len = 0;
      usp = (u_short *)buf;
      *usp = htons(len);
      usp++;
      *usp = htons(0);
      usp++;
      *usp = htons(0);
      memcpy(&buf[6], DUMMY_MAGIC, 4);
      router->dummycnt--;
    }
    else {
      router->c_buf.p = router->c_buf.buf;
      return 0;
    }
  }

  /* reset buffer */
  router->c_buf.anz = 0;
  router->c_buf.p = router->c_buf.buf;
  router->last_send_time = t;
  if (router->com == com_udp) {
    /* send packet by UDP */
    snd = sendto(router->udp_fd, (void *)buf, len + UDP_TCP_HDR_LEN, 0,
		  (struct sockaddr *) &router->sin, sizeof(router->sin));
    if (snd < 0) {
      errormsg(E_LOG,"flush_to_udp_tcp","sendto: %m");
      router->stat.errors_s++;
      return -1;
    }
  }
  else if (router->com == com_tcp) {
    /* send data by TCP */
    snd = writen(router->tcp_fd, (void *)buf, len + UDP_TCP_HDR_LEN);
    if (snd < 0) {
      errormsg(E_LOG,"flush_to_udp_tcp","write: %m");
      router->stat.errors_s++;
      return -1;
    }
  }
  else {
    errormsg(E_LOG,"flush_to_udp_tcp","wrong com type");
    return -1;
  }

  if (snd != len + UDP_TCP_HDR_LEN) {
    errormsg(E_LOG,"flush_to_udp_tcp",
             "Warning: could only write to %s %d bytes of %d bytes\n",
             router->name, snd, len + UDP_TCP_HDR_LEN);
  }

#ifdef TRACE_PACKET_TIME
  if (debuglevel > 1) {
    fprintf(log_fp,"udp/tcp flush time: %ld msec\n", msec_from_start());
  }
#endif
  if (debuglevel > 1) {
    fprintf(log_fp,"Packet sent by %s (%d bytes) to %s\n",
            router->com == com_udp ? "UDP" : "TCP", snd,
            router->name);
  }

  router->stat.packets_s++;
  router->stat.bytes_s += snd;
  if (snd > router->stat.max_bytes_s) {
    router->stat.max_bytes_s = snd;
  }

  return 0;
}
