#include "ipxtund.h"

boolean get_ipx_to_ether_offset(const u_char *frame, u_int *type,
                                size_t *ipxoff)
/*
 determines Ethernet frametype and offset to IPX data header
*/
{
  u_short t;

  *type = 0;
  if (frame[12] == 129 && frame[13] == 55) {
    /* Ether_II */
    *type = ETHER_II;
    *ipxoff = 14;
    return TRUE;
  }
  else {
    t = 256 *frame[12] + frame[13];
    if (t <= 1500) {
      if (memcmp(&frame[14], "\377\377", 2) == 0) {
        /* Ether_802.3 */
	*type = ETHER_802_3;
	*ipxoff = 14;
        return TRUE;
      }
      else {
        if (frame[14] == 176) {
	  /* Ether_SNAP */
	  *type = ETHER_SNAP;
          *ipxoff = 22;
	  return TRUE;
	}
	else {
	  /* Ether_802.2 */
	  *type = ETHER_802_2;
          *ipxoff = 17;
	  return TRUE;
	}
      }
    }
  }
  if (debuglevel > 2) {
    fprintf(log_fp,"frametype 0x %2x %2x is unknown\n", frame[12], frame[13]);
  }
  *ipxoff = 0;
  return FALSE;
}


int build_ether_packet(char *ether_packet, size_t *ether_len, size_t *ipx_off,
                       u_int ethertype, const char *ipx_packet,
                       const size_t ipx_len)
/*
  build an Ethernet frame of the desired frametype from IPX packet
*/
{
  size_t len, evenize;
  
  evenize = 0;
  memcpy(&ether_packet[0], ipx_packet + 10, 6);
  memcpy(&ether_packet[6], ipx_packet + 22, 6);
  if (ethertype == ETHER_II) {
    *ipx_off = 14;
    evenize = ipx_len % 2;
    ether_packet[12] = (u_char)129;
    ether_packet[13] = (u_char)55;
  }
  else if (ethertype == ETHER_802_2) {
    *ipx_off = 17;
    hs_to_str(&ether_packet[12], ipx_len + 3);
    ether_packet[12] = htons((u_short)ipx_len);
    ether_packet[14] = (u_char)224;
    ether_packet[15] = (u_char)224;
    ether_packet[16] = (u_char)3;
  }
  else if (ethertype == ETHER_802_3) {
    *ipx_off = 14;
    evenize = ipx_len % 2;
    hs_to_str(&ether_packet[12], ipx_len);
  }
  else if (ethertype == ETHER_SNAP) {
    *ipx_off = 22;
    hs_to_str(&ether_packet[12], ipx_len + 8);
    ether_packet[14] = (u_char)176;
    ether_packet[15] = (u_char)176;
    ether_packet[16] = (u_char)3;
    ether_packet[17] = (u_char)0;
    ether_packet[18] = (u_char)0;
    ether_packet[19] = (u_char)0;
    ether_packet[20] = (u_char)129;
    ether_packet[21] = (u_char)55;
  }
  else {
    return -1;
  }
  memcpy(&ether_packet[*ipx_off], ipx_packet, ipx_len);
  /* restore IPX addresses */
  if (ipx_packet[0] == '\376') {
    ether_packet[*ipx_off] = '\377';
    memcpy(ether_packet + *ipx_off + 10, IPX_NOVELL_SERVER_NODE, 6);
  }
  if (ipx_packet[1] == '\376') {
    ether_packet[*ipx_off+1] = '\377';
    memcpy(ether_packet + *ipx_off + 22, IPX_NOVELL_SERVER_NODE, 6);
  }
  len = ipx_len + *ipx_off;
  /* evenize Ether data length (even number of bytes) for II and 802.3 */
  if (evenize > 0) {
    ether_packet[len++] = '\0';
  }
  /* assure minimun ethernet length of 60 bytes */
  if (len < 60) {
   memset(&ether_packet[len], 0, 60 - len);
   len = 60;
  }
  *ether_len = len;

  return 0;
}


boolean is_realframetype(const u_int frametype)
{
  return ((frametype & ETHER_II) || (frametype & ETHER_802_2) ||
          (frametype & ETHER_802_3) || (frametype & ETHER_SNAP));
}


int check_ether_age(const struct Data_packet *packet,
		    const struct Ether *ether, const u_int maxage)
/*
  Test, if packet was already received since maxage time.
*/
{
  int rv = 0;
  int n, k;
  static u_int cnt = 0;
  u_int flags;
  struct Sdb *db;
  struct Sdb_datum dbkey, dbdata;
  struct timeval tp;
  struct timezone tzp;
  double cur_time, reg_time;
  
  if (maxage == 0)  return 0;

  gettimeofday(&tp, &tzp);
  cur_time = 1000 * tp.tv_sec + tp.tv_usec / 1000;
  db = ether->packet_db;
  dbkey.data = packet->data;
  dbkey.size = packet->len;
  dbdata.data = &cur_time;
  dbdata.size = sizeof(cur_time);
  n = db->put(db, &dbkey, &dbdata, SDB_NOOVERWRITE);
  if (n == 0) {
    /* packet was unknown -> accepted*/
    if (debuglevel > 2) {
      fprintf(log_fp,"check_ether_age: packet accepted\n");
    }
    rv = 0;
  }
  else if (n < 0) {
    errormsg(E_LOG,"check_ether_age","DB put: %m");
    return -1;
  }
  else {
    /* (n == 1) */
    /* packet already known */
    n = db->get(db, &dbkey, &dbdata, SDB_DATA_REFERENCE);
    if (n < 0) {
      errormsg(E_LOG,"check_ether_age","DB get: %m");
      return -1;
    }
    else if (n == 1) {
      errormsg(E_LOG,"check_ether_age","DB get: entry not found");
      return -1;
    }
    else {
      /* (n == 0) */
      memcpy(&reg_time, dbdata.data, sizeof(reg_time));
      if (cur_time - reg_time > maxage) {
	/* entry is outdated -> packet accepted */
	if (debuglevel > 2) {
	  fprintf(log_fp,"check_ether_age: packet accepted\n");
	}
	rv = 0;
      }
      else {
	/* entry is still vaild -> packed discarded */
	if (debuglevel > 2) {
	  fprintf(log_fp,"check_ether_age: packet discarded\n");
	}
	rv = 1;
      }
    }
  }

  cnt++;
  if (cnt > 10) {
    /* clean up db */
    cnt = 0;
    k = 0;
    flags = SDB_KEY_REFERENCE | SDB_DATA_REFERENCE | SDB_FIRST;
    while ((n = db->seq(db, &dbkey, &dbdata, flags)) != 1) {
      if (n < 0) {
	errormsg(E_LOG,"check_ether_age","DB seq: %m");
	return -1;
      }
      k++;
      flags = SDB_KEY_REFERENCE | SDB_DATA_REFERENCE;
      memcpy(&reg_time, dbdata.data, sizeof(reg_time));
      if (cur_time - reg_time > maxage) {
	/* entry is outdated */
	if (n < 0) {
	  errormsg(E_LOG,"check_ether_age","DB del: %m");
	  return -1;
	}
	if (n == 1) {
	  errormsg(E_LOG,"check_ether_age","DB del: entry not found");
	}
      }
    }
  }
  return rv;
}


void save_ether_addr(char *ether_data, const size_t ipx_off)
/*
  Save Ethernet-Address in IPX-header and remeber this change in IPX checksum
*/
{
  char *ipx_dest_node, *ipx_source_node, *ether_dest, *ether_source;
  
  ipx_dest_node = ether_data + ipx_off + 10;
  ipx_source_node = ether_data + ipx_off + 22;
  if (memcmp(ipx_dest_node, IPX_NOVELL_SERVER_NODE, 6) == 0) {
    ether_dest = ether_data;
    memcpy(ipx_dest_node, ether_dest, 6);
    ether_data[ipx_off] = '\376';
  }
  if (memcmp(ipx_source_node, IPX_NOVELL_SERVER_NODE, 6) == 0) {
    ether_source = ether_data + 6;
    memcpy(ipx_source_node, ether_source, 6);
    ether_data[ipx_off+1] = '\376';
  }
  return;
}


boolean filter_sender(struct Sdb *sender_db, struct Data_packet *packet)
/*
  Check if sender matches a MAC from sender file.
*/
{
  int n;
  char ipx_source_node[6];
  struct Sdb_datum dbkey, dbdata;

  if (sender_db == NULL)  return TRUE;
  get_ipx_source_node(ipx_source_node, packet->data);
  dbkey.data = ipx_source_node;
  dbkey.size = 6;
  n = sender_db->get(sender_db, &dbkey, &dbdata, SDB_DATA_REFERENCE);
  if (n < 0) {
    errormsg(E_LOG,"filter_sender","DB get: %m");
    return TRUE;
  }
  if (n == 0) {
    return TRUE;
  }
  if (debuglevel > 1) {
    fprintf(log_fp,"no transfer because sender ");
    fprint_as_hex(log_fp, ipx_source_node, 6);
    fprintf(log_fp," not in list\n");
  }
  return FALSE;
}
