/*
 * $Id: hash_spec.c,v 1.4 2002/10/17 20:02:30 ljb Exp $
 * originally Id: hash_spec.c,v 1.14 1998/07/20 01:22:03 labovit Exp 
 */

/* routines for handling special indicies in hashes  */

#include <stdio.h>
#include <string.h>
#include "mrt.h"
#include "trace.h"
#include <time.h>
#include <signal.h>
#include "config_file.h"
#include <fcntl.h>
#include <ctype.h>
#include "irrd.h"

extern trace_t *default_trace;

/* called when indexes are stored in main memory hash */
int irr_spec_hash_store (irr_database_t *database, char *key, char *value) {
  hash_item_t *hash_item;

  hash_item = New (hash_item_t);
  hash_item->key = strdup (key);
  hash_item->value = value;
  HASH_Insert (database->hash_spec, hash_item);
  return (1);
}
  
/* put a series of object offsets/lengths from a linked list */
void util_put_ll_objs (LINKED_LIST *ll, char **cp) {
  maint_objlist_t *maint_obj_p;
  u_short _type;
  char *c = *cp;

  LL_Iterate (ll, maint_obj_p) {
    UTIL_PUT_NETLONG  (maint_obj_p->offset, c); 
    UTIL_PUT_NETLONG  (maint_obj_p->len, c);
    _type = (u_short) maint_obj_p->type;
    UTIL_PUT_NETSHORT (_type, c); /* type */
  }
  *cp = c;
}

/* make a string from a linked list, each item is seperated by a ' ' */
void util_put_ll_string (LINKED_LIST *ll, char **cp) {
  irr_hash_string_t *p;
  char *c = *cp;

  LL_Iterate (ll, p) {
    strcpy (c, p->string);
    while (*++c); 
    *c++ = ' ';
  }
  *c++ = '\0';  /* terminate with a null byte */
  *cp = c;	/* update pointer past the null */
}

/* make a linked list of objects from a string */
void util_get_ll_objs (LINKED_LIST **ll, u_long items, char **cp) {
  maint_objlist_t *maint_obj_p;
  u_short _type;
  char *c = *cp;

  (*ll) = LL_Create (LL_DestroyFunction, free, 0);

  while (items > 0) {
    maint_obj_p = New(maint_objlist_t);
    UTIL_GET_NETLONG (maint_obj_p->offset, c);
    UTIL_GET_NETLONG (maint_obj_p->len, c);
    UTIL_GET_NETSHORT (_type, c);
    maint_obj_p->type = _type;
    LL_Add ((*ll), maint_obj_p);
    items--;
  }
  *cp = c; 
}

/* make a linked list from a string, items in the string are seperated by a ' ' */
int util_get_ll_string (LINKED_LIST **ll, u_long items, char **cp) {
  char *a, *b;
  irr_hash_string_t tmp;
  int return_len;

  a = *cp;

  (*ll) = LL_Create (LL_Intrusive, True, 
		     LL_NextOffset, LL_Offset (&tmp, &tmp.next),
		     LL_PrevOffset, LL_Offset (&tmp, &tmp.prev),
		     LL_DestroyFunction, delete_irr_hash_string, 0);

  if (items == 0)
    return (0);

  while (items > 0) {
    if ((b = strchr (a, ' ')) == NULL) {
      /* this should't happen */
      trace (ERROR, default_trace, "util_get_ll_string: badly formatted list - %s;\n", *cp);
      break;
    }
    *b = '\0';
    LL_Add ((*ll), new_irr_hash_string (a));
    *b++ = ' ';
    a = b;
    items--;
  }
  return_len = a - *cp;	/* calculate the length of this string */
  /* make cp point to the begining of the next item (skip null) */
  *cp = a + 1; 
  return (return_len);
}

/* Marshal the hash_spec_t struct into a hash_item_t struct.
 * this means flattening out the linked lists (ie, put both
 * ll's into a single char string).
 */
void store_hash_spec (irr_database_t *database, hash_spec_t *hash_sval) { 
  char *cp, *buf;
  u_short _id;
  u_int str_size;
  hash_item_t *hash_x;

  _id = hash_sval->id;

  /* compute the size of the packed value string */
  /* the lengths should include the '\0' at the end */
  if (_id == MNTOBJS )
    str_size = NETSHORT_SIZE + NETLONG_SIZE + hash_sval->items1 * (2 * NETLONG_SIZE + NETSHORT_SIZE);
  else {
    str_size = NETSHORT_SIZE + NETLONG_SIZE + hash_sval->len1 + 1;
    if (_id == SET_OBJX)
      str_size += NETLONG_SIZE + hash_sval->len2 + 1;
  }

  /* now pack up the value part */
  cp = buf = malloc (str_size);
  UTIL_PUT_NETSHORT (_id, cp); 
  UTIL_PUT_NETLONG  (hash_sval->items1, cp);
  if (_id == MNTOBJS )
    util_put_ll_objs(hash_sval->ll_1, &cp);
  else {
    if (hash_sval->items1 > 0)
      util_put_ll_string(hash_sval->ll_1, &cp);
    if (_id == SET_OBJX) {
      UTIL_PUT_NETLONG  (hash_sval->items2, cp);
      if (hash_sval->items2 > 0)
        util_put_ll_string(hash_sval->ll_2, &cp);
    }
  }

  hash_x = HASH_Lookup (database->hash_spec, hash_sval->key);
  if (hash_x != NULL) HASH_Remove (database->hash_spec, hash_x);
  irr_spec_hash_store (database, hash_sval->key, buf);
}

void remove_hash_spec (irr_database_t *db, char *key) {
  /* printf("enter remove_hash_spec( key-(%s))\n",key); */
  HASH_RemoveByKey (db->hash_spec, key);
}

hash_spec_t *fetch_hash_spec (irr_database_t *database, char *key, 
                              enum FETCH_T mode) {
  char *cp;
  u_short _id;
  hash_item_t *hash_item = NULL;
  hash_spec_t *hash_sval = NULL;

  hash_item = HASH_Lookup (database->hash_spec, key);

  if (hash_item) {
    cp = hash_item->value;
    hash_sval = New (hash_spec_t);
    hash_sval->key = strdup (key);
    UTIL_GET_NETSHORT (_id, cp);
    hash_sval->id = _id;
    UTIL_GET_NETLONG (hash_sval->items1, cp);
    if (_id == MNTOBJS)
        util_get_ll_objs (&hash_sval->ll_1, hash_sval->items1, &cp);
    else {
      if (mode == UNPACK) {
        hash_sval->len1 = util_get_ll_string (&hash_sval->ll_1, 
                   hash_sval->items1, &cp);
        if (_id == SET_OBJX) {
          UTIL_GET_NETLONG (hash_sval->items2, cp);
          hash_sval->len2 = util_get_ll_string (&hash_sval->ll_2,
                   hash_sval->items2, &cp);
        }
      } else  {/* FAST mode, just return the unpacked item, ie !gas */
        hash_sval->len1 =  strlen(cp);
        hash_sval->gas_answer = cp; /* points to the first string/gas answer */
      }
    }
  }    
  else
    hash_sval = NULL;

  return (hash_sval);
}

void memory_hash_spec_del (hash_spec_t *hash_value, enum SPEC_KEYS id, 
		           void *blob_item) {
  irr_hash_string_t *irr_hash_str;
  maint_objlist_t *maint_obj_p;

  switch (id) {
    case SET_OBJX:
      hash_value->len1 = hash_value->len2 = 0; 
      hash_value->items1 = hash_value->items2 = 0;
      LL_Clear (hash_value->ll_1);
      LL_Clear (hash_value->ll_2);
      break;
    case SET_MBRSX:
    case GASX:
      if (blob_item == NULL)
        return;
      LL_Iterate (hash_value->ll_1, irr_hash_str) {
        if (!strcmp ((char *)blob_item, irr_hash_str->string)) {
          LL_Remove (hash_value->ll_1, irr_hash_str);
          hash_value->items1--;
          hash_value->len1 -= strlen ((char *)blob_item) + 1; 
          break;
        }
      }
      break;
    case MNTOBJS:
      if (blob_item == NULL)
        return;
      LL_Iterate (hash_value->ll_1, maint_obj_p) {
        if (*((u_long *)blob_item) == maint_obj_p->offset) {
          LL_Remove (hash_value->ll_1, maint_obj_p);
          hash_value->items1--;
          break;
        }
      }
      break;
    default:
      trace (ERROR, default_trace, "memory_hash_spec_del() error.  Got an unknown index id-type, cannot perform delete operation id-(%d)\n!", id);
      break;
  };
}

hash_spec_t *memory_hash_spec_create (char *key, enum SPEC_KEYS id) {
  hash_spec_t *hash_value;
  irr_hash_string_t hash_str;

  hash_value = New (hash_spec_t);
  hash_value->id = id;
  hash_value->key = strdup (key);

  if (id == MNTOBJS) {
    hash_value->ll_1 = LL_Create (LL_DestroyFunction, free, 0);
  } else {
    hash_value->ll_1 = LL_Create (LL_Intrusive, True, 
			LL_NextOffset, LL_Offset (&hash_str, &hash_str.next),
			LL_PrevOffset, LL_Offset (&hash_str, &hash_str.prev),
			LL_DestroyFunction, delete_irr_hash_string, 0);
  
    if (id == SET_OBJX) {
      hash_value->ll_2 = LL_Create (LL_Intrusive, True, 
			LL_NextOffset, LL_Offset (&hash_str, &hash_str.next),
			LL_PrevOffset, LL_Offset (&hash_str, &hash_str.prev),
			LL_DestroyFunction, delete_irr_hash_string, 0);
    }
  }

  return (hash_value);
}

int memory_hash_spec_remove (irr_database_t *db, char *key, enum SPEC_KEYS id,
                             void *blob_item) {
  hash_spec_t *hash_sval;

  convert_toupper(key);
  hash_sval = HASH_Lookup (db->hash_spec_tmp, key);

  if (hash_sval == NULL) { /* might be in the mem hash index */
				    
    if ((hash_sval = fetch_hash_spec (db, key, UNPACK)) == NULL)
      return (-1); /* item not found, can't delete */

    HASH_Insert (db->hash_spec_tmp, hash_sval);
  }

  memory_hash_spec_del (hash_sval, id, blob_item);
  return (1);
} 

int memory_hash_spec_store (irr_database_t *db, char *key, enum SPEC_KEYS id,
                            irr_object_t *irr_object) {
  hash_spec_t *hash_sval;
  LINKED_LIST *ll_1 = NULL;
  LINKED_LIST *ll_2 = NULL;
  char *tmpstr;
  maint_objlist_t *maint_obj_p;
  int retval = 1;

  convert_toupper(key);
  hash_sval = HASH_Lookup (db->hash_spec_tmp, key);

  if (hash_sval == NULL) { /* might be in the mem hash index */
				    
    if ((hash_sval = fetch_hash_spec (db, key, UNPACK)) == NULL)
      hash_sval = memory_hash_spec_create (key, id);

    HASH_Insert (db->hash_spec_tmp, hash_sval);
  }

  /* if the hash lookup found something and the id's don't match
   * something is really wrong!
   */
  if (id != hash_sval->id) {
    trace (ERROR, default_trace, "Attempt to add hash item with different id value-(key(%s),%d,%d)\n!", hash_sval->key, id, hash_sval->id);
    return (-1);
  }

  switch (id) {
    case SET_OBJX:
      if ( (ll_1 = irr_object->ll_as) != NULL) {
        if (hash_sval->items1 > 0) { 
          /* there should be only one unique as-set or route-set object 
           * and therefore we need only one 'members:'/ll_1 list 
           */
          trace (ERROR, default_trace, "Append to existing 'members:' list! Object key-(%s)\n", hash_sval->key);
          LL_Clear (hash_sval->ll_1);
          hash_sval->items1 = 0;
          hash_sval->len1 = 0;
          retval = -1;
        }
        LL_Iterate (ll_1, tmpstr) {
          LL_Add (hash_sval->ll_1, new_irr_hash_string (tmpstr));
          hash_sval->len1 += strlen (tmpstr) + 1;
          hash_sval->items1++;
        }
      }
      if ( (ll_2 = irr_object->ll_mbr_by_ref) != NULL) {
        if (hash_sval->items2 > 0) {
          /* ditto above except 'mbrs_by_ref:'/ll_2 */
          trace (ERROR, default_trace, "Append to existing 'mbrs_by_ref:' list! Object key-(%s)\n", hash_sval->key);
          LL_Clear (hash_sval->ll_2);
          hash_sval->items2 = 0; 
          hash_sval->len2 = 0;
          retval = -1;
	}
	LL_Iterate (ll_2, tmpstr) {
	  LL_Add (hash_sval->ll_2,new_irr_hash_string (tmpstr));
	  hash_sval->len2 += strlen (tmpstr) + 1;
 	  hash_sval->items2++;
	}
      }
      break;
    case SET_MBRSX:
    case GASX:
      if ( (tmpstr = irr_object->name) != NULL) {
        LL_Add (hash_sval->ll_1, new_irr_hash_string (tmpstr));
        hash_sval->len1 += strlen (tmpstr) + 1;
        hash_sval->items1++;
      }
      break;
    case MNTOBJS:
      maint_obj_p = New(maint_objlist_t);
      maint_obj_p->offset = irr_object->offset;
      maint_obj_p->len = irr_object->len;
      maint_obj_p->type = irr_object->type;
      LL_Add (hash_sval->ll_1, maint_obj_p);
      hash_sval->items1++;
      break;
  }

  return(retval);
} 

/* this delete's the spec temp memory hash */
void Delete_hash_spec (hash_spec_t *hash_sval) {

  if (hash_sval == NULL)
    return;

  if (hash_sval->key)
    Delete (hash_sval->key);

  if (hash_sval->ll_1)
    LL_Destroy (hash_sval->ll_1);

  if (hash_sval->id == SET_OBJX) {
    if (hash_sval->ll_2)
      LL_Destroy (hash_sval->ll_2);
  }

  Delete (hash_sval);
}

/* commit our mem index to our regular index for 2 reasons:
 * 1. the regular index is suitable for mem and disk storage
 *    ie, there are no linked lists.
 * 2. it is faster to manipulate mem indexes, so our disk
 *    scan processing will be faster and they need to be 
 *    written to disk anyway.
 */
void commit_spec_hash (irr_database_t *db) {
  hash_spec_t *hash_tval;

  HASH_Iterate (db->hash_spec_tmp, hash_tval) {
    if (hash_tval->items1 == 0 && hash_tval->items2 == 0)
      remove_hash_spec (db, hash_tval->key); 
    else 
      store_hash_spec (db, hash_tval); 
  }
}

/* makes the keys for the mbrs-by-ref hash 
 * used in the rpsl !i command
 */
void make_spec_key (char *new_key, char *maint, char *set_name) {
  
  /* some objects may not have a maintainer ;) */
  if (maint != NULL)
    strcpy (new_key, maint);
  else
    strcpy (new_key, "ANY");
  
  strcat (new_key, "|");
  strcat (new_key, set_name);
  convert_toupper (new_key);  
}

/* makes the keys for the mbrs objects hash 
 */
void make_mntobj_key (char *new_key, char *maint) {

  strcpy (new_key, "|");  /* key uniqueness */
  strcat (new_key, maint);
  convert_toupper (new_key);
}

/* routine expects an origin without the "as", eg "231" */
void make_gas_key (char *gas_key, char *origin) {

  strcpy (gas_key, "@"); /* key uniqueness */
  strcat (gas_key, origin);
}

void make_setobj_key (char *new_key, char *obj_name) {

  strcpy (new_key, "@"); /* key uniqueness */
  strcat (new_key, obj_name);
  convert_toupper (new_key);
}


syntax highlighted by Code2HTML, v. 0.9.1