/*---------------------------------------------------------------------------*/
/* asn1_ber.cpp                                                              */
/* copyright (c) innovaphone 1997, 1998                                      */
/*                                                                           */
/* ASN1 packed encoding rules implementation                                 */
/*---------------------------------------------------------------------------*/

#include <platform.h>
#include "../ilib/asn1.h"
#include "../ilib/asn1_ber.h"

USING_NAMESPACE;

#define ASN1_TAB                4

static word read_tag(class asn1_in & in);
static int read_length(class asn1_in & in);
static void write_tag(class asn1_out & out, word tag);
static void write_length(class asn1_out & out, int length);

static void read_dummy(class asn1_in & in, word tag, int length);
static const char * space = "                                                                                                     ";

void asn1_context_ber::read(class asn1 & asn1, class asn1_in & in)
{
    word tag = read_tag(in);
    int length = read_length(in);

    read(asn1,in,tag,length);
}

void asn1_context_ber::read(class asn1 & asn1, class asn1_in & in, word tag, int length)
{
    if( in.left() < 0 ) {
        return;
    }
    
    switch(asn1.type) {
    case ASN1_TYPE_NULL:
        read_null((class asn1_null &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_BOOLEAN:
        read_boolean((class asn1_boolean &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_INT:
        read_int((class asn1_int &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_INT8:
        read_int8((class asn1_int8 &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_INT16:
        read_int16((class asn1_int16 &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_OCTET_ARRAY:
        read_octet_array((class asn1_octet_array &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_IA5_STRING:
        read_ia5_string((class asn1_ia5_string &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_BITSTRING:
		read_bitstring((class asn1_bitstring &)asn1, in, tag, length);
		break;
	case ASN1_TYPE_OCTET_STRING:
	case ASN1_TYPE_UTF8STRING:
	case ASN1_TYPE_PRINTABLESTRING:
	case ASN1_TYPE_TELETEXSTRING:
	case ASN1_TYPE_VIDEOTEXSTRING:
	case ASN1_TYPE_GRAPHICSTRING:
	case ASN1_TYPE_VISIBLESTRING:
	case ASN1_TYPE_GENERALSTRING:
	case ASN1_TYPE_UNIVERSALSTRING:
	case ASN1_TYPE_BMPSTRING:
	case ASN1_TYPE_UTCTIME:
	case ASN1_TYPE_GENERALIZEDTIME:
        read_octet_string((class asn1_octet_string &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_WORD_STRING:
        read_word_string((class asn1_word_string &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_OBJECT_IDENTIFIER:
        read_object_object_identifier((class asn1_object_identifier &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_ENUMERATED:
        read_enumerated((class asn1_enumerated &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_SEQUENCE:
        read_sequence((class asn1_sequence &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_CHOICE:
        read_choice((class asn1_choice &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_SET_OF:
		read_set_of((class asn1_set_of &)asn1, in, tag, length);
        break;
	case ASN1_TYPE_SEQUENCE_OF:
        read_sequence_of((class asn1_sequence_of &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_NUMERIC_STRING:
        read_numeric_string((class asn1_numeric_string &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_ANY:
        read_any((class asn1_any &)asn1, in, tag, length);
        break;
    case ASN1_TYPE_INNO_DUMMY:
        read_inno_dummy((class asn1_octet_string &)asn1, in, tag, length);
        break;
    }
}

void asn1_context_ber::write(class asn1 & asn1, class asn1_out & out)
{
    if( tag_index > (int)(tag_buffer_size/sizeof(asn1_tag) )
        || content_index > (int)content_buffer_size  )
            return;

    write(asn1,out,0);
}

void asn1_context_ber::write(class asn1 & asn1, class asn1_out & out, word tag)
{
    switch(asn1.type) {
    case ASN1_TYPE_NULL:
        write_null((class asn1_null &)asn1, out, tag);
        break;
    case ASN1_TYPE_BOOLEAN:
        write_boolean((class asn1_boolean &)asn1, out, tag);
        break;
    case ASN1_TYPE_INT:
        write_int((class asn1_int &)asn1, out, tag);
        break;
    case ASN1_TYPE_INT8:
        write_int8((class asn1_int8 &)asn1, out, tag);
        break;
    case ASN1_TYPE_INT16:
        write_int16((class asn1_int16 &)asn1, out, tag);
        break;
    case ASN1_TYPE_OCTET_ARRAY:
        write_octet_array((class asn1_octet_array &)asn1, out, tag);
        break;
    case ASN1_TYPE_IA5_STRING:
        write_ia5_string((class asn1_ia5_string &)asn1, out, tag);
        break;
    case ASN1_TYPE_OCTET_STRING:
	case ASN1_TYPE_UTF8STRING:
	case ASN1_TYPE_PRINTABLESTRING:
	case ASN1_TYPE_TELETEXSTRING:
	case ASN1_TYPE_VIDEOTEXSTRING:
	case ASN1_TYPE_GRAPHICSTRING:
	case ASN1_TYPE_VISIBLESTRING:
	case ASN1_TYPE_GENERALSTRING:
	case ASN1_TYPE_UNIVERSALSTRING:
	case ASN1_TYPE_BMPSTRING:
	case ASN1_TYPE_UTCTIME:
	case ASN1_TYPE_GENERALIZEDTIME:
        write_octet_string((class asn1_octet_string &)asn1, out, tag);
        break;
	case ASN1_TYPE_BITSTRING:
		write_bitstring((class asn1_bitstring &)asn1, out, tag);
		break;
    case ASN1_TYPE_WORD_STRING:
        write_word_string((class asn1_word_string &)asn1, out, tag);
        break;
    case ASN1_TYPE_OBJECT_IDENTIFIER:
        write_object_object_identifier((class asn1_object_identifier &)asn1, out, tag);
        break;
    case ASN1_TYPE_ENUMERATED:
        write_enumerated((class asn1_enumerated &)asn1, out, tag);
        break;
	case ASN1_TYPE_SEQUENCE:
        write_sequence((class asn1_sequence &)asn1, out, tag);
        break;
    case ASN1_TYPE_CHOICE:
        write_choice((class asn1_choice &)asn1, out, tag);
        break;
	case ASN1_TYPE_SET_OF:
		write_set_of((class asn1_set_of &)asn1, out, tag);
        break;
	case ASN1_TYPE_SEQUENCE_OF:
        write_sequence_of((class asn1_sequence_of &)asn1, out, tag);
        break;
    case ASN1_TYPE_NUMERIC_STRING:
        write_numeric_string((class asn1_numeric_string &)asn1, out, tag);
        break;
    case ASN1_TYPE_ANY:
        write_any((class asn1_any &)asn1, out, tag);
        break;
	case ASN1_TYPE_INNO_DUMMY:
        write_inno_dummy((class asn1_octet_string &)asn1, out, tag);
        break;

    }
}

/*---------------------------------------------------------------------------*/
/* NULL                                                                      */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_null(class asn1_null & asn1, class asn1_in & in, word tag, int length)
{
    if(trace_on()) debug->iprintf("%.*snull: %s",level,space,asn1.name);
    read_dummy(in,tag,length);
}

void asn1_context_ber::write_null(class asn1_null & asn1, class asn1_out & out, word tag)
{
    if(!tag) tag = ASN1_TAG_UNIVERSAL_NULL;
    write_tag(out,tag);
    write_length(out,0);

    if(trace_on()) debug->iprintf("%.*snull: %s",level,space,asn1.name);
}

/*---------------------------------------------------------------------------*/
/* BOOLEAN                                                                   */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_boolean(class asn1_boolean & asn1, class asn1_in & in, word tag, int length)
{
    struct asn1_tag * b;
    byte c;

    c = in.octet();
    b = new_tag(asn1.tag);
    if(!b) {
        in.abort();
        return;
    }

	bool * t = (bool *)&b->content;
    *t = c?(bool)0xff:(bool)0x00;
    if(trace_on()) debug->iprintf("%.*sboolean: %s = %s",level,space,asn1.name,c?"true":"false");
}

void asn1_context_ber::write_boolean(class asn1_boolean & asn1, class asn1_out & out, word tag)
{
    struct asn1_tag * b;

    b = find_tag(asn1.tag);
    if(!b) return;

    if(!tag) tag = ASN1_TAG_UNIVERSAL_BOOLEAN;
    write_tag(out,tag);
    write_length(out,1);
    out.octet((byte)(b->content?0xff:0x00));
    if(trace_on()) debug->iprintf("%.*sbool: %s = %s",level,space,asn1.name,b->content?"true":"false");
}

/*---------------------------------------------------------------------------*/
/* INT                                                                       */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_int(class asn1_int & asn1, class asn1_in & in, word tag, int length)
{
    int i=0;
    struct asn1_tag * b;
    byte c;

    if(length==-1) {
        in.abort();
        return;
    }

    c = in.octet();
    length--;

    if(c&0x80) i=-1;
    else i=0;
    i = (i<<8)|c;

    while(length-- > 0) {
        c = in.octet();
        i = (i<<8)|c;
    }

    b = new_tag(asn1.tag);
    if(!b) {
        in.abort();
        return;
    }
    void ** p = &b->content;
    *(int *)p = i;

    if(trace_on()) debug->iprintf("%.*sint: %s = %i",level,space,asn1.name,i);
}

void asn1_context_ber::write_int(class asn1_int & asn1, class asn1_out & out, word tag)
{
    struct asn1_tag * b;
    int c, sav;
    byte o[4];

    b = find_tag(asn1.tag);
    if(!b) return;

    if(!tag) tag = ASN1_TAG_UNIVERSAL_INTEGER;

    sav = c = (int)(uintp)b->content;

    o[0] = (byte)(c>>24);
    o[1] = (byte)(c>>16);
    o[2] = (byte)(c>>8);
    o[3] = (byte)(c);

    if(c<0) {
        for (c = 0; c<3 && o[c] == 0xff && o[c + 1] & 0x80; c++);
    }
    else {
        for (c = 0; c<3 && !o[c] && !(o[c + 1] & 0x80); c++);
    }
    write_tag(out,tag);
    write_length(out,4-c);

    out.octet_field(&o[c],4-c);

    if(trace_on()) debug->iprintf("%.*sint: %s = %i",level,space,asn1.name,sav);
}

/*---------------------------------------------------------------------------*/
/* INT8                                                                      */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_int8(class asn1_int8 & asn1, class asn1_in & in, word tag, int length)
{
}

void asn1_context_ber::write_int8(class asn1_int8 & asn1, class asn1_out & out, word tag)
{
}

/*---------------------------------------------------------------------------*/
/* INT16                                                                     */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_int16(class asn1_int16 & asn1, class asn1_in & in, word tag, int length)
{
    int i=0;
    struct asn1_tag * b;
    byte c;

    if(length==-1) {
        in.abort();
        return;
    }

    c = in.octet();
    length--;

    if(c&0x80) i=-1;
    else i=0;
    i = (i<<8)|c;

    while(length-- > 0) {
        c = in.octet();
        i = (i<<8)|c;
    }

    b = new_tag(asn1.tag);
    if(!b) {
        in.abort();
        return;
    }
    void ** p = &b->content;
    *(int *)p = i;

    if(trace_on()) debug->iprintf("%.*sint16: %s = %i",level,space,asn1.name,i);
}

void asn1_context_ber::write_int16(class asn1_int16 & asn1, class asn1_out & out, word tag)
{
    struct asn1_tag * b;
    int c, sav;
    byte o[4];

    b = find_tag(asn1.tag);
    if(!b) return;

    if(!tag) tag = ASN1_TAG_UNIVERSAL_INTEGER;

    sav = c = (int)(uintp)b->content;

    o[0] = (byte)(c>>24);
    o[1] = (byte)(c>>16);
    o[2] = (byte)(c>>8);
    o[3] = (byte)(c);

    if(c<0) {
        for (c = 0; c<3 && o[c] == 0xff && o[c + 1] & 0x80; c++);
    }
    else {
        for (c = 0; c<3 && !o[c] && !(o[c + 1] & 0x80); c++);
    }
    write_tag(out,tag);
    write_length(out,4-c);

    out.octet_field(&o[c],4-c);

    if(trace_on()) debug->iprintf("%.*sint16: %s = %i",level,space,asn1.name,sav);
}

/*---------------------------------------------------------------------------*/
/* ENUMERATED                                                               */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_enumerated(class asn1_enumerated & asn1, class asn1_in & in, word tag, int length)
{
    int i=0;
    struct asn1_tag * b;
    byte c;

    if(length==-1) {
        in.abort();
        return;
    }

    c = in.octet();
    length--;

    if(c&0x80) i=-1;
    else i=0;
    i = (i<<8)|c;

    while(length-- > 0) {
        c = in.octet();
        i = (i<<8)|c;
    }

    b = new_tag(asn1.tag);
    if(!b) {
        in.abort();
        return;
    }
    void ** p = &b->content;
    *(int *)p = i;

    if(trace_on()) debug->iprintf("%.*senumerated: %s = %i",level,space,asn1.name,i);
}

void asn1_context_ber::write_enumerated(class asn1_enumerated & asn1, class asn1_out & out, word tag)
{
    struct asn1_tag * b;
    int c, sav;
    byte o[4];

    b = find_tag(asn1.tag);
    if(!b) return;

    if(!tag) tag = ASN1_TAG_UNIVERSAL_ENUMERATED;

    sav = c = (int)(uintp)b->content;

    o[0] = (byte)(c>>24);
    o[1] = (byte)(c>>16);
    o[2] = (byte)(c>>8);
    o[3] = (byte)(c);

    if(c<0) {
        for (c = 0; c<3 && o[c] == 0xff && o[c + 1] & 0x80; c++);
    }
    else {
        for (c = 0; c<3 && !o[c] && !(o[c + 1] & 0x80); c++);
    }
    write_tag(out,tag);
    write_length(out,4-c);

    out.octet_field(&o[c],4-c);

    if(trace_on()) debug->iprintf("%.*senumerated: %s = %i",level,space,asn1.name,sav);
}

/*---------------------------------------------------------------------------*/
/* OCTET_ARRAY                                                               */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_octet_array(class asn1_octet_array & asn1, class asn1_in & in, word tag, int length)
{
}

void asn1_context_ber::write_octet_array(class asn1_octet_array & asn1, class asn1_out & out, word tag)
{
}

/*---------------------------------------------------------------------------*/
/* IA5_STRING                                                                */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_ia5_string(class asn1_ia5_string & asn1, class asn1_in & in, word tag, int length)
{
    struct asn1_tag * b;
    struct octet_string_buffer * c;

    if(tag & ASN1_TAG_CONSTRUCTED && trace_on()) {
        debug->iprintf("%.*swarn: constructed ia5-string",level,space);
    }

    if(length==-1) {
        in.abort();
        return;
    }

    b = new_tag(asn1.tag,sizeof(struct octet_string_buffer)+length,3);
    if(!b) {
        in.abort();
        return;
    }
    c = (struct octet_string_buffer *)b->content;

    c->len = length;
    c->b_ptr = c->b;
    in.octet_field(c->b,length);

    if(trace_on()) debug->iprintf("%.*sia5_string: %s(%i)",level,space,asn1.name,length);
}

void asn1_context_ber::write_ia5_string(class asn1_ia5_string & asn1, class asn1_out & out, word tag)
{
    struct asn1_tag * b;
    struct octet_string_buffer * c;

    b = find_tag(asn1.tag);
    if(!b) return;

    if(!tag) tag = ASN1_TAG_UNIVERSAL_IA5STRING;

    c = (struct octet_string_buffer *)b->content;

    write_tag(out,tag);
    write_length(out,c->len);

    out.octet_field(c->b_ptr,c->len);

    if(trace_on()) debug->iprintf("%.*sia5_string: %s(%i)",level,space,asn1.name,c->len);
}

/*---------------------------------------------------------------------------*/
/* OCTET_STRING                                                              */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_octet_string(class asn1_octet_string & asn1, class asn1_in & in, word tag, int length)
{
    struct asn1_tag * b;
    struct octet_string_buffer * c;

    if(tag & ASN1_TAG_CONSTRUCTED) {
        byte data[10000];
        int data_len = 0;

        if(length==-1) {
            while(in.left()>0) {
                word t = read_tag(in);
                int l = read_length(in);
                if(t==ASN1_TAG_UNIVERSAL_OCTETSTRING && l>=0 && l<(int)sizeof(data)-data_len) {
                    in.octet_field(&data[data_len],l);
                    data_len += l;
                    if(trace_on()) debug->iprintf("%.*sconstructed octet_string fragment: %s(%i)",level,space,asn1.name,l);
                }
                else if(t==0 && l==0) {
                    break;
                }
                else {
                    if(trace_on()) debug->iprintf("%.*sinvalid constructed octet_string fragment: %s(0x%04x,%i)",level,space,asn1.name,t,l);
                    in.abort();
                    return;
                }
            }
        }
        else {
            int start = in.get_count();
            int current = start;
            for(int i=0; current-start<length; i++) {
                word t = read_tag(in);
                int l = read_length(in);
                if(t==ASN1_TAG_UNIVERSAL_OCTETSTRING && l>=0 && l<(int)sizeof(data)-data_len) {
                    in.octet_field(&data[data_len],l);
                    data_len += l;
                    if(trace_on()) debug->iprintf("%.*sconstructed octet_string fragment: %s(%i)",level,space,asn1.name,l);
                }
                else {
                    if(trace_on()) debug->iprintf("%.*sinvalid constructed octet_string fragment: %s(0x%04x,%i)",level,space,asn1.name,t,l);
                    in.abort();
                    return;
                }
                current = in.get_count();
            }
        }

        b = new_tag(asn1.tag,sizeof(struct octet_string_buffer)+data_len,3);
        if(!b) {
            in.abort();
            return;
        }

        c = (struct octet_string_buffer *)b->content;
        c->b_ptr = c->b;
        c->len = data_len;
        memcpy(c->b, data, data_len);

        if(trace_on()) debug->iprintf("%.*sconstructed octet_string: %s(%i)",level,space,asn1.name,data_len);
    }
    else {
        if (length<0) {
            in.abort();
            return;
        }

        b = new_tag(asn1.tag,sizeof(struct octet_string_buffer)+length,3);
        if(!b) {
            in.abort();
            return;
        }

        c = (struct octet_string_buffer *)b->content;
        c->b_ptr = c->b;
        c->len = length;
        in.octet_field(c->b,length);
        
        if(trace_on()) debug->iprintf("%.*soctet_string: %s(%i)",level,space,asn1.name,length);
    }
}

void asn1_context_ber::write_octet_string(class asn1_octet_string & asn1, class asn1_out & out, word tag)
{
    struct asn1_tag * b;
    struct octet_string_buffer * c;

    b = find_tag(asn1.tag);
    if(!b) return;

	if(!tag) tag = asn1.std_tag;

    c = (struct octet_string_buffer *)b->content;

    write_tag(out,tag);
    write_length(out,c->len);

    out.octet_field(c->b_ptr,c->len);

    if(trace_on()) debug->iprintf("%.*soctet_string: %s(%i)",level,space,asn1.name,c->len);
}

/*---------------------------------------------------------------------------*/
/* BITSTRING                                                              */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_bitstring(class asn1_bitstring & asn1, class asn1_in & in, word tag, int length)
{
    struct asn1_tag * b;
    struct octet_string_buffer * c;

    if(tag & ASN1_TAG_CONSTRUCTED && trace_on()) {
        debug->iprintf("%.*swarn: constructed bitstringstring",level,space);
    }

    if(length==-1) {
        in.abort();
        return;
    }

    b = new_tag(asn1.tag,sizeof(struct octet_string_buffer)+length-1,3);
    if(!b) {
        in.abort();
        return;
    }
    c = (struct octet_string_buffer *)b->content;
	c->b_ptr = c->b;
	c->len = 0;

    byte pad = in.octet();
 	if (pad > 7 || length <= 1) {
		in.abort();
		return;
	}

	c->len = (length - 1) * 8 - pad;
    in.octet_field(c->b,length - 1);
    
	if(trace_on()) debug->iprintf("%.*sbitstring: %s(%i)",level,space,asn1.name,c->len);
}

void asn1_context_ber::write_bitstring(class asn1_bitstring & asn1, class asn1_out & out, word tag)
{
	struct asn1_tag * b;
    struct octet_string_buffer * c;

	b = find_tag(asn1.tag);
    if(!b) return;

	if(!tag) tag = ASN1_TAG_UNIVERSAL_BITSTRING;

    c = (struct octet_string_buffer *)b->content;

    write_tag(out,tag);
    write_length(out,1 + c->len/8);

	out.octet(c->len % 8);
    out.octet_field(c->b_ptr, c->len/8);

    if(trace_on()) debug->iprintf("%.*sbitstring: %s(%i)",level,space,asn1.name,c->len);
}

/*---------------------------------------------------------------------------*/
/* NUMERIC_STRING                                                            */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_numeric_string(class asn1_numeric_string & asn1, class asn1_in & in, word tag, int length)
{
    struct asn1_tag * b;
    struct octet_string_buffer * c;

    if(tag & ASN1_TAG_CONSTRUCTED && trace_on()) {
        debug->iprintf("%.*swarn: constructed numeric-string",level,space);
    }

    if(length==-1) {
        in.abort();
        return;
    }

    b = new_tag(asn1.tag,sizeof(struct octet_string_buffer)+length,3);
    if(!b) {
        in.abort();
        return;
    }
    c = (struct octet_string_buffer *)b->content;

    c->len = length;
    c->b_ptr = c->b;
    in.octet_field(c->b,length);

    if(trace_on()) debug->iprintf("%.*snumeric_string: %s(%i)",level,space,asn1.name,length);
}

void asn1_context_ber::write_numeric_string(class asn1_numeric_string & asn1, class asn1_out & out, word tag)
{
    struct asn1_tag * b;
    struct octet_string_buffer * c;

    b = find_tag(asn1.tag);
    if(!b) return;

    if(!tag) tag = ASN1_TAG_UNIVERSAL_NUMERICSTRING;

    c = (struct octet_string_buffer *)b->content;

    write_tag(out,tag);
    write_length(out,c->len);

    out.octet_field(c->b_ptr,c->len);

    if(trace_on()) debug->iprintf("%.*snumeric_string: %s(%i)",level,space,asn1.name,c->len);
}

/*---------------------------------------------------------------------------*/
/* WORD_STRING                                                               */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_word_string(class asn1_word_string & asn1, class asn1_in & in, word tag, int length)
{
}

void asn1_context_ber::write_word_string(class asn1_word_string & asn1, class asn1_out & out, word tag)
{
}

/*---------------------------------------------------------------------------*/
/* OBJECT_IDENTIFIER                                                         */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_object_object_identifier(class asn1_object_identifier & asn1, class asn1_in & in, word tag, int length)
{
    struct asn1_tag * b;
    byte * c;

    if(length==-1) {
        in.abort();
        return;
    }

    b = new_tag(asn1.tag,length+1,0);
    if(!b) {
        in.abort();
        return;
    }
    c = (byte *)b->content;

    c[0] = (byte)length;
    in.octet_field(&c[1],length);

    if(trace_on()) debug->iprintf("%.*sobject_id: %s(%i)",level,space,asn1.name,length);
}

void asn1_context_ber::write_object_object_identifier(class asn1_object_identifier & asn1, class asn1_out & out, word tag)
{
    struct asn1_tag * b;
    byte * c;

    b = find_tag(asn1.tag);
    if(!b) return;

    c = (byte *)b->content;

    if(!tag) tag = ASN1_TAG_UNIVERSAL_OBJECT_IDENTIFIER;

    write_tag(out,tag);
    write_length(out,c[0]);

    out.octet_field(&c[1],c[0]);

    if(trace_on()) debug->iprintf("%.*sobject_id: %s(%i)",level,space,asn1.name,c[0]);
}

/*---------------------------------------------------------------------------*/
/* SEQUENCE                                                                  */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_sequence(class asn1_sequence & asn1, class asn1_in & in, word tag, int length)
{
    struct asn1_tag * b;

    b = new_tag(asn1.tag);
    if(!b) {
        in.abort();
        return;
    }

    if(trace_on()) {
        if(level==0) debug->iprintf("asn1-read");
        debug->iprintf("%.*ssequence: %s",level,space,asn1.name);
        level +=ASN1_TAB;
    }

    if(length==-1) {
        int i;

        for(i=0;;i++) {
            tag = read_tag(in);
            length = read_length(in);

            while (asn1.tag_table 
                && i<asn1.count + asn1.ext_count
                && asn1.tag_table[i]
                && (asn1.tag_table[i] & ~ASN1_TAG_CONSTRUCTED) != (tag & ~ASN1_TAG_CONSTRUCTED)     // tag matching ?
                ) {

                if (asn1.flags && asn1.flags[i] != ASN1_OPTIONAL) { 
                    if (trace_on())
                        debug->iprintf("%.*ssequence(%s): %s not found!", level, space, asn1.name,
                            asn1.table[i]->name ? asn1.table[i]->name : "");

                    in.abort();
                    return;
                }
                i++;
            }
            if (i >= (asn1.count + asn1.ext_count)) break;

            if(tag && length) {
                if(i<asn1.count+asn1.ext_count) {
                    read(*asn1.table[i],in,tag,length);
                }
                else {
                    read_dummy(in,tag,length);
                }
            }
            else {
                break;
            }
        }
    }
    else {
        int i;
        int start;
        int current;

        start = in.get_count();
        current = start;
        for(i=0; current-start<length && i<asn1.count+asn1.ext_count; i++) {

            word seq_tag = read_tag(in);
            int seq_length = read_length(in);

            while(   asn1.tag_table 
                  && i<asn1.count + asn1.ext_count
                  && asn1.tag_table[i]
                  && (asn1.tag_table[i] & ~ASN1_TAG_CONSTRUCTED ) != ( seq_tag & ~ASN1_TAG_CONSTRUCTED)
                   ) {

                    if(asn1.flags && asn1.flags[i] != ASN1_OPTIONAL) {
                        if(trace_on())
                            debug->iprintf("%.*ssequence(%s): %s not found!",level,space,asn1.name,
											asn1.table[i]->name?asn1.table[i]->name:"");

                        in.abort();
                        return;
				    }
                i++;
            }
            if(i>=(asn1.count+asn1.ext_count)) break;

            read(*asn1.table[i],in,seq_tag,seq_length);
            current = in.get_count();
        }

        for( ; i<asn1.count+asn1.ext_count; i++) {
                //if there are remains in asn1.table[] ensure they're
                //all optional. otherwise it's an errror

            if(!asn1.flags || asn1.flags[i] != ASN1_OPTIONAL ) {
                if(trace_on())
                    debug->iprintf("%.*ssequence(%s): %s not found!",level,space,asn1.name,
                                            asn1.table[i]->name?asn1.table[i]->name:"");

                in.abort();
                return;
            }
        }

            // skip additional bytes
        if(current-start<length) in.set_count(start+length);
    }
    if(trace_on()) level-=ASN1_TAB;
}

void asn1_context_ber::write_sequence(class asn1_sequence & asn1, class asn1_out & out, word tag)
{
    struct asn1_tag * b;
    int i;
    void * save_out;
    int length;

    b = find_tag(asn1.tag);
    if(!b) return;

    if(!tag) tag = ASN1_TAG_UNIVERSAL_SEQUENCE;
    tag |= ASN1_TAG_CONSTRUCTED;

    if(trace_on()) {
        if(level==0) debug->iprintf("asn1-write");
        debug->iprintf("%.*ssequence: %s",level,space,asn1.name);
        level +=ASN1_TAB;
    }

    save_out = out.new_stream(0);
    for(i=0;i<asn1.count;i++) {
        if( asn1.tag_table &&  asn1.tag_table[i] ) {
            write(*asn1.table[i],out,asn1.tag_table[i]);
        }
        else
            write(*asn1.table[i],out);
    }
    length = out.length();
    save_out = out.new_stream(save_out);
    write_tag(out,tag);
    write_length(out,length);
    out.append(save_out);

    if(trace_on()) level-=ASN1_TAB;
}

/*---------------------------------------------------------------------------*/
/* CHOICE                                                                    */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_choice(class asn1_choice & asn1, class asn1_in & in, word tag, int length)
{
    struct asn1_tag * b;
    dword choice;

    for(choice=0; choice<asn1.count && (asn1.tag_table[choice]&~ASN1_TAG_CONSTRUCTED)!=(tag &~ASN1_TAG_CONSTRUCTED); choice++);
    if(choice>=asn1.count) {

            //neither alternative present,
            //is entire choice optional ?
        if((byte)ASN1_OPTIONAL & asn1.properties)
            return;

        if(trace_on()) debug->iprintf("choice(0x%x) %s not found",tag&~ASN1_TAG_CONSTRUCTED,asn1.name?asn1.name:"");
        in.abort();
        return;
    }

    b = new_tag(asn1.tag);
    if(!b) {
        in.abort();
        return;
    }
    void ** p = &b->content;
    *(dword *)p = choice;

    if(trace_on()) {
        if(level==0) debug->iprintf("asn1-read");
        debug->iprintf("%.*schoice: %s = %i",level,space,asn1.name,choice);
        level +=ASN1_TAB;
    }

    if(asn1.table) {
        if(asn1.table[choice]) {
            read(*asn1.table[choice],in,tag,length);
        }
    }

    if(trace_on()) {
        level -=ASN1_TAB;
    }
}

void asn1_context_ber::write_choice(class asn1_choice & asn1, class asn1_out & out, word tag)
{
    struct asn1_tag * b;
    dword choice;

    b = find_tag(asn1.tag);
    if(!b) return;

    void ** p = &b->content;
    choice = *(dword *)p;
    choice &=0xffff;

    if(trace_on()) {
        if(level==0) debug->iprintf("asn1-write");
        debug->iprintf("%.*schoice: %s = %i",level,space,asn1.name,choice);
        level +=ASN1_TAB;
    }
    if(asn1.table) {
        if(asn1.table[choice]) {
            write(*asn1.table[choice],out,asn1.tag_table[choice]);
        }
    }
    if(trace_on()) {
        level -=ASN1_TAB;
    }
}

/*---------------------------------------------------------------------------*/
/* SEQUENCE_OF                                                               */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_sequence_of(class asn1_sequence_of & asn1, class asn1_in & in, word tag, int length)
{
    dword count=0;
    struct asn1_tag * b;
    dword save_seq_of;
    dword save_seq_mask;
    dword i;
    dword m;

    if(length==-1) {
        in.save();
        while(in.left()>0) {
            word tag = read_tag(in);
            int length = read_length(in);
            if(tag) {
                count ++;
                read_dummy(in,tag,length);
            }
            else {
                break;
            }
        }
        in.restore();
    }
    else {
        in.save();
        int limit = in.get_count()+length;
        while(in.get_count()<limit) {
            word tag = read_tag(in);
            int length = read_length(in);
            if(tag) {
                count ++;
                read_dummy(in,tag,length);
            }
            else {
                break;
            }
        }
        in.restore();
    }

    if(trace_on()) {
        if(level==0) debug->iprintf("asn1-read");
        debug->iprintf("%.*ssequence_of: %s(%i)",level,space,asn1.name,count);
        level +=ASN1_TAB;
    }
    b = new_tag(asn1.tag);
    if(!b) {
        in.abort();
        return;
    }

    save_seq_of = seq_of;
    save_seq_mask = seq_mask;

    for(i=16,m=0x8000; m && !(count&m); i--,m>>=1);
    seq_mask <<=i;

    void ** p = &b->content;
    *(dword *)p = (seq_mask&0xffff0000) | count;

    for(i=0; i<count; i++) {
        dword m;
        word tag = read_tag(in);
        int length = read_length(in);
        m = (~save_seq_mask) & (save_seq_mask<<1);
        seq_of = save_seq_of + (i*m);
        read(*asn1.seq,in,tag,length);
    }

    seq_of = save_seq_of;
    seq_mask = save_seq_mask;

    if(trace_on()) {
        level -=ASN1_TAB;
    }
}

void asn1_context_ber::write_sequence_of(class asn1_sequence_of & asn1, class asn1_out & out, word tag)
{
    dword count=0;
    struct asn1_tag * b;
    dword save_seq_of;
    dword save_seq_mask;
    dword mask;
    void * save_out;
    int length;

    b = find_tag(asn1.tag);
    if(!b) return;

    if(!tag) tag = ASN1_TAG_UNIVERSAL_SEQUENCE;
    tag |= ASN1_TAG_CONSTRUCTED;

    void ** p = &b->content;
    count = *(dword *)p;
    mask = count &0xffff0000;
    count &=0xffff;

    if(trace_on()) {
        if(level==0) debug->iprintf("asn1-write");
        debug->iprintf("%.*ssequence_of: %s(%i)",level,space,asn1.name,count);
        level +=ASN1_TAB;
    }

    save_out = out.new_stream(0);

    save_seq_of = seq_of;
    save_seq_mask = seq_mask;

    seq_mask = mask;

    for(dword i=0; i<count; i++) {
        dword m;
        m = (~save_seq_mask) & (save_seq_mask<<1);
        seq_of = save_seq_of + i*m;
        write(*asn1.seq,out,0);
    }

    seq_of = save_seq_of;
    seq_mask = save_seq_mask;

    length = out.length();
    save_out = out.new_stream(save_out);
    write_tag(out,tag);
    write_length(out,length);
    out.append(save_out);

    if(trace_on()) {
        level -=ASN1_TAB;
    }
}

/*---------------------------------------------------------------------------*/
/* SET_OF                                                               */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_set_of(class asn1_set_of & asn1, class asn1_in & in, word tag, int length)
{
    dword count=0;
    struct asn1_tag * b;
    dword save_seq_of;
    dword save_seq_mask;
    dword i;
    dword m;

    if(length==-1) {
        in.save();
        while(in.left()>0) {
            word tag = read_tag(in);
            int length = read_length(in);
            if(tag) {
                count ++;
                read_dummy(in,tag,length);
            }
            else {
                break;
            }
        }
        in.restore();
    }
    else {
        in.save();
        int limit = in.get_count()+length;
        while(in.get_count()<limit) {
            word tag = read_tag(in);
            int length = read_length(in);
            if(tag) {
                count ++;
                read_dummy(in,tag,length);
            }
            else {
                break;
            }
        }
        in.restore();
    }

    if(trace_on()) {
        if(level==0) debug->iprintf("asn1-read");
        debug->iprintf("%.*sset_of: %s(%i)",level,space,asn1.name,count);
        level +=ASN1_TAB;
    }
    b = new_tag(asn1.tag);
    if(!b) {
        in.abort();
        return;
    }

    save_seq_of = seq_of;
    save_seq_mask = seq_mask;

    for(i=16,m=0x8000; m && !(count&m); i--,m>>=1);
    seq_mask <<=i;

    void ** p = &b->content;
    *(dword *)p = (seq_mask&0xffff0000) | count;

    for(i=0; i<count; i++) {
        dword m;
        word tag = read_tag(in);
        int length = read_length(in);
        m = (~save_seq_mask) & (save_seq_mask<<1);
        seq_of = save_seq_of + (i*m);
        read(*asn1.seq,in,tag,length);
    }

    seq_of = save_seq_of;
    seq_mask = save_seq_mask;

    if(trace_on()) {
        level -=ASN1_TAB;
    }
}

void asn1_context_ber::write_set_of(class asn1_set_of & asn1, class asn1_out & out, word tag)
{
    dword count=0;
    struct asn1_tag * b;
    dword save_seq_of;
    dword save_seq_mask;
    dword mask;
    void * save_out;
    int length;

    b = find_tag(asn1.tag);
    if(!b) return;

    if(!tag) tag = ASN1_TAG_UNIVERSAL_SET;
    tag |= ASN1_TAG_CONSTRUCTED;

    void ** p = &b->content;
    count = *(dword *)p;
    mask = count &0xffff0000;
    count &=0xffff;

    if(trace_on()) {
        if(level==0) debug->iprintf("asn1-write");
        debug->iprintf("%.*sset_of: %s(%i)",level,space,asn1.name,count);
        level +=ASN1_TAB;
    }

    save_out = out.new_stream(0);

    save_seq_of = seq_of;
    save_seq_mask = seq_mask;

    seq_mask = mask;

    for(dword i=0; i<count; i++) {
        dword m;
        m = (~save_seq_mask) & (save_seq_mask<<1);
        seq_of = save_seq_of + i*m;
        write(*asn1.seq,out,0);
    }

    seq_of = save_seq_of;
    seq_mask = save_seq_mask;

    length = out.length();
    save_out = out.new_stream(save_out);
    write_tag(out,tag);
    write_length(out,length);
    out.append(save_out);

    if(trace_on()) {
        level -=ASN1_TAB;
    }
}

/*---------------------------------------------------------------------------*/
/* ANY                                                                       */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_any(class asn1_any & asn1, class asn1_in & in, word tag, int length)
{
    class asn1 * actual = asn1.get_actual(*this);

    if(actual) {
        read(*actual,in,tag,length);
    }
    else {
        if(trace_on()) debug->iprintf("%.*sany:unknown op, len=%i",level,space,length);
        in.octet_field(0,length);
    }
}

void asn1_context_ber::write_any(class asn1_any & asn1, class asn1_out & out, word tag)
{
    class asn1 * actual = asn1.get_actual(*this);

    if(actual) {
        write(*actual,out,tag);
    }
    else {
        if(trace_on()) debug->iprintf("%.*sany:unknown op",level,space);
    }
}

/*---------------------------------------------------------------------------*/
/* static functions                                                          */
/*---------------------------------------------------------------------------*/

static word read_tag(class asn1_in & in)
{
    word tag=0;
    word c;
    byte b;

    b = in.octet();
    c = (b &0xe0)<<8;
    if((b &0x1f)==0x1f) {
        do {
            b = in.octet();
            tag <<=7;
            tag |=b&0x7f;
        } while(b&0x80);
    }
    else {
        tag = b &0x1f;
    }
    return tag|c;
}

static int read_length(class asn1_in & in)
{
    byte b;
    int length=0;

    b = in.octet();
    if(!(b&0x80)) {
        length = b;
    }
    else {
        if(b==0x80) {
            return -1;
        }
        else {
            for(b &=0x7f; b; b--) {
                length <<=8;
                length |=in.octet();
            }
        }
    }
    if( length > in.left() || length < 0 ) {
        in.abort();
        return(0);
    }
    return length;
}

static void write_tag(class asn1_out & out, word tag)
{
    byte b;
    if(!(tag &0x1fe0)) {
        b = (tag>>8) | tag;
        out.octet(b);
    }
    else {
        b = (tag>>8) | 0x1f;
        out.octet(b);
        tag &=0x1fff;
        b = tag >>7;
        if(b) out.octet(b|0x80);
        b = tag &0x7f;
        out.octet(b);
    }
}

static void write_length(class asn1_out & out, int length)
{
    if(length==-1) {
        out.octet(0x80);
    }
    else if(length<128) {
        out.octet((byte)length);
    }
    else {
        byte b;
        if(length<256) b=1;
        else if(length<256*256) b=2;
        else if(length<256*256*256) b=3;
        else b=4;
        out.octet(b|0x80);
        while(b--) {
            out.octet((length>>(b*8))&0xff);
        }
    }
}

static void read_dummy(class asn1_in & in, word tag, int length)
{
    if(length==-1) {
        for(;;) {
            tag = read_tag(in);
            length = read_length(in);
            if(tag && length) {
                read_dummy(in,tag,length);
            }
            else {
                break;
            }
        }
    }
    else {
        in.skip(length);
    }
}
/*

    08 01 81 4d
        1c 17
            91 - Service discriminator

            a1 - Tag CONTEXT, constructed 1
            14 - length
                02 01 78 - INTEGER
                02 01 24 - INTEGER
                30 0c    - SEQUENCE
                    30 0a   - SEQUENCE
                        a1 05  - CONTEXT, constructed 1
                            30 03  - SEQUENCE
                                02 01 01 - INTEGER
                        82 01 00 - CONTEXT 1
*/

/*---------------------------------------------------------------------------*/
/* INNO_DUMMY                                                              */
/*---------------------------------------------------------------------------*/

void asn1_context_ber::read_inno_dummy(class asn1_octet_string & asn1, class asn1_in & in, word tag, int length)
{
    struct asn1_tag * b;
    struct octet_string_buffer * c;

    b = new_tag(asn1.tag,sizeof(struct octet_string_buffer)+length,3);
    if(!b) {
        in.abort();
        return;
    }
    c = (struct octet_string_buffer *)b->content;

    c->len = length;
    c->b_ptr = c->b;
    in.octet_field(c->b,length);

    if(trace_on()) debug->iprintf("%.*sinno_dummy: %s(%i)",level,space,asn1.name,length);
}

void asn1_context_ber::write_inno_dummy(class asn1_octet_string & asn1, class asn1_out & out, word tag)
{
    struct asn1_tag * b;
    struct octet_string_buffer * c;

    b = find_tag(asn1.tag);
    if(!b) return;

	if(!tag) tag = asn1.std_tag;

    c = (struct octet_string_buffer *)b->content;

    write_tag(out,tag);
    write_length(out,c->len);

    out.octet_field(c->b_ptr,c->len);

    if(trace_on()) debug->iprintf("%.*sinno_dummy: %s(%i)",level,space,asn1.name,c->len);
}
