/*
 * GXSNMP -- An snmp mangament application
 * Copyright (C) 1998 Gregory McLean & Jochen Friedrich
 * Beholder RMON ethernet network monitor, Copyright (C) 1993 DNPAP group
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
 * MODULE INFORMATION
 * ------------------ 
 *     FILE     NAME:       gxsnmp_asn1.c
 *     SYSTEM   NAME:       ASN1 Basic Encoding
 *     ORIGINAL AUTHOR(S):  Dirk Wisse
 *     VERSION  NUMBER:     1
 *     CREATION DATE:       1990/11/22
 *
 * DESCRIPTION: ASN1 Basic Encoding Rules.
 *              Encoding takes place from end to begin. A normal
 *              procedure for definite encoding is:
 *
 *              asn1 = gxsnmp_asn1_new (buf, sizeof (buf), GXSNMP_ASN1_ENC);
 *              gxsnmp_asn1_eoc_encode (asn1, &end_of_seq);
 *              gxsnmp_asn1_int_encode (asn1, &end_of_int, 3);
 *              gxsnmp_asn1_header_encode (asn1, end_of_int, 
 *                      GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI, GXSNMP_ASN1_INT);
 *              gxsnmp_asn1_octets_encode (asn1, &end_of_octs, "String", 6);
 *              gxsnmp_asn1_header_encode (asn1, end_of_octets, 
 *                      GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI, GXSNMP_ASN1_OTS);
 *              gxsnmp_asn1_header_encode (asn1, end_of_seq, 
 *                      GXSNMP_ASN1_UNI, GXSNMP_ASN1_CON, GXSNMP_ASN1_SEQ);
 *              gxsnmp_asn1_destroy (asn1, &buf_start, &buf_len);
 *
 *              To decode this we must do:
 *
 *              asn1 = gxsnmp_asn1_open (buf_start, buf_len, GXSNMP_ASN1_DEC);
 *              gxsnmp_asn1_header_decode (asn1, &end_of_seq, cls, con, tag);
 *              gxsnmp_asn1_header_decode (asn1, &end_of_octs, cls, con, tag);
 *              gxsnmp_asn1_octets_decode (asn1, end_of_octs, str, len);
 *              gxsnmp_asn1_header_decode (asn1, &end_of_int, cls, con, tag);
 *              gxsnmp_asn1_int_decode (asn1, end_of_int, &integer);
 *              gxsnmp_asn1_eoc_decode (asn1, end_of_seq);
 *              gxsnmp_asn1_destroy (asn1, &buf_start, &buf_len);
 *              
 *              For indefinite encoding end_of_seq and &end_of_seq in the
 *              example above should be replaced by NULL.
 *              For indefinite decoding nothing has to be changed.
 *              This can be very useful if you want to decode both
 *              definite and indefinite encodings.
 */

#include "gxsnmp_asn1.h"

/**
 * gxsnmp_asn1_new:
 * @buf: Character buffer for encoding.
 * @len: Length of character buffer.
 * @mode: Encoding, Decoding (GXSNMP_ASN1_ENC, GXSNMP_ASN1_DEC).
 *
 * Creates an ASN1 object.
 * Encoding starts at the end of the buffer, and proceeds to the beginning.
 *
 * Return value: asn1 object.
 *
 **/
GxsnmpAsn1 *
gxsnmp_asn1_new(guchar *buf, guint len, GxsnmpAsn1Mode mode)
{
    GxsnmpAsn1 *asn1;

    asn1 = g_new(GxsnmpAsn1, 1);
    asn1->begin = buf;
    asn1->end = buf + len;
    asn1->pointer = (mode == GXSNMP_ASN1_ENC) ? buf + len : buf;
    asn1->error = GXSNMP_ASN1_ERR_NOERROR;
}

/**
 * gxsnmp_asn1_destroy:
 * @asn1: self
 * @buf: Pointer to beginning of encoding.
 * @len: Length of encoding.
 *
 * Destroys an ASN1 object.
 *
 **/
void 
gxsnmp_asn1_destroy(GxsnmpAsn1 *asn1, guchar **buf, guint *len)
{
    *buf   = asn1->pointer;
    *len   = asn1->end - asn1->pointer;
    g_free(asn1);
}

/**
 * gxsnmp_asn1_octet_encode:
 * @asn1: self
 * @ch: Octet to be encoded.
 *
 * Encodes an octet.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_octet_encode(GxsnmpAsn1 *asn1, guchar ch)
{
    if (asn1->pointer <= asn1->begin)
	{
		asn1->error = GXSNMP_ASN1_ERR_ENC_FULL;
        return FALSE;
	}
    *--(asn1->pointer) = ch;
    return TRUE;
}

/**
 * gxsnmp_asn1_octet_decode:
 * @asn1: self
 * @ch: Pointer to decoded octet.
 *
 * Decodes an octet.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_octet_decode(GxsnmpAsn1 *asn1, guchar *ch)
{
    if (asn1->pointer >= asn1->end)
	{
		asn1->error = GXSNMP_ASN1_ERR_DEC_EMPTY;
        return FALSE;
	}
    *ch = *(asn1->pointer)++;
    return TRUE;
}

/**
 * gxsnmp_asn1_tag_encode:
 * @asn1: self
 * @tag: Tag to be encoded.
 *
 * Encodes a tag.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_tag_encode(GxsnmpAsn1 *asn1, GxsnmpAsn1Tag tag)
{
    guchar ch;

    ch = (guchar) (tag & 0x7F);
    tag >>= 7;
    if (!gxsnmp_asn1_octet_encode(asn1, ch))
        return FALSE;
    while (tag > 0)
    {
        ch = (guchar) (tag | 0x80);
        tag >>= 7;
        if (!gxsnmp_asn1_octet_encode(asn1, ch))
            return FALSE;
    }
    return TRUE;
}

/**
 * gxsnmp_asn1_tag_decode:
 * @asn1: self
 * @ch: Pointer to decoded tag.
 *
 * Decodes a tag.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_tag_decode(GxsnmpAsn1 *asn1, GxsnmpAsn1Tag *tag)
{
    guchar ch;

    *tag = 0;
    do
    {
        if (!gxsnmp_asn1_octet_decode(asn1, &ch))
            return FALSE;
        *tag <<= 7;
        *tag |= ch & 0x7F;
    }
    while ((ch & 0x80) == 0x80);
    return TRUE;
}

/**
 * gxsnmp_asn1_id_encode:
 * @asn1: self
 * @cls: ENUM: universal, application, context, private.
 * @con: ENUM: constructed, primitive.
 * @tag: ID tag to be encoded.
 *
 * Encodes an identifier.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_id_encode(GxsnmpAsn1 *asn1, GxsnmpAsn1Class cls, 
		      GxsnmpAsn1Constructed con, GxsnmpAsn1Tag tag)
{
    guint ch;

    if (tag >= 0x1F)
    {
        if (!gxsnmp_asn1_tag_encode (asn1, tag))
            return FALSE;
        tag = 0x1F;
    }
    ch = (guchar) ((cls << 6) | (con << 5) | (tag));
    if (!gxsnmp_asn1_octet_encode (asn1, ch))
        return FALSE;
    return TRUE;
}

/**
 * gxsnmp_asn1_id_decode:
 * @asn1: self
 * @cls: Pointer to ENUM: universal, application, context, private
 * @con: Pointer to ENUM: constructed, primitive
 * @tag: Pointer to decoded ID tag
 *
 * Decodes an identifier.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_id_decode(GxsnmpAsn1 *asn1, GxsnmpAsn1Class *cls, 
		      GxsnmpAsn1Constructed *con, GxsnmpAsn1Tag *tag)
{
    guchar ch;

    if (!gxsnmp_asn1_octet_decode (asn1, &ch))
        return FALSE;
    *cls = (ch & 0xC0) >> 6;
    *con = (ch & 0x20) >> 5;
    *tag = (ch & 0x1F);
    if (*tag == 0x1F)
    {
        if (!gxsnmp_asn1_tag_decode (asn1, tag))
            return FALSE;
    }
    return TRUE;
}

/**
 * gxsnmp_asn1_length_encode:
 * @asn1: self
 * @def: ENUM: definite, indefinite
 * @len: Length to be encoded.
 *
 * Encodes a definite or indefinite length.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_length_encode(GxsnmpAsn1 *asn1, guint def, guint len)
{
    guchar ch, cnt;

    if (!def)
        ch = 0x80;
    else
    {
        if (len < 0x80)
            ch = (guchar) len;
        else
        {
            cnt = 0;
            while (len > 0)
            {
                ch = (guchar) len;
                len >>= 8;
                if (!gxsnmp_asn1_octet_encode (asn1, ch))
                    return FALSE;
                cnt++;
            }
            ch = (guchar) (cnt | 0x80);
        }
    }
    if (!gxsnmp_asn1_octet_encode (asn1, ch))
        return FALSE;
    return TRUE;
}

/**
 * gxsnmp_asn1_length_decode:
 * @asn1: self
 * @def: Pointer to ENUM: definite, indefinite
 * @len: Pointer to decoded length.
 *
 * Decodes a definite or indefinite length.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_length_decode(GxsnmpAsn1 *asn1, guint *def, guint *len)
{
    guchar ch, cnt;

    if (!gxsnmp_asn1_octet_decode (asn1, &ch))
        return FALSE;
    if (ch == 0x80)
        *def = 0;
    else
    {
        *def = 1;
        if (ch < 0x80)
            *len = ch;
        else
        {
            cnt = (guchar) (ch & 0x7F);
            *len = 0;
            while (cnt > 0)
            {
                if (!gxsnmp_asn1_octet_decode (asn1, &ch))
                    return FALSE;
                *len <<= 8;
                *len |= ch;
                cnt--;
            }
        }
    }
    return TRUE;
}

/**
 * gxsnmp_asn1_header_encode:
 * @asn1: self
 * @eoc: pointer to end of encoding or NULL if indefinite.
 * @cls: ENUM: universal, application, context, private.
 * @con: ENUM: constructed, primitive.
 * @tag: ID tag to be encoded.
 *
 * Encodes an ASN1 header.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_header_encode(GxsnmpAsn1 *asn1, guchar *eoc, GxsnmpAsn1Class cls,
		          GxsnmpAsn1Constructed con, GxsnmpAsn1Tag tag)
{
    guint def, len;

    if (eoc == 0)
    {
        def = 0;
        len = 0;
    }
    else
    {
        def = 1;
        len = eoc - asn1->pointer;
    }
    if (!gxsnmp_asn1_length_encode (asn1, def, len))
        return FALSE;
    if (!gxsnmp_asn1_id_encode (asn1, cls, con, tag))
        return FALSE;
    return TRUE;
}

/**
 * gxsnmp_asn1_header_decode:
 * @asn1: self
 * @eoc: Pointer to pointer to end of decoding or NULL if indefinite.
 * @cls: Pointer to ENUM: universal, application, context, private.
 * @con: Pointer to ENUM: constructed, primitive.
 * @tag: Pointer to ID tag to be encoded.
 *
 * Decodes an ASN1 header.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_header_decode(GxsnmpAsn1 *asn1, guchar **eoc, GxsnmpAsn1Class *cls,
		          GxsnmpAsn1Constructed *con, GxsnmpAsn1Tag *tag)
{
    guint def, len;

    if (!gxsnmp_asn1_id_decode (asn1, cls, con, tag))
        return FALSE;
    if (!gxsnmp_asn1_length_decode (asn1, &def, &len))
        return FALSE;
    if (def)
        *eoc = asn1->pointer + len;
    else
        *eoc = 0;
    return TRUE;
}

/**
 * gxsnmp_asn1_eoc:
 * @asn1: self
 * @eoc: Pointer to end of encoding or NULL if indefinite.
 *
 * Checks if decoding is at End Of Contents.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_eoc ( GxsnmpAsn1 *asn1, guchar *eoc)
{
  if (eoc == NULL)
    return (asn1->pointer [0] == 0x00 && asn1->pointer [1] == 0x00);
  else
    return (asn1->pointer >= eoc);
}

/**
 * gxsnmp_asn1_eoc_encode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 *
 * Encodes End Of Contents.
 * If eoc is NULL it encodes an ASN1 End Of Contents (0x00 0x00), so 
 * it produces an indefinite length encoding. If eoc points to a
 * character pointer, eoc is filled with the pointer to the last
 * encoded octet. This pointer can be used in the next
 * gxsnmp_asn1_header_encode to determine the length of the encoding.
 * This produces a definite length encoding.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_eoc_encode ( GxsnmpAsn1 *asn1, guchar **eoc )        
{
  if (eoc == 0)
    {
      if (!gxsnmp_asn1_octet_encode (asn1, 0x00))
	return FALSE;
      if (!gxsnmp_asn1_octet_encode (asn1, 0x00))
	return FALSE;
      return TRUE;
    }
  else
    {
      *eoc = asn1->pointer;
      return TRUE;
    }
}

/**
 * gxsnmp_asn1_eoc_decode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 *
 * Decodes End Of Contents.
 * If eoc is 0 it decodes an ASN1 End Of Contents (0x00 0x00), so it
 * has to be an indefinite length encoding. If eoc is a character
 * pointer, it probably was filled by gxsnmp_asn1_header_decode, and
 * should point to the octet after the last of the encoding. It is
 * checked if this pointer points to the octet to be decoded. This
 * only takes place in decoding a definite length encoding.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_eoc_decode (GxsnmpAsn1 *asn1, guchar *eoc)
{
  guchar ch;
    
  if (eoc == 0)
    {
      if (!gxsnmp_asn1_octet_decode (asn1, &ch))
	return FALSE;
      if (ch != 0x00)
	{
	  asn1->error = GXSNMP_ASN1_ERR_DEC_EOC_MISMATCH;
	  return FALSE;
	}
      if (!gxsnmp_asn1_octet_decode (asn1, &ch))
	return FALSE;
      if (ch != 0x00)
	{
	  asn1->error = GXSNMP_ASN1_ERR_DEC_EOC_MISMATCH;
	  return FALSE;
	}
      return TRUE;
    }
  else
    {
      if (asn1->pointer != eoc)
	{
	  asn1->error = GXSNMP_ASN1_ERR_DEC_LENGTH_MISMATCH;
	  return FALSE;
	}
      return TRUE;
    }
}

/**
 * gxsnmp_asn1_null_encode:
 * @asn1: self
 * @eoc: Pointer to end of encoding.
 *
 * Encodes Null.
 * Encodes nothing but can be used to fill eoc.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_null_encode ( GxsnmpAsn1 *asn1, guchar **eoc )
{
  *eoc = asn1->pointer;
  return TRUE;
}

/**
 * gxsnmp_asn1_null_decode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 *
 * Decodes Null.
 * Decodes anything up to eoc.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_null_decode ( GxsnmpAsn1 *asn1, guchar *eoc)
{
  asn1->pointer = eoc;
  return TRUE;
}

/**
 * gxsnmp_asn1_bool_encode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @bool: Boolean to be encoded.
 *
 * Encodes Boolean.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_bool_encode ( GxsnmpAsn1 *asn1, guchar **eoc, gboolean bool)
{
    guchar ch;

    *eoc = asn1->pointer;
    ch = (guchar) (bool ? 0xFF : 0x00);
    if (!gxsnmp_asn1_octet_encode (asn1, ch))
        return FALSE;
    return TRUE;
}

/**
 * gxsnmp_asn1_bool_decode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @bool: Pointer to decoded boolean.
 *
 * Decodes Boolean.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_bool_decode ( GxsnmpAsn1 *asn1, guchar *eoc, gboolean *bool)
{
    guchar ch;

    if (!gxsnmp_asn1_octet_decode (asn1, &ch))
        return FALSE;
    *bool = ch ? 1 : 0;
    if (asn1->pointer != eoc)
	{
		asn1->error = GXSNMP_ASN1_ERR_DEC_LENGTH_MISMATCH;
        return FALSE;
	}
    return TRUE;
}

/**
 * gxsnmp_asn1_int_encode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @integer: Integer to be encoded.
 *
 * Encodes Integer.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_int_encode ( GxsnmpAsn1 *asn1, guchar **eoc, int integer)
{
    guchar ch,sign;
    int    lim;

    *eoc = asn1->pointer;
    if (integer < 0)
    {
        lim  = -1;
        sign = 0x80;
    }
    else
    {
        lim  = 0;
        sign = 0x00;
    }
    do
    {
        ch = (guchar) integer;
        integer >>= 8;
        if (!gxsnmp_asn1_octet_encode (asn1, ch))
            return FALSE;
    }
    while ((integer != lim) || (guchar) (ch & 0x80) != sign);
    return TRUE;
}

/**
 * gxsnmp_asn1_int_decode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @integer: Pointer to decoded Integer.
 *
 * Decodes Integer.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_int_decode ( GxsnmpAsn1 *asn1, guchar *eoc, gint *integer)
{
    guchar ch;
    guint  len;

    if (!gxsnmp_asn1_octet_decode (asn1, &ch))
        return FALSE;
    *integer = (gint) ch;
    len = 1;
    while (asn1->pointer < eoc)
    {
        if (++len > sizeof (gint))
		{
			asn1->error = GXSNMP_ASN1_ERR_DEC_BADVALUE;
            return FALSE;
		}
        if (!gxsnmp_asn1_octet_decode (asn1, &ch))
            return FALSE;
        *integer <<= 8;
        *integer |= ch;
    }
    return TRUE;
}

/**
 * gxsnmp_asn1_long_encode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @integer: Long integer to be encoded.
 *
 * Encodes Long Integer.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_long_encode ( GxsnmpAsn1 *asn1, guchar **eoc, glong integer)
{
    guchar ch, sign;
    glong  lim;

    *eoc = asn1->pointer;
    if (integer < 0)
    {
        lim  = -1;
        sign = 0x80;
    }
    else
    {
        lim  = 0;
        sign = 0x00;
    }
    do
    {
        ch = (guchar) integer;
        integer >>= 8;
        if (!gxsnmp_asn1_octet_encode (asn1, ch))
            return FALSE;
    }
    while ((integer != lim) || (guchar) (ch & 0x80) != sign);
    return TRUE;
}

/**
 * gxsnmp_asn1_long_decode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @integer: Pointer to decoded Long integer.
 *
 * Decodes Long Integer.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_long_decode ( GxsnmpAsn1 *asn1, guchar *eoc, glong *integer)
{
    guchar ch;
    guint  len;

    if (!gxsnmp_asn1_octet_decode (asn1, &ch))
        return FALSE;
    *integer = (signed char) ch;
    len = 1;
    while (asn1->pointer < eoc)
    {
      if (++len > sizeof (glong))
	{
	  asn1->error = GXSNMP_ASN1_ERR_DEC_BADVALUE;
	  return FALSE;
	}
      if (!gxsnmp_asn1_octet_decode (asn1, &ch))
	return FALSE;
      *integer <<= 8;
      *integer |= ch;
    }
    return TRUE;
}

/**
 * gxsnmp_asn1_uint_encode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @integer: Unsigned integer to be encoded.
 *
 * Encodes Unsigned Integer.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_uint_encode ( GxsnmpAsn1 *asn1, guchar **eoc, guint integer)
{
  guchar ch;
  
  *eoc = asn1->pointer;
  do
    {
      ch = (guchar) integer;
      integer >>= 8;
      if (!gxsnmp_asn1_octet_encode (asn1, ch))
	return FALSE;
    }
  while ((integer != 0) || (ch & 0x80) != 0x00);
  return TRUE;
}

/**
 * gxsnmp_asn1_uint_decode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @integer: Pointer to decoded Unsigned integer.
 *
 * Decodes Unsigned Integer.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_uint_decode ( GxsnmpAsn1 *asn1, guchar *eoc, guint *integer)
{
    guchar ch;
    guint  len;

    if (!gxsnmp_asn1_octet_decode (asn1, &ch))
        return FALSE;
    *integer = ch;
    if (ch == 0) len = 0;
    else len = 1;
    while (asn1->pointer < eoc)
    {
        if (++len > sizeof (guint))
		{
			asn1->error = GXSNMP_ASN1_ERR_DEC_BADVALUE;
            return FALSE;
		}
        if (!gxsnmp_asn1_octet_decode (asn1, &ch))
            return FALSE;
        *integer <<= 8;
        *integer |= ch;
    }
    return TRUE;
}

/**
 * gxsnmp_asn1_ulong_encode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @integer: Unsigned long integer to be encoded.
 *
 * Encodes Unsigned Long Integer.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_ulong_encode ( GxsnmpAsn1 *asn1, guchar **eoc, gulong integer)
{
    guchar ch;

    *eoc = asn1->pointer;
    do
    {
        ch = (guchar) integer;
        integer >>= 8;
        if (!gxsnmp_asn1_octet_encode (asn1, ch))
            return FALSE;
    }
    while ((integer != 0) || (ch & 0x80) != 0x00);
    return TRUE;
}

/**
 * gxsnmp_asn1_ulong_decode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @integer: Pointer to decoded Unsigned long integer.
 *
 * Decodes Unsigned Long Integer.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_ulong_decode ( GxsnmpAsn1 *asn1, guchar *eoc, gulong *integer)
{
    guchar ch;
    guint  len;

    if (!gxsnmp_asn1_octet_decode (asn1, &ch))
        return FALSE;
    *integer = ch;
    if (ch == 0)
        len = 0;
    else
        len = 1;
    while (asn1->pointer < eoc)
    {
        if (++len > sizeof (gulong))
		{
			asn1->error = GXSNMP_ASN1_ERR_DEC_BADVALUE;
            return FALSE;
		}
        if (!gxsnmp_asn1_octet_decode (asn1, &ch))
            return FALSE;
        *integer <<= 8;
        *integer |= ch;
    }
    return TRUE;
}

/**
 * gxsnmp_asn1_bits_encode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @bits: Pointer to begin of Bit String.
 * @len: Length of Bit String in characters.
 * @unused: Number of unused bits in last character.
 *
 * Encodes Bit String.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_bits_encode ( GxsnmpAsn1 *asn1, guchar **eoc, guchar *bits,
		          guint len, guchar unused)
{
    *eoc = asn1->pointer;
    bits += len;
    while (len-- > 0)
    {
        if (!gxsnmp_asn1_octet_encode (asn1, *--bits))
            return FALSE;
    }
    if (!gxsnmp_asn1_octet_encode (asn1, unused))
        return FALSE;
    return TRUE;
}

/**
 * gxsnmp_asn1_bits_decode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @bits: Pointer to begin of Bit String.
 * @size: Size of Bit String in characters.
 * @len: Length of Bit String in characters.
 * @unused: Number of unused bits in last character.
 *
 * Decodes Bit String.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_bits_decode ( GxsnmpAsn1 *asn1, guchar *eoc, guchar **bits,
		          guint *len, guchar *unused)
{
    *bits = NULL;
    if (!gxsnmp_asn1_octet_decode (asn1, unused))
        return FALSE;
    *len = 0;
    *bits = g_malloc(eoc - asn1->pointer);
    while (asn1->pointer < eoc)
    {
        if (!gxsnmp_asn1_octet_decode (asn1, (guchar *)bits++))
          {
            g_free(*bits);
            *bits = NULL;
	    return FALSE;
          }
    }
    return TRUE;
}

/**
 * gxsnmp_asn1_octets_encode:
 * @asn1: self
 * @octets: Pointer to begin of Octet String.
 * @len: Length of Octet String in characters.
 *
 * Encodes Octet String.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_octets_encode ( GxsnmpAsn1 *asn1, guchar **eoc, guchar *octets,
			    guint len)
{
  guchar *ptr;

  *eoc = asn1->pointer;
  ptr = octets + len;

  while (len-- > 0)
    {
      if (!gxsnmp_asn1_octet_encode (asn1, *--ptr))
        return FALSE;
    }
  return TRUE;
}

/**
 * gxsnmp_asn1_octets_decode:
 * @asn1: self
 * @octets: Pointer to begin of Octet String.
 * @len: Length of Octet String in characters.
 *
 * Decodes Octet String.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_octets_decode ( GxsnmpAsn1 *asn1, guchar *eoc, guchar **octets,
			    guint *len)
{
  guchar *ptr;

  *octets = NULL;
  *len = 0;
  *octets = g_malloc (eoc - asn1->pointer);
  ptr = *octets;
  while (asn1->pointer < eoc)
    {
      if (!gxsnmp_asn1_octet_decode (asn1, (guchar *)ptr++))
        {
          g_free(*octets);
          *octets = NULL;
	  return FALSE;
        }
      (*len)++;
    }
  return TRUE;
}

/**
 * gxsnmp_asn1_subid_encode:
 * @asn1: self
 * @subid: Sub Identifier to be encoded.
 *
 * Encodes Sub Identifier.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_subid_encode ( GxsnmpAsn1 *asn1, gulong subid)
{
  guchar ch;
  
  ch = (guchar) (subid & 0x7F);
  subid >>= 7;
  if (!gxsnmp_asn1_octet_encode (asn1, ch))
    return FALSE;
  while (subid > 0)
    {
      ch = (guchar) (subid | 0x80);
      subid >>= 7;
      if (!gxsnmp_asn1_octet_encode (asn1, ch))
	return FALSE;
    }
  return TRUE;
}

/**
 * gxsnmp_asn1_subid_decode:
 * @asn1: self
 * @ch: Pointer to decoded Sub Identifier.
 *
 * Decodes Sub Identifier.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_subid_decode ( GxsnmpAsn1 *asn1, gulong *subid)
{
    guchar ch;

    *subid = 0;
    do
    {
        if (!gxsnmp_asn1_octet_decode(asn1, &ch))
            return FALSE;
        *subid <<= 7;
        *subid |= ch & 0x7F;
    }
    while ((ch & 0x80) == 0x80);
    return TRUE;
}

/**
 * gxsnmp_asn1_oid_encode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @oid: Pointer to begin of Object Identifier.
 * @len: Length of Object Identifier in gulongs.
 *
 * Encodes Object Identifier.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_oid_encode ( GxsnmpAsn1 *asn1, guchar **eoc, gulong *oid,
		         guint len)
{
  gulong subid;
  
  *eoc = asn1->pointer;
  if (len < 2)
    {
      asn1->error = GXSNMP_ASN1_ERR_ENC_BADVALUE;
      return FALSE;
    }
  subid = oid [1] + oid [0] * 40;
  oid += len;
  while (len-- > 2)
    {
      if (!gxsnmp_asn1_subid_encode (asn1, *--oid))
	return FALSE;
    }
  if (!gxsnmp_asn1_subid_encode (asn1, subid))
    return FALSE;
  return TRUE;
}

/**
 * gxsnmp_asn1_oid_decode:
 * @asn1: self
 * @eoc: Pointer to end of encoding or 0 if indefinite.
 * @oid: Pointer to begin of Object Identifier.
 * @len: Length of Object Identifier in gulongs.
 *
 * Decodes Object Identifier.
 *
 * Return value: success.
 *
 **/
gboolean 
gxsnmp_asn1_oid_decode ( GxsnmpAsn1 *asn1, guchar *eoc, gulong **oid, 
		         guint *len)
{
  gulong subid;
  guint  size;
  gulong *optr;

  size = eoc - asn1->pointer + 1;
  *oid = g_malloc(size * sizeof(gulong));
  optr = *oid;
  if (!gxsnmp_asn1_subid_decode (asn1, &subid))
    {
      g_free(*oid);
      *oid = NULL;
      return FALSE;
    }
  if (subid < 40)
    {
      optr [0] = 0;
      optr [1] = subid;
    }
  else if (subid < 80)
    {
      optr [0] = 1;
      optr [1] = subid - 40;
    }
  else
    {
      optr [0] = 2;
      optr [1] = subid - 80;
    }
  *len = 2;
  optr += 2;
  while (asn1->pointer < eoc)
    {
      if (++(*len) > size)
	{
	  asn1->error = GXSNMP_ASN1_ERR_DEC_BADVALUE;
          g_free(*oid);
          *oid = NULL;
	  return FALSE;
	}
      if (!gxsnmp_asn1_subid_decode (asn1, optr++))
        {
          g_free(*oid);
          *oid = NULL;
	  return FALSE;
        }
    }
  return TRUE;
}

/* EOF */
