/*
 * Abstract Syntax Notation One, ASN.1
 * As defined in ISO/IS 8824 and ISO/IS 8825
 * This implements a subset of the above International Standards that
 * is sufficient to implement SNMP.
 *
 * Encodes abstract data types into a machine independent stream of bytes.
 *
 */
/***************************************************************************
 *
 *           Copyright 1998 by Carnegie Mellon University
 * 
 *                       All Rights Reserved
 * 
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of CMU not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * 
 * CMU DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
 * CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 * 
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <sys/types.h>
#ifdef WIN32
#include <memory.h>
#include <winsock2.h>
#else  /* WIN32 */
#include <netinet/in.h>
#endif /* WIN32 */

#include "asn1.h"
#include "snmp_api_error.h"

static char rcsid[] =
"$Id: asn1.c,v 1.9 1998/05/06 04:06:47 ryan Exp $";

u_char *asn_build_header(u_char *data,       /* IN - ptr to start of object */
			 int    *datalength, /* IN/OUT - # of valid bytes */
                                             /*          left in buffer */
			 u_char  type,       /* IN - ASN type of object */
			 int     length)     /* IN - length of object */
{
  /* Truth is 0 'cause we don't know yet */
  return(asn_build_header_with_truth(data, datalength, type, length, 0));
}


/*
 * asn_parse_int - pulls an int out of an ASN int type.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   following the end of this object.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 */
u_char *asn_parse_int(u_char *data, int *datalength, 
		      u_char *type, int *intp, int intsize)
  /*    u_char *data;        IN     - pointer to start of object */
  /*    int    *datalength;  IN/OUT - # of valid bytes left in buffer */
  /*    u_char *type;        OUT    - asn type of object */
  /*    int   *intp;         IN/OUT - pointer to start of output buffer */
  /*    int     intsize;     IN     - size of output buffer */
{
  /*
   * ASN.1 integer ::= 0x02 asnlength byte {byte}*
   */
  u_char *bufp = data;
  u_int   asn_length;
  int   value = 0;
  
  /* Room to store int? */
  if (intsize != sizeof (int)) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  /* Type */
  *type = *bufp++;

  /* Extract length */
  bufp = asn_parse_length(bufp, &asn_length);
  if (bufp == NULL)
    return(NULL);

  /* Make sure the entire int is in the buffer */
  if (asn_length + (bufp - data) > (u_int)(*datalength)) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  /* Can we store this int? */
  if (asn_length > (u_int)intsize) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  /* Remaining data */
  *datalength -= (int)asn_length + (bufp - data);

  /* Is the int negative? */
  if (*bufp & 0x80)
    value = -1; /* integer is negative */

  /* Extract the bytes */
  while(asn_length--)
    value = (value << 8) | *bufp++;

  /* That's it! */
  *intp = value;
  return(bufp);
}

/*
 * asn_parse_unsigned_int - pulls an unsigned int out of an ASN int type.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   following the end of this object.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 */
u_char *asn_parse_unsigned_int(u_char *data, int *datalength, 
			       u_char *type, u_int *intp, int intsize)
  /*    u_char *data;          IN     - pointer to start of object */
  /*    int    *datalength;    IN/OUT - # of valid bytes left in buffer */
  /*    u_char *type;          OUT    - asn type of object */
  /*    u_int *intp;           IN/OUT - pointer to start of output buffer */
  /*    int     intsize;       IN     - size of output buffer */
{
  /*
   * ASN.1 integer ::= 0x02 asnlength byte {byte}*
   */
  u_char *bufp = data;
  u_int  asn_length;
  int    value = 0;
  
  /* Room to store int? */
  if (intsize != sizeof (int)) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  /* Type */
  *type = *bufp++;

  /* Extract length */
  bufp = asn_parse_length(bufp, &asn_length);
  if (bufp == NULL)
    return(NULL);

  /* Make sure the entire int is in the buffer */
  if (asn_length + (bufp - data) > (u_int)(*datalength)) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  /* Can we store this int? */
  if ((asn_length > ((u_int)intsize + 1)) ||
      ((asn_length == (u_int)intsize + 1) && *bufp != 0x00)) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  /* Remaining data */
  *datalength -= (int)asn_length + (bufp - data);

  /* Is the int negative? */
  if (*bufp & 0x80)
    value = -1; /* integer is negative */

  /* Extract the bytes */
  while(asn_length--)
    value = (value << 8) | *bufp++;

  /* That's it! */
  *intp = value;
  return(bufp);
}

/*
 * asn_build_int - builds an ASN object containing an integer.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   following the end of this object.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 */
u_char *asn_build_int(u_char *data, int *datalength, 
		      u_char type, int *intp, int intsize)
  /*     u_char *data;         IN - pointer to start of output buffer */
  /*     int    *datalength;   IN/OUT - # of valid bytes left in buffer */
  /*     u_char  type;         IN - asn type of object */
  /*     int   *intp;          IN - pointer to start of integer */
  /*     int    intsize;       IN - size of *intp */
{
  /*
   * ASN.1 integer ::= 0x02 asnlength byte {byte}*
   */
  int integer;
  u_int mask;
    
  if (intsize != sizeof (int)) {
    snmp_set_api_error(SNMPERR_ASN_ENCODE);
    return(NULL);
  }

  integer = *intp;

  /*
   * Truncate "unnecessary" bytes off of the most significant end of this
   * 2's complement integer.  There should be no sequence of 9
   * consecutive 1's or 0's at the most significant end of the
   * integer.
   */
  mask = (u_int)0x1FF << ((8 * (sizeof(int) - 1)) - 1);
  /* mask is 0xFF800000 on a big-endian machine */

  while((((integer & mask) == 0) || ((integer & mask) == mask))
	&& intsize > 1) {
    intsize--;
    integer <<= 8;
  }

  data = asn_build_header_with_truth(data, datalength, type, intsize, 1);
  if (data == NULL)
    return(NULL);

  /* Enough room for what we just encoded? */
  if (*datalength < intsize) {
    snmp_set_api_error(SNMPERR_ASN_ENCODE);
    return(NULL);
  }

  /* Insert it */
  *datalength -= intsize;
  mask = (u_int)0xFF << (8 * (sizeof(int) - 1));
  /* mask is 0xFF000000 on a big-endian machine */
  while(intsize--) {
    *data++ = (u_char)((integer & mask) >> (8 * (sizeof(int) - 1)));
    integer <<= 8;
  }
  return(data);
}

/*
 * asn_build_unsigned_int - builds an ASN object containing an integer.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   following the end of this object.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 */
u_char *asn_build_unsigned_int(u_char *data, int *datalength, 
			       u_char type, u_int *intp, int intsize)
  /*     u_char *data;         IN     - pointer to start of output buffer */
  /*     int    *datalength;   IN/OUT - # of valid bytes left in buffer */
  /*     u_char  type;         IN     - asn type of object */
  /*     u_int  *intp;         IN     - pointer to start of integer */
  /*     int     intsize;      IN     - size of *intp */
{
  /*
   * ASN.1 integer ::= 0x02 asnlength byte {byte}*
   */
  u_int integer;
  u_int mask;
  int add_null_byte = 0;
    
  if (intsize != sizeof (int)) {
    snmp_set_api_error(SNMPERR_ASN_ENCODE);
    return(NULL);
  }

  integer = *intp;
  mask = (u_int)0xFF << (8 * (sizeof(int) - 1));
  /* mask is 0xFF000000 on a big-endian machine */
  if ((u_char)((integer & mask) >> (8 * (sizeof(int) - 1))) & 0x80) {
    /* if MSB is set */
    add_null_byte = 1;
    intsize++;
  }

  /*
   * Truncate "unnecessary" bytes off of the most significant end of
   * this 2's complement integer. 
   * There should be no sequence of 9 consecutive 1's or 0's at the
   * most significant end of the integer.
   */
  mask = (u_int)0x1FF << ((8 * (sizeof(int) - 1)) - 1);
  /* mask is 0xFF800000 on a big-endian machine */
  while((((integer & mask) == 0)
	 || ((integer & mask) == mask)) && intsize > 1) {
    intsize--;
    integer <<= 8;
  }
  
  data = asn_build_header_with_truth(data, datalength, type, intsize, 1);
  if (data == NULL)
    return(NULL);

  if (*datalength < intsize) {
    snmp_set_api_error(SNMPERR_ASN_ENCODE);
    return(NULL);
  }

  *datalength -= intsize;
  if (add_null_byte == 1) {
    *data++ = '\0';
    intsize--;
  }
  
  mask = (u_int)0xFF << (8 * (sizeof(int) - 1));
  /* mask is 0xFF000000 on a big-endian machine */
  while(intsize--) {
    *data++ = (u_char)((integer & mask) >> (8 * (sizeof(int) - 1)));
    integer <<= 8;
  }
  return(data);
}

/*
 * asn_parse_string - pulls an octet string out of an ASN octet string type.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   following the beginning of the next object.
 *
 *  "string" is filled with the octet string.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 */
u_char *asn_parse_string(u_char *data, int *datalength, 
			 u_char *type, u_char *string, int *strlength)
  /*    u_char *data;       IN - pointer to start of object */
  /*    int    *datalength; IN/OUT - # of valid bytes left in buffer */
  /*    u_char *type;       OUT - asn type of object */
  /*    u_char *string;     IN/OUT - pointer to start of output buffer */
  /*    int    *strlength;  IN/OUT - size of output buffer */
{
  /*
   * ASN.1 octet string ::= primstring | cmpdstring
   * primstring ::= 0x04 asnlength byte {byte}*
   * cmpdstring ::= 0x24 asnlength string {string}*
   */
  u_char *bufp = data;
  u_int          asn_length;
    
  *type = *bufp++;
  bufp = asn_parse_length(bufp, &asn_length);
  if (bufp == NULL)
    return(NULL);

  if (asn_length + (bufp - data) > (u_int)(*datalength)) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  if (asn_length > (u_int)(*strlength)) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  memcpy((char *)string, (char *)bufp, (int)asn_length);
  *strlength = (int)asn_length;
  *datalength -= (int)asn_length + (bufp - data);
  return(bufp + asn_length);
}

/*
 * asn_build_string - Builds an ASN octet string object containing the input
 *   string.  On entry, datalength is input as the number of valid bytes 
 *   following "data".  On exit, it is returned as the number of valid bytes
 *   following the beginning of the next object.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 */
u_char *asn_build_string(u_char *data, int *datalength, 
			 u_char type, u_char *string, int strlength)
  /*    u_char *data;       IN - pointer to start of object */
  /*    int    *datalength; IN/OUT - # of valid bytes left in buf */
  /*    u_char  type;       IN - ASN type of string */
  /*    u_char *string;     IN - pointer to start of input buffer */
  /*    int     strlength;  IN - size of input buffer */
{
  /*
   * ASN.1 octet string ::= primstring | cmpdstring
   * primstring ::= 0x04 asnlength byte {byte}*
   * cmpdstring ::= 0x24 asnlength string {string}*
   * This code will never send a compound string.
   */
  data = asn_build_header_with_truth(data, datalength, type, strlength, 1);
  if (data == NULL)
    return(NULL);

  if (*datalength < strlength) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  memcpy((char *)data, (char *)string, strlength);
  *datalength -= strlength;
  return(data + strlength);
}


/*
 * asn_parse_header - interprets the ID and length of the current object.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   in this object following the id and length.
 *
 *  Returns a pointer to the first byte of the contents of this object.
 *  Returns NULL on any error.
 */
u_char *asn_parse_header(u_char *data, int *datalength, u_char *type)
  /*    u_char  *data;       IN - pointer to start of object */
  /*    int     *datalength; IN/OUT - # of valid bytes left in buffer */
  /*    u_char  *type;       OUT - ASN type of object */
{
  u_char *bufp = data;
  int     header_len;
  u_int  asn_length;
    
  /* this only works on data types < 30, i.e. no extension octets */
  if (IS_EXTENSION_ID(*bufp)) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  *type = *bufp;
  bufp = asn_parse_length(bufp + 1, &asn_length);
  if (bufp == NULL)
    return(NULL);

  header_len = bufp - data;
  if (header_len + asn_length > (u_int)(*datalength)) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  *datalength = (int)asn_length;
  return(bufp);
}

/*
 * asn_build_header - builds an ASN header for an object with the ID and
 * length specified.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   in this object following the id and length.
 *
 *  This only works on data types < 30, i.e. no extension octets.
 *  The maximum length is 0xFFFF;
 *
 *  Returns a pointer to the first byte of the contents of this object.
 *  Returns NULL on any error.
 */

u_char *asn_build_header_with_truth(u_char *data, int *datalength, 
				    u_char type, int length, int truth)
  /*    u_char *data;       IN - pointer to start of object */
  /*    int    *datalength; IN/OUT - # of valid bytes left in buffer */
  /*    u_char  type;       IN - ASN type of object */
  /*    int     length;     IN - length of object */
  /*    int     truth;      IN - Whether length is truth */
{
  if (*datalength < 1) {
    snmp_set_api_error(SNMPERR_ASN_ENCODE);
    return(NULL);
  }

  *data++ = type;
  (*datalength)--;
  return(asn_build_length(data, datalength, length, truth));
}

#if 0
/*
 * asn_build_sequence - builds an ASN header for a sequence with the ID and
 * length specified.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   in this object following the id and length.
 *
 *  This only works on data types < 30, i.e. no extension octets.
 *  The maximum length is 0xFFFF;
 *
 *  Returns a pointer to the first byte of the contents of this object.
 *  Returns NULL on any error.
 */
static u_char *asn_build_sequence(u_char *data, int *datalength, 
				  u_char type, int length)
  /*    u_char *data;       IN - pointer to start of object */
  /*    int    *datalength; IN/OUT - # of valid bytes left in buffer */
  /*    u_char  type;       IN - ASN type of object */
  /*    int     length;     IN - length of object */
{
  *datalength -= 4;
  if (*datalength < 0) {
    *datalength += 4;   /* fix up before punting */
    snmp_set_api_error(SNMPERR_ASN_ENCODE);
    return(NULL);
  }

  *data++ = type;
  *data++ = (u_char)(0x02 | ASN_LONG_LEN);
  *data++ = (u_char)((length >> 8) & 0xFF);
  *data++ = (u_char)(length & 0xFF);
  return(data);
}
#endif

/*
 * asn_parse_length - interprets the length of the current object.
 *  On exit, length contains the value of this length field.
 *
 *  Returns a pointer to the first byte after this length
 *  field (aka: the start of the data field).
 *  Returns NULL on any error.
 */
u_char *asn_parse_length(u_char *data, u_int *length)
  /*    u_char  *data;   IN - pointer to start of length field */
  /*    u_int  *length; OUT - value of length field */
{
  u_char lengthbyte = *data;
    
  if (lengthbyte & ASN_LONG_LEN) {
    lengthbyte &= ~ASN_LONG_LEN;    /* turn MSb off */

    if (lengthbyte == 0) {
      snmp_set_api_error(SNMPERR_ASN_DECODE);
      return(NULL);
    }

    if (lengthbyte > sizeof(int)) {
      snmp_set_api_error(SNMPERR_ASN_DECODE);
      return(NULL);
    }

    *length = (u_int)0;
    memcpy((char *)(length), (char *)data + 1, (int)lengthbyte);
    *length = ntohl(*length);
    *length >>= (8 * ((sizeof *length) - lengthbyte));
    return(data + lengthbyte + 1);

  }

  /* short asnlength */

  *length = (int)lengthbyte;
  return(data + 1);
}


u_char *asn_build_length(u_char *data, int *datalength, 
			 int length, int truth)
  /*   u_char *data;       IN - pointer to start of object */
  /*   int    *datalength; IN/OUT - # of valid bytes left in buf */
  /*   int     length;     IN - length of object */
  /*   int     truth;      IN - If 1, this is the true len. */
{
  u_char    *start_data = data;

  if (truth) {

    /* no indefinite lengths sent */
    if (length < 0x80) {
      if (*datalength < 1) {
	snmp_set_api_error(SNMPERR_ASN_ENCODE);
	return(NULL);
      }
      *data++ = (u_char)length;

    } else if (length <= 0xFF) {
      if (*datalength < 2) {
	snmp_set_api_error(SNMPERR_ASN_ENCODE);
	return(NULL);
      }
      *data++ = (u_char)(0x01 | ASN_LONG_LEN);
      *data++ = (u_char)length;
    } else { /* 0xFF < length <= 0xFFFF */
      if (*datalength < 3) {
	snmp_set_api_error(SNMPERR_ASN_ENCODE);
	return(NULL);
      }
      *data++ = (u_char)(0x02 | ASN_LONG_LEN);
      *data++ = (u_char)((length >> 8) & 0xFF);
      *data++ = (u_char)(length & 0xFF);
    }

  } else {

    /* Don't know if this is the true length.  Make sure it's large
     * enough for later.
     */
    if (*datalength < 3) {
      snmp_set_api_error(SNMPERR_ASN_ENCODE);
      return(NULL);
    }
    *data++ = (u_char)(0x02 | ASN_LONG_LEN);
    *data++ = (u_char)((length >> 8) & 0xFF);
    *data++ = (u_char)(length & 0xFF);
  }

  *datalength -= (data - start_data);
  return(data);
}

/*
 * asn_parse_objid - pulls an object indentifier out of an ASN object
 * identifier type.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   following the beginning of the next object.
 *
 *  "objid" is filled with the object identifier.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 */
u_char *asn_parse_objid(u_char *data, int *datalength, 
			u_char *type, oid *objid, int *objidlength)
  /*    u_char  *data;        IN - pointer to start of object */
  /*    int     *datalength;  IN/OUT - # of valid bytes left in buf */
  /*    u_char  *type;        OUT - ASN type of object */
  /*    oid     *objid;       IN/OUT - pointer to start of output buffer */
  /*    int     *objidlength; IN/OUT - number of sub-id's in objid */
{
  /*
   * ASN.1 objid ::= 0x06 asnlength subidentifier {subidentifier}*
   * subidentifier ::= {leadingbyte}* lastbyte
   * leadingbyte ::= 1 7bitvalue
   * lastbyte ::= 0 7bitvalue
   */
  u_char     *bufp = data;
  oid        *oidp = objid + 1;
  u_int     subidentifier;
  int       length;
  u_int              asn_length;

  *type = *bufp++;
  bufp = asn_parse_length(bufp, &asn_length);
  if (bufp == NULL)
    return(NULL);

  if (asn_length + (bufp - data) > (u_int)(*datalength)) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }
  *datalength -= (int)asn_length + (bufp - data);
    
  /* Handle invalid object identifier encodings of the form 06 00 robustly */
  if (asn_length == 0)
    objid[0] = objid[1] = 0;
    
  length = asn_length;
  (*objidlength)--;   /* account for expansion of first byte */
  while (length > 0 && (*objidlength)-- > 0) {
    subidentifier = 0;

    do {    /* shift and add in low order 7 bits */
      subidentifier = (subidentifier << 7)
	+ (*(u_char *)bufp & ~ASN_BIT8);
      length--;
    } while (*(u_char *)bufp++ & ASN_BIT8);

    /* while last byte has high bit clear */
    if (subidentifier > (u_int)MAX_SUBID) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
    }
    *oidp++ = (oid)subidentifier;
  }
    
  /*
   * The first two subidentifiers are encoded into the first component
   * with the value (X * 40) + Y, where:
   *  X is the value of the first subidentifier.
   *  Y is the value of the second subidentifier.
   */
  subidentifier = (u_int)objid[1];
  if (subidentifier == 0x2B) {
    objid[0] = 1;
    objid[1] = 3;
  } else {
    objid[1] = (u_char)(subidentifier % 40);
    objid[0] = (u_char)((subidentifier - objid[1]) / 40);
  }
  
  *objidlength = (int)(oidp - objid);
  return(bufp);
}

/*
 * asn_build_objid - Builds an ASN object identifier object containing the
 * input string.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   following the beginning of the next object.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 */
u_char *asn_build_objid(u_char *data, int *datalength, 
			u_char type, oid *objid, int objidlength)
  /*    u_char *data;         IN - pointer to start of object */
  /*    int    *datalength;   IN/OUT - # of valid bytes left in buf */
  /*    u_char  type;         IN - ASN type of object */
  /*    oid    *objid;        IN - pointer to start of input buffer */
  /*    int     objidlength;  IN - number of sub-id's in objid */
{
  /*
   * ASN.1 objid ::= 0x06 asnlength subidentifier {subidentifier}*
   * subidentifier ::= {leadingbyte}* lastbyte
   * leadingbyte ::= 1 7bitvalue
   * lastbyte ::= 0 7bitvalue
   */
  u_char  buf[MAX_OID_LEN];
  u_char *bp = buf;
  oid    *op = objid;
  int     asnlength;
  u_int  subid, mask, testmask;
  int     bits, testbits;
    
  if (objidlength < 2) {
    *bp++ = 0;
    objidlength = 0;
  } else {
    *bp++ = op[1] + (op[0] * 40);
    objidlength -= 2;
    op += 2;
  }
    
  while(objidlength-- > 0) {
    subid = *op++;
    if (subid < 127) { /* off by one? */
      *bp++ = subid;
    } else {
      mask = 0x7F; /* handle subid == 0 case */
      bits = 0;
      /* testmask *MUST* !!!! be of an unsigned type */
      for(testmask = 0x7F, testbits = 0; testmask != 0;
	  testmask <<= 7, testbits += 7) {
	if (subid & testmask) {  /* if any bits set */
	  mask = testmask;
	  bits = testbits;
	}
      }
      /* mask can't be zero here */
      for(;mask != 0x7F; mask >>= 7, bits -= 7) {
	/* fix a mask that got truncated above */
	if (mask == 0x1E00000)  
	  mask = 0xFE00000;
	*bp++ = (u_char)(((subid & mask) >> bits) | ASN_BIT8);
      }
      *bp++ = (u_char)(subid & mask);
    }
  }

  asnlength = bp - buf;
  data = asn_build_header_with_truth(data, datalength, type, asnlength, 1);
  if (data == NULL) 
    return(NULL);
  if (*datalength < asnlength) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }
  memcpy((char *)data, (char *)buf, asnlength);
  *datalength -= asnlength;
  return(data + asnlength);
}

/*
 * asn_parse_null - Interprets an ASN null type.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   following the beginning of the next object.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 */
u_char *asn_parse_null(u_char *data, int *datalength, u_char *type)
  /*    u_char  *data;       IN - pointer to start of object */
  /*    int     *datalength; IN/OUT - # of valid bytes left in buf */
  /*    u_char  *type;       OUT - ASN type of object */
{
  /*
   * ASN.1 null ::= 0x05 0x00
   */
  u_char     *bufp = data;
  u_int              asn_length;
    
  *type = *bufp++;
  bufp = asn_parse_length(bufp, &asn_length);
  if (bufp == NULL)
    return(NULL);

  if (asn_length != 0) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
    }

  *datalength -= (bufp - data);
  return(bufp + asn_length);
}

/*
 * asn_build_null - Builds an ASN null object.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   following the beginning of the next object.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 */
u_char *asn_build_null(u_char *data, int *datalength, u_char type)
  /*    u_char  *data;       IN - pointer to start of object */
  /*    int     *datalength; IN/OUT - # of valid bytes left in buf */
  /*    u_char   type;       IN - ASN type of object */
{
  /*
   * ASN.1 null ::= 0x05 0x00
   */
  return(asn_build_header_with_truth(data, datalength, type, 0, 1));
}

/*
 * asn_parse_bitstring - pulls a bitstring out of an ASN bitstring type.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   following the beginning of the next object.
 *
 *  "string" is filled with the bit string.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 */
u_char *asn_parse_bitstring(u_char *data, int *datalength, 
			    u_char *type, u_char *string, int *strlength)
  /*   u_char  *data;        IN - pointer to start of object */
  /*   int     *datalength;  IN/OUT - # of valid bytes left in buf */
  /*   u_char  *type;        OUT - asn type of object */
  /*   u_char  *string;      IN/OUT - pointer to start of output buf */
  /*   int     *strlength;   IN/OUT - size of output buffer */
{
  /*
   * bitstring ::= 0x03 asnlength unused {byte}*
   */
  u_char     *bufp = data;
  u_int              asn_length;
    
  *type = *bufp++;
  bufp = asn_parse_length(bufp, &asn_length);
  if (bufp == NULL)
    return(NULL);

  if (asn_length + (bufp - data) > (u_int)(*datalength)) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  if (asn_length > (u_int)(*strlength)) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  if (asn_length < 1) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  if (*bufp > 7) {
    snmp_set_api_error(SNMPERR_ASN_DECODE);
    return(NULL);
  }

  memcpy((char *)string, (char *)bufp, (int)asn_length);
  *strlength = (int)asn_length;
  *datalength -= (int)asn_length + (bufp - data);
  return(bufp + asn_length);
}

/*
 * asn_build_bitstring - Builds an ASN bit string object containing the
 * input string.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   following the beginning of the next object.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 */
u_char *asn_build_bitstring(u_char *data, int *datalength, 
			    u_char type, u_char *string, int strlength)
  /*   u_char  *data;       IN - pointer to start of object */
  /*   int     *datalength; IN/OUT - # of valid bytes left in buf */
  /*   u_char   type;       IN - ASN type of string */
  /*   u_char  *string;     IN - pointer to start of input buffer */
  /*   int      strlength;  IN - size of input buffer */
{
  /*
   * ASN.1 bit string ::= 0x03 asnlength unused {byte}*
   */
  if (strlength < 1 || *string > 7) {
    snmp_set_api_error(SNMPERR_ASN_ENCODE);
    return(NULL);
  }

  data = asn_build_header_with_truth(data, datalength, type, strlength, 1);
  if (data == NULL)
    return(NULL);

  if (*datalength < strlength) {
    snmp_set_api_error(SNMPERR_ASN_ENCODE);
    return(NULL);
  }

  memcpy((char *)data, (char *)string, strlength);
  *datalength -= strlength;
  return(data + strlength);
}


/*
 * To do: Write an asn_parse_exception function to go with the new 
 * asn_build_exception function below so that the exceptional values can
 * be handled in input packets aswell as output ones.
 */

/*
 * asn_build_exception - Builds an ASN exception object.
 *  On entry, datalength is input as the number of valid bytes following
 *   "data".  On exit, it is returned as the number of valid bytes
 *   following the beginning of the next object.
 *
 *  Returns a pointer to the first byte past the end
 *   of this object (i.e. the start of the next object).
 *  Returns NULL on any error.
 *
 * ASN.1 variable exception ::= 0x8i 0x00, where 'i' is one of these 
 *                                         exception identifiers:
 *                                           0 -- noSuchObject
 *                                           1 -- noSuchInstance
 *                                           2 -- endOfMibView
 */
u_char *asn_build_exception(u_char *data, int *datalength, u_char type)
  /*    u_char  *data;       IN - pointer to start of object */
  /*    int     *datalength; IN/OUT - # of valid bytes left in buf */
  /*    u_char   type;       IN - ASN type of object */
{
  return(asn_build_header_with_truth(data, datalength, type, 0, 1));
}
