#include "ipxtund.h"

/*
  Functions for compressions: data compression using "zlib" and header
  compression by header caching.
*/

/* data compression */

int compr(char *dest, u_long *d_len, char *source, u_long s_len)
{
  int err;
  static z_stream zstream;
  static boolean init = TRUE;
  
  zstream.next_in = source;
  zstream.avail_in = s_len;
  zstream.next_out = dest;
  zstream.avail_out = *d_len;
  if (init) {
    init = FALSE;
    zstream.zalloc = (alloc_func)0;
    zstream.zfree = (free_func)0;
    zstream.opaque = (voidpf)0;
    err = deflateInit(&zstream, compresslevel);
    if (err != Z_OK) {
      return err;
    }
  }
  err = deflate(&zstream, Z_FINISH);
  if (err != Z_STREAM_END) {
    return err;
  }
  *d_len = zstream.total_out;
  err = deflateReset(&zstream);
  return err;
}


int uncompr(char *dest, u_long *d_len, char *source, u_long s_len)
{
  int err;
  static z_stream zstream;
  static boolean init = TRUE;
  
  zstream.next_in = source;
  zstream.avail_in = s_len;
  zstream.next_out = dest;
  zstream.avail_out = *d_len;
  if (init) {
    init = FALSE;
    zstream.zalloc = (alloc_func)0;
    zstream.zfree = (free_func)0;
    err = inflateInit(&zstream);
    if (err != Z_OK) {
      return err;
    }
  }
  err = inflate(&zstream, Z_FINISH);
  if (err != Z_STREAM_END) {
    return err;
  }
  *d_len = zstream.total_out;
  err = inflateReset(&zstream);
  return err;
}


/* header compression */
/*
  uncompressed header:
    | FF  FF |  (2 bytes)
    |  len   |  (2 bytes)
    | ...    |  (26 more bytes)
    
  compressed header:
    |  len     |  (2 bytes)   original length
    | cache no |  (1 byte)
    
  header to be cached by remote (for later compressing):
    | FF       |  (1 byte)
    | cache no |  (1 byte)
    | ...      |  (28 more bytes)

  uncompressed header with saved Ethernet destination address:
    | FE  FF |  (2 bytes)
    |  len   |  (2 bytes)
    | ...    |  (26 more bytes)
    
  uncompressed header with saved Ethernet source address:
    | FF  FE |  (2 bytes)
    |  len   |  (2 bytes)
    | ...    |  (26 more bytes)
*/

int header_compress(struct Data_packet *packet, struct Router *router)
{
  int k;
  u_short idx;
  struct Cache_entry entry;
  
  if (packet->data[0] == '\376' || packet->data[1] == '\376') {
    /* packets with saved addresses never cached */
    return 0;
  }
  entry.size = 26;
  entry.data = packet->data + 4;  
  k = get_i_cache(&entry, &router->s_hdr_cache);
  if (k >= 0) {
    /* cache hit */
    idx = (u_short)k;
    if (debuglevel > 1) {
      fprintf(log_fp,"header send cache hit, id %u\n", idx);
    }
    memcpy(packet->data + 27, packet->data + 2, 2);
    packet->data[29] = (u_char)idx;
    packet->data += 27;
    packet->len -= 27;
  }
  else {
    /* cache miss */
    if (debuglevel > 1) {
      fprintf(log_fp,"header send cache miss, id %u\n",
              router->s_hdr_cache.next_idx);
    }
    packet->data[1] = (u_char)router->s_hdr_cache.next_idx;
    put_cache(&router->s_hdr_cache, &entry, TRUE);
  }
  return 0;
}


int header_decompress(struct Data_packet *packet, struct Router *router)
{
  u_short idx;
  struct Cache_entry *entry;
  
  if (packet->data[0] == '\376' || packet->data[1] == '\376') {
    /* packets with saved addresses never cached */
    return 0;
  }
  if (packet->data[0] == '\377' && packet->data[1] == '\377') {
    /* packet is not header compressed and header shall not be cached */
    if (debuglevel > 1) {
      fprintf(log_fp,"header is not compressed\n");
    }
    return 0;
  }

  if (router == (struct Router *)0) {
    errormsg(E_LOG,"header_decompress",
	     "cant header decompress, router points to zero");
    return -1;
  }

  if (packet->data[0] == '\377') {
    /* packet is not header compressed and header shall be cached */
    if (debuglevel > 1) {
      fprintf(log_fp,"header put into receive cache, id %u\n",
              (u_char)packet->data[1]);
    }
    if (header_put_cache(packet, router) < 0) {
      return -1;
    }
    return 0;
  }
  else {
    /* packet is header compressed */
    if (packet->data - 27 < packet->buf) {
      errormsg(E_LOG,"header_decompress","to few space for header expansion");
      return -1;
    }
    packet->data -= 27;
    packet->len += 27;
    memset(packet->data,'\377', 2);
    memcpy(packet->data + 2, packet->data + 27, 2);
    idx = (u_short)packet->data[29];
    entry = get_d_cache(idx, &router->r_hdr_cache);
    if (entry == NULL) {
      return -1;
    }
    memcpy(packet->data + 4, entry->data, 26);
    if (debuglevel > 1) {
      fprintf(log_fp,"header decompressing, id %u\n", idx);
    }
    return 27;
  }
  return 0;
}


int header_put_cache(struct Data_packet *packet, struct Router *router)
{
  struct Cache_entry entry;
  
  if (router->r_hdr_cache.next_idx != (u_char)packet->data[1]) {
    errormsg(E_LOG,"header_put_cache","cache inconsistency");
    return -1;
  }
  entry.size = 26;
  entry.data = packet->data + 4;
  if (put_cache(&router->r_hdr_cache, &entry, TRUE) < 0) {
    return -1;
  }
  if (debuglevel > 1) {
    fprintf(log_fp,"header put into cache, id %u\n",
            router->r_hdr_cache.next_idx);
  }
  return 0;
}


struct Cache_entry *get_d_cache(const u_short idx, struct Cache *cache)
{
  if (idx >= MAX_CACHE) {
    errormsg(E_LOG,"get_d_cache","index %d beyond maximum bounds %d", idx,
	     MAX_CACHE);
    return NULL;
  }
  if (idx >= cache->anz) {
    errormsg(E_LOG,"get_d_cache","index %d beyond current bounds %d", idx,
	     cache->anz);
    return NULL;
  }
  if (cache->slot[idx].data == NULL) {
    errormsg(E_LOG,"get_d_cache","stale cache entry %d", idx);
    return NULL;
  }
  return &cache->slot[idx];
}


int get_i_cache(const struct Cache_entry *entry, struct Cache *cache)
{
  u_short k;
  
  for (k = 0; k < cache->anz; k++) {
    if (entry->size == cache->slot[k].size &&
        memcmp(entry->data, cache->slot[k].data, entry->size) == 0) {
      return k;
    }
  }
  return -1;
}


int put_cache(struct Cache *cache, struct Cache_entry *entry,
              const boolean copy)
{
  cache->slot[cache->next_idx].size = entry->size;
  if (cache->slot[cache->next_idx].data != NULL) {
    free(cache->slot[cache->next_idx].data);
  }
  if (copy) {
    if ((cache->slot[cache->next_idx].data = malloc(entry->size)) == NULL) {
      errormsg(E_LOG,"put_cache","malloc: %m");
      return -1;
    }
    memcpy(cache->slot[cache->next_idx].data, entry->data, entry->size);
  }
  else {
    cache->slot[cache->next_idx].data = entry->data;
  }
  cache->next_idx = (cache->next_idx + 1) % cache->max_anz;
  if (cache->anz < cache->max_anz) {
    cache->anz++;
  }
  return 0;
}


void clear_cache(struct Cache *cache)
{
  int k;
  
  for (k = 0; k < MAX_CACHE; k++) {
    if (cache->slot[k].data != NULL) {
      free(cache->slot[k].data);
      cache->slot[k].data = NULL;
    }
    cache->slot[k].size = 0;
  }
  cache->next_idx = 0;
  cache->anz = 0;
}
