/* header complex:  More complex storage and data sorting for mib modules */

#include <config.h>

#include <sys/types.h>
#if HAVE_WINSOCK_H
#include <winsock.h>
#endif
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#include "mibincl.h"
#include "header_complex.h"

int
header_complex_generate_varoid(struct variable_list *var) {
  int i;
  
  if (var->name == NULL) {
    /* assume cached value is correct */
    switch(var->type) {
      case ASN_INTEGER:
      case ASN_COUNTER:
      case ASN_GAUGE:
      case ASN_TIMETICKS:
        var->name_length = 1;
        var->name = (oid *) malloc(sizeof(oid));
        if (var->name == NULL)
            return SNMPERR_GENERR;
        var->name[0] = *(var->val.integer);
        break;

      case ASN_PRIV_IMPLIED_OBJECT_ID:
        var->name_length = var->val_len/sizeof(oid);
        var->name = (oid *) malloc(sizeof(oid) * (var->name_length));
        if (var->name == NULL)
            return SNMPERR_GENERR;

        for(i = 0; i < (int)var->name_length; i++)
          var->name[i] = var->val.objid[i];
        break;

      case ASN_OBJECT_ID:
        var->name_length = var->val_len/sizeof(oid) + 1;
        var->name = (oid *) malloc(sizeof(oid) * (var->name_length));
        if (var->name == NULL)
            return SNMPERR_GENERR;

        var->name[0] = var->name_length-1;
        for(i = 0; i < (int)var->name_length-1; i++)
          var->name[i+1] = var->val.objid[i];
        break;
        
      case ASN_PRIV_IMPLIED_OCTET_STR:
        var->name_length = var->val_len;
        var->name = (oid *) malloc(sizeof(oid) * (var->name_length));
        if (var->name == NULL)
            return SNMPERR_GENERR;

        for(i = 0; i < (int)var->val_len; i++)
          var->name[i] = (oid) var->val.string[i];
        break;

      case ASN_OPAQUE:
      case ASN_OCTET_STR:
        var->name_length = var->val_len + 1;
        var->name = (oid *) malloc(sizeof(oid) * (var->name_length));
        if (var->name == NULL)
            return SNMPERR_GENERR;

        var->name[0] = (oid) var->val_len;
        for(i = 0; i < (int)var->val_len; i++)
          var->name[i+1] = (oid) var->val.string[i];
        break;
      
      default:
        DEBUGMSGTL(("header_complex_generate_varoid",
                    "invalid asn type: %d\n", var->type));
        return SNMPERR_GENERR;
    }
  }
  if (var->name_length > MAX_OID_LEN) {
    DEBUGMSGTL(("header_complex_generate_varoid",
                "Something terribly wrong, namelen = %d\n", var->name_length));
    return SNMPERR_GENERR;
  }

  return SNMPERR_SUCCESS;
}

/* header_complex_parse_oid(): parses an index to the usmTable to
   break it down into a engineID component and a name component.
   The results are stored in the data pointer, as a varbindlist:


   returns 1 if an error is encountered, or 0 if successful.
*/
int header_complex_parse_oid(oid *oidIndex, size_t oidLen,
                         struct variable_list *data)
{
  struct variable_list *var = data;
  int i, itmp;
  
  while(var && oidLen > 0) {
    switch(var->type) {
      case ASN_INTEGER:
      case ASN_COUNTER:
      case ASN_GAUGE:
      case ASN_TIMETICKS:
        var->val.integer = (long *) calloc(1,sizeof(long));
        if (var->val.string == NULL)
            return SNMPERR_GENERR;

        *var->val.integer = (long) *oidIndex++;
        var->val_len = sizeof(long);
        oidLen--;
        DEBUGMSGTL(("header_complex_parse_oid",
                    "Parsed int(%d): %d\n", var->type, *var->val.integer));
        break;

      case ASN_OBJECT_ID:
      case ASN_PRIV_IMPLIED_OBJECT_ID:
        if (var->type == ASN_PRIV_IMPLIED_OBJECT_ID) {
            itmp = oidLen;
        } else {
            itmp = (long) *oidIndex++;
            oidLen--;
            if (itmp > (int)oidLen)
                return SNMPERR_GENERR;
        }

        if (itmp == 0)
          break;        /* zero length strings shouldn't malloc */
        
        var->val_len = itmp*sizeof(oid);
        var->val.objid = (oid *) calloc(1,var->val_len);
        if (var->val.objid == NULL)
            return SNMPERR_GENERR;

        for(i = 0; i < itmp; i++)
          var->val.objid[i] = (u_char) *oidIndex++;
        oidLen -= itmp;

        DEBUGMSGTL(("header_complex_parse_oid", "Parsed oid: "));
        DEBUGMSGOID(("header_complex_parse_oid", var->val.objid, var->val_len/sizeof(oid)));
        DEBUGMSG(("header_complex_parse_oid", "\n"));
        break;

      case ASN_OPAQUE:
      case ASN_OCTET_STR:
      case ASN_PRIV_IMPLIED_OCTET_STR:
        if (var->type == ASN_PRIV_IMPLIED_OCTET_STR) {
            itmp = oidLen;
        } else {
            itmp = (long) *oidIndex++;
            oidLen--;
            if (itmp > (int)oidLen)
                return SNMPERR_GENERR;
        }

        if (itmp == 0)
          break;        /* zero length strings shouldn't malloc */
        
        /* malloc by size+1 to allow a null to be appended. */
        var->val_len = itmp;
        var->val.string = (u_char *) calloc(1,itmp+1);
        if (var->val.string == NULL)
            return SNMPERR_GENERR;

        for(i = 0; i < itmp; i++)
          var->val.string[i] = (u_char) *oidIndex++;
        var->val.string[itmp] = '\0';
        oidLen -= itmp;

        DEBUGMSGTL(("header_complex_parse_oid",
                    "Parsed str(%d): %s\n", var->type, var->val.string));
        break;

      default:
        DEBUGMSGTL(("header_complex_parse_oid",
                    "invalid asn type: %d\n", var->type));
        return SNMPERR_GENERR;
    }
    var = var->next_variable;
  }
  if (var != NULL || oidLen > 0)
    return SNMPERR_GENERR;
  return SNMPERR_SUCCESS;
}


void
header_complex_generate_oid(oid *name, /* out */
                            size_t *length, /* out */
                            oid *prefix,
                            size_t prefix_len,
                            struct variable_list *data) {

  oid *oidptr;
  struct variable_list *var;
  
  if (prefix) {
    memcpy(name, prefix, prefix_len * sizeof(oid));
    oidptr = (name + (prefix_len));
    *length = prefix_len;
  } else {
    oidptr = name;
    *length = 0;
  }
    
  for(var = data; var != NULL; var = var->next_variable) {
    header_complex_generate_varoid(var);
    memcpy(oidptr, var->name, sizeof(oid) * var->name_length);
    oidptr = oidptr + var->name_length;
    *length += var->name_length;
  }
  
  DEBUGMSGTL(("header_complex_generate_oid", "generated: "));
  DEBUGMSGOID(("header_complex_generate_oid", name, *length));
  DEBUGMSG(("header_complex_generate_oid", "\n"));
}

/* finds the data in "datalist" stored at "index" */
void *
header_complex_get(struct header_complex_index *datalist,
                    struct variable_list *index) {
    oid searchfor[MAX_OID_LEN];
    size_t searchfor_len;
    struct header_complex_index *nptr;
    
    header_complex_generate_oid(searchfor,     /* out */
                                &searchfor_len, /* out */
                                NULL, 0, index);
    for(nptr = datalist; nptr != NULL; nptr = nptr->next) {
        if (snmp_oid_compare(searchfor, searchfor_len,
                             nptr->name, nptr->namelen) == 0)
            return nptr->data;
    }
    return NULL;
}


void *
header_complex(struct header_complex_index *datalist,
               struct variable *vp,
	       oid *name,
	       size_t *length,
	       int exact,
	       size_t *var_len,
	       WriteMethod **write_method) {

  struct header_complex_index *nptr, *found = NULL;
  oid indexOid[MAX_OID_LEN];
  size_t len;
  int result;
  
  /* set up some nice defaults for the user */
  if (write_method)
    *write_method = NULL;
  if (var_len)
    *var_len = sizeof (long);

  for(nptr = datalist; nptr != NULL && found == NULL; nptr = nptr->next) {
    if (vp) {
        memcpy(indexOid, vp->name, vp->namelen * sizeof(oid));
        memcpy(indexOid + vp->namelen, nptr->name,
               nptr->namelen * sizeof(oid));
        len = vp->namelen + nptr->namelen;
    } else {
        memcpy(indexOid, nptr->name, nptr->namelen * sizeof(oid));
        len = nptr->namelen;
    }
    result = snmp_oid_compare(name, *length, indexOid, len);
    DEBUGMSGTL(("header_complex", "Checking: "));
    DEBUGMSGOID(("header_complex", indexOid, len));
    DEBUGMSG(("header_complex", "\n"));

    if (exact) {
      if (result == 0) {
        found = nptr;
      }
    } else {
      if (result == 0) {
        /* found an exact match.  Need the next one for !exact */
        if (nptr->next)
          found = nptr->next;
      } else if (result == -1) {
        found = nptr;
      }
    }
  }
  if (found) {
      if (vp) {
          memcpy(name, vp->name, vp->namelen * sizeof(oid));
          memcpy(name + vp->namelen, found->name,
                 found->namelen * sizeof(oid));
          *length = vp->namelen + found->namelen;
      } else {
          memcpy(name, found->name, found->namelen * sizeof(oid));
          *length = found->namelen;
      }
      return found->data;
  }
    
  return NULL;
}

struct header_complex_index *
header_complex_add_data(struct header_complex_index **thedata,
                        struct variable_list *var, void *data) {
  struct header_complex_index *hciptrn, *hciptrp, *ourself;
  oid newoid[MAX_OID_LEN];
  size_t newoid_len;

  if (thedata == NULL || var == NULL || data == NULL)
    return NULL;
  
  header_complex_generate_oid(newoid, &newoid_len, NULL, 0, var);

  for(hciptrn = *thedata, hciptrp = NULL;
      hciptrn != NULL;
      hciptrp = hciptrn, hciptrn = hciptrn->next)
      /* XXX: check for == and error (overlapping table entries) */
    if (snmp_oid_compare(hciptrn->name, hciptrn->namelen, newoid, newoid_len)
        > 0)
      break;
  
  /* nptr should now point to the spot that we need to add ourselves
     in front of, and pptr should be our new 'prev'. */

  /* create ourselves */
  ourself = (struct header_complex_index *)
      SNMP_MALLOC_STRUCT(header_complex_index);

  /* change our pointers */
  ourself->prev = hciptrp;
  ourself->next = hciptrn;
    
  if (ourself->next)
    ourself->next->prev = ourself;

  if (ourself->prev)
    ourself->prev->next = ourself;

  ourself->data = data;
  ourself->name = snmp_duplicate_objid(newoid, newoid_len);
  ourself->namelen = newoid_len;

  /* free the variable list, but not the enclosed data!  it's not ours! */
  snmp_free_varbind(var);

  /* rewind to the head of the list and return it (since the new head
     could be us, we need to notify the above routine who the head now is. */
  for(hciptrp = ourself; hciptrp->prev != NULL; hciptrp = hciptrp->prev);

  *thedata = hciptrp;
  DEBUGMSGTL(("header_complex_add_data", "adding something...\n"));

  return hciptrp;
}

/* extracts an entry from the storage space (removing it from future
   accesses) and returns the data stored there

   Modifies "thetop" pointer as needed (and if present) if were
   extracting the first node.
*/

void *
header_complex_extract_entry(struct header_complex_index **thetop,
                             struct header_complex_index *thespot) {
  struct header_complex_index *hciptrp, *hciptrn;
  void *retdata = thespot->data;

  if (thespot == NULL) {
    DEBUGMSGTL(("header_complex_extract_entry",
                "Null pointer asked to be extracted\n"));
    return NULL;
  }
  
  hciptrp = thespot->prev;
  hciptrn = thespot->next;

  if (hciptrp)
    hciptrp->next = hciptrn;
  else if (thetop)
    *thetop = hciptrn;

  if (hciptrn)
    hciptrn->prev = hciptrp;

  if (thespot->name)
      free(thespot->name);

  free(thespot);
  return retdata;
}

/* wipe out a single entry */
void
header_complex_free_entry(struct header_complex_index *theentry,
                          HeaderComplexCleaner *cleaner) {
  void *data;
  data = header_complex_extract_entry(NULL, theentry);
  (*cleaner)(data);
}

/* completely wipe out all entries in our data store */
void
header_complex_free_all(struct header_complex_index *thestuff,
                         HeaderComplexCleaner *cleaner) {
  struct header_complex_index *hciptr, *hciptrn;  

  for(hciptr = thestuff; hciptr != NULL; hciptr = hciptrn) {
    hciptrn = hciptr->next; /* need to extract this before deleting it */
    header_complex_free_entry(hciptr, cleaner);
  }
}

struct header_complex_index *
header_complex_find_entry(struct header_complex_index *thestuff,
                          void *theentry) {
  struct header_complex_index *hciptr;  

  for(hciptr = thestuff; hciptr != NULL && hciptr->data != theentry;
      hciptr = hciptr->next);
  return hciptr;
}

#ifdef TESTING

void
header_complex_dump(struct header_complex_index *thestuff) {
  struct header_complex_index *hciptr;
  oid oidsave[MAX_OID_LEN];
  size_t len;
  
  for(hciptr = thestuff; hciptr != NULL; hciptr = hciptr->next) {
    DEBUGMSGTL(("header_complex_dump", "var:  "));
    header_complex_generate_oid(oidsave, &len, NULL, 0, hciptr->);
    DEBUGMSGOID(("header_complex_dump", oidsave, len));
    DEBUGMSG(("header_complex_dump", "\n"));
  }
}

main() {
  oid oidsave[MAX_OID_LEN];
  int len = MAX_OID_LEN, len2;
  struct variable_list *vars;
  long ltmp = 4242, ltmp2=88, ltmp3 = 1, ltmp4 = 4200;
  oid ourprefix[] = { 1,2,3,4};
  oid testparse[] = { 4,116,101,115,116,4200};
  int ret;
  
  char *string="wes", *string2 = "dawn", *string3 = "test";

  struct header_complex_index *thestorage = NULL;

  debug_register_tokens("header_complex");
  snmp_set_do_debugging(1);
  
  vars = NULL;
  len2 = sizeof(ltmp);
  snmp_varlist_add_variable(&vars, NULL, 0, ASN_INTEGER, (char *) &ltmp, len2);
  header_complex_add_data(&thestorage, vars, ourprefix);

  vars = NULL;
  len2 = strlen(string);
  snmp_varlist_add_variable(&vars, NULL, 0, ASN_OCTET_STR, string, len2);
  header_complex_add_data(&thestorage, vars, ourprefix);

  vars = NULL;
  len2 = sizeof(ltmp2);
  snmp_varlist_add_variable(&vars, NULL, 0, ASN_INTEGER, (char *) &ltmp2, len2);
  header_complex_add_data(&thestorage, vars, ourprefix);

  vars = NULL;
  len2 = strlen(string2);
  snmp_varlist_add_variable(&vars, NULL, 0, ASN_OCTET_STR, string2, len2);
  header_complex_add_data(&thestorage, vars, ourprefix);

  vars = NULL;
  len2 = sizeof(ltmp3);
  snmp_varlist_add_variable(&vars, NULL, 0, ASN_INTEGER, (char *) &ltmp3, len2);
  header_complex_add_data(&thestorage, vars, ourprefix);

  vars = NULL;
  len2 = strlen(string3);
  snmp_varlist_add_variable(&vars, NULL, 0, ASN_OCTET_STR, string3, len2);
  len2 = sizeof(ltmp4);
  snmp_varlist_add_variable(&vars, NULL, 0, ASN_INTEGER, (char *) &ltmp4, len2);
  header_complex_add_data(&thestorage, vars, ourprefix);

  header_complex_dump(thestorage);

  vars = NULL;
  snmp_varlist_add_variable(&vars, NULL, 0, ASN_OCTET_STR, NULL, 0);
  snmp_varlist_add_variable(&vars, NULL, 0, ASN_INTEGER, NULL, 0);
  ret = header_complex_parse_oid(testparse, sizeof(testparse)/sizeof(oid), vars);
  DEBUGMSGTL(("header_complex_test", "parse returned %d...\n", ret));

}
#endif
