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

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

USING_NAMESPACE;

#define ASN1_TAB    4

static dword per_read_int(class asn1_in & in, int size);
static void per_write_int(class asn1_out & out, int size, dword c);
static const char * space = "                                                                                                     ";

void asn1_context_per::read(class asn1 & asn1, class asn1_in & in)
{
    asn1.fix(fix_mask);
    switch(asn1.type) {
    case ASN1_TYPE_NULL:
        read_null((class asn1_null &)asn1, in);
        break;
    case ASN1_TYPE_BOOLEAN:
        read_boolean((class asn1_boolean &)asn1, in);
        break;
    case ASN1_TYPE_INT:
        read_int((class asn1_int &)asn1, in);
        break;
    case ASN1_TYPE_INTS:
        read_ints((class asn1_int &)asn1, in);
        break;
    case ASN1_TYPE_INTE:
        read_inte((class asn1_inte &)asn1, in);
        break;
    case ASN1_TYPE_INT8:
        read_int8((class asn1_int8 &)asn1, in);
        break;
    case ASN1_TYPE_INT16:
        read_int16((class asn1_int16 &)asn1, in);
        break;
    case ASN1_TYPE_BITSTRING:
        read_bitstring((class asn1_bitstring &)asn1, in);
        break;
    case ASN1_TYPE_ENUMERATED:
        read_enumerated((class asn1_enumerated &)asn1, in);
        break;
    case ASN1_TYPE_OCTET_ARRAY:
        read_octet_array((class asn1_octet_array &)asn1, in);
        break;
    case ASN1_TYPE_IA5_STRING:
        read_ia5_string((class asn1_ia5_string &)asn1, in);
        break;
    case ASN1_TYPE_OCTET_STRING:
        read_octet_string((class asn1_octet_string &)asn1, in);
        break;
    case ASN1_TYPE_WORD_STRING:
        read_word_string((class asn1_word_string &)asn1, in);
        break;
    case ASN1_TYPE_OBJECT_IDENTIFIER:
        read_object_object_identifier((class asn1_object_identifier &)asn1, in);
        break;
    case ASN1_TYPE_SEQUENCE:
        read_sequence((class asn1_sequence &)asn1, in);
        break;
    case ASN1_TYPE_CHOICE:
        read_choice((class asn1_choice &)asn1, in);
        break;
    case ASN1_TYPE_SEQUENCE_OF:
        read_sequence_of((class asn1_sequence_of &)asn1, in);
        break;
    case ASN1_TYPE_ANY:
        read_any((class asn1_any &)asn1, in);
        break;
    }
}

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

    switch(asn1.type) {
    case ASN1_TYPE_NULL:
        write_null((class asn1_null &)asn1, out);
        break;
    case ASN1_TYPE_BOOLEAN:
        write_boolean((class asn1_boolean &)asn1, out);
        break;
    case ASN1_TYPE_INT:
        write_int((class asn1_int &)asn1, out);
        break;
    case ASN1_TYPE_INTS:
        write_ints((class asn1_int &)asn1, out);
        break;
    case ASN1_TYPE_INTE:
        write_inte((class asn1_inte &)asn1, out);
        break;
    case ASN1_TYPE_INT8:
        write_int8((class asn1_int8 &)asn1, out);
        break;
    case ASN1_TYPE_INT16:
        write_int16((class asn1_int16 &)asn1, out);
        break;
    case ASN1_TYPE_BITSTRING:
        write_bitstring((class asn1_bitstring &)asn1, out);
        break;
    case ASN1_TYPE_ENUMERATED:
        write_enumerated((class asn1_enumerated &)asn1, out);
        break;
    case ASN1_TYPE_OCTET_ARRAY:
        write_octet_array((class asn1_octet_array &)asn1, out);
        break;
    case ASN1_TYPE_IA5_STRING:
        write_ia5_string((class asn1_ia5_string &)asn1, out);
        break;
    case ASN1_TYPE_OCTET_STRING:
        write_octet_string((class asn1_octet_string &)asn1, out);
        break;
    case ASN1_TYPE_WORD_STRING:
        write_word_string((class asn1_word_string &)asn1, out);
        break;
    case ASN1_TYPE_OBJECT_IDENTIFIER:
        write_object_object_identifier((class asn1_object_identifier &)asn1, out);
        break;
    case ASN1_TYPE_SEQUENCE:
        write_sequence((class asn1_sequence &)asn1, out);
        break;
    case ASN1_TYPE_CHOICE:
        write_choice((class asn1_choice &)asn1, out);
        break;
    case ASN1_TYPE_SEQUENCE_OF:
        write_sequence_of((class asn1_sequence_of &)asn1, out);
        break;
    case ASN1_TYPE_ANY:
        write_any((class asn1_any &)asn1, out);
        break;
    }
}

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

void asn1_context_per::read_null(class asn1_null & asn1, class asn1_in & in)
{
    struct asn1_tag * b = new_tag(asn1.tag);
    if(!b) {
        in.abort();
        return;
    }
    if(trace_on()) debug->iprintf("%.*snull: %s",level,space,asn1.name);
    else if(dbg) dbg->iprintf("\n%.*snull: %s",level+6,space,asn1.name);
}

void asn1_context_per::write_null(class asn1_null & asn1, class asn1_out & out)
{
    struct asn1_tag * b;

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

    if(trace_on()) debug->iprintf("%.*snull: %s",level,space,asn1.name);
    else if(dbg) dbg->iprintf("\n%.*snull: %s",level+6,space,asn1.name);
}

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

void asn1_context_per::read_boolean(class asn1_boolean & asn1, class asn1_in & in)
{
    struct asn1_tag * b;

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

    void * p = &b->content;
    *(bool *)p = in.bit();
    if(trace_on()) debug->iprintf("%.*sbool: %s = %i",level,space,asn1.name,*(bool *)p);
    else if(dbg) dbg->iprintf("\n%.*sbool: %s = %i",level+6,space,asn1.name,*(bool *)p);
}

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

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

    void * p = &b->content;
    out.bit(*(bool *)p);
    if(trace_on()) debug->iprintf("%.*sbool: %s = %i",level,space,asn1.name,*(bool *)p);
    else if(dbg) dbg->iprintf("\n%.*sbool: %s = %i",level+6,space,asn1.name,*(bool *)p);
}

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

void asn1_context_per::read_int(class asn1_int & asn1, class asn1_in & in)
{
    dword i=0;
    struct asn1_tag * b;

    if(!asn1.count_bits) {
        int l;
        in.align();
        l = in.octet();

        for(int j=0; j<l; j++) {
            i <<=8;
            i |=in.octet();
        }
    }
    else if(asn1.count_bits<=8) {
        i = in.bit_field(asn1.count_bits);
    }
    else if(asn1.count_bits && asn1.count_bits<=16) {
        in.align();
        i = in.octet();
        i<<=8;
        i |=in.octet();
    }
    else {
        int l;
        l = in.bit_field(2);
        in.align();

        for(int j=0; j<l+1; j++) {
            i <<=8;
            i |=in.octet();
        }
    }

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

    void ** p = &b->content;
    *(dword *)p = i;

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

void asn1_context_per::write_int(class asn1_int & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    dword c;
    dword l;

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

    c = (dword)(uintp)b->content;

    if(!asn1.count_bits) {
        byte o[4];

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

        for(l=0;l<3 && !o[l]; l++);
        out.align();
        out.octet((byte)(4-l));
        for(;l<4;l++) out.octet(o[l]);
    }
    else if(asn1.count_bits<=8) {
        out.bit_field(c,asn1.count_bits);
    }
    else if(asn1.count_bits && asn1.count_bits<=16) {
        out.align();
        out.bit_field(c,16);
    }
    else {
        byte o[4];

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

        for(l=0;!o[l] && l<3; l++);
        out.bit_field(3-l,2);
        out.align();
        for(;l<4;l++) out.octet(o[l]);
    }

    if(trace_on()) debug->iprintf("%.*sint: %s = %i",level,space,asn1.name,c);
    else if(dbg) dbg->iprintf("\n%.*sint: %s = %i",level+6,space,asn1.name,c);
}

/*---------------------------------------------------------------------------*/
/* INTS (signed)                                                              */
/*---------------------------------------------------------------------------*/

void asn1_context_per::read_ints(class asn1_int & asn1, class asn1_in & in)
{
    dword i=0;
    struct asn1_tag * b;

    int l;
    in.align();
    l = in.octet();
    bool negative = false;

    for(int j=0; j<l; j++) {
        i <<=8;
        i |=in.octet();
        if(j==0) {
            if(i&0x80) {
                negative = true;
            }
        }
    }
    if(negative) {
        switch(l) {
        case 1:
            i |=0xffffff00;
            break;
        case 2:
            i |=0xffff0000;
            break;
        case 3:
            i |=0xff000000;
            break;
        default:
            i |=0x80000000;
            break;
        }
    }

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

    void ** p = &b->content;
    *(dword *)p = i;

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

void asn1_context_per::write_ints(class asn1_int & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    dword c;
    dword l;

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

    c = (dword)(uintp)b->content;

    byte o[4];

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

    if((int)c<0) {
        for(l=0;l<3 && o[l]!=0xff; l++);
        if(!(o[l]&0x80)) l--;
    }
    else {
        for(l=0;l<3 && !o[l]; l++);
        if(o[l]&0x80) l--;
    }
    out.align();
    out.octet((byte)(4-l));
    for(;l<4;l++) out.octet(o[l]);

    if(trace_on()) debug->iprintf("%.*sint: %s = %i",level,space,asn1.name,(int)c);
    else if(dbg) dbg->iprintf("\n%.*sint: %s = %i",level+6,space,asn1.name,(int)c);
}

/*---------------------------------------------------------------------------*/
/* INTE                                                                      */
/*---------------------------------------------------------------------------*/

void asn1_context_per::read_inte(class asn1_inte & asn1, class asn1_in & in)
{
    dword i=0;
    struct asn1_tag * b;
    word count_bits = asn1.count_bits;

    if(in.bit()) {
        count_bits = 0;
    }
    if(!count_bits) {
        int l;
        in.align();
        l = in.octet();

        for(int j=0; j<l; j++) {
            i <<=8;
            i |=in.octet();
        }
    }
    else if(count_bits<=8) {
        i = in.bit_field(count_bits);
    }
    else if(count_bits && count_bits<=16) {
        in.align();
        i = in.octet();
        i<<=8;
        i |=in.octet();
    }
    else {
        int l;
        l = in.bit_field(2);
        in.align();

        for(int j=0; j<l+1; j++) {
            i <<=8;
            i |=in.octet();
        }
    }

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

    void ** p = &b->content;
    *(dword *)p = i;

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

void asn1_context_per::write_inte(class asn1_inte & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    dword c;
    word count_bits;

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

    c = (dword)(uintp)b->content;

    if(c>asn1.max) {
        out.bit(1);
        count_bits = 0;
    }
    else {
        count_bits = asn1.count_bits;
    }

    if(!count_bits) {
        byte o[4];

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

        for(c=0;c<3 && !o[c]; c++);
        out.align();
        out.octet((byte)(4-c));
        for(;c<4;c++) out.octet(o[c]);
    }
    else if(count_bits<=8) {
        out.bit_field(c,count_bits);
    }
    else if(count_bits && count_bits<=16) {
        out.align();
        out.bit_field(c,16);
    }
    else {
        byte o[4];

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

        for(c=0;!o[c] && c<3; c++);
        out.bit_field(3-c,2);
        out.align();
        for(;c<4;c++) out.octet(o[c]);
    }

    if(trace_on()) debug->iprintf("%.*sint: %s = %i",level,space,asn1.name,c);
    else if(dbg) dbg->iprintf("\n%.*sint: %s = %i",level+6,space,asn1.name,c);
}

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

void asn1_context_per::read_int8(class asn1_int8 & asn1, class asn1_in & in)
{
    dword c;
    struct asn1_tag * b;

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

    in.align();
    void ** p = &b->content;
    c = in.octet();
    *(dword *)p = c;

    if(trace_on()) debug->iprintf("%.*sint8: %s = %i",level,space,asn1.name,(int)c);
    else if(dbg) dbg->iprintf("\n%.*sint8: %s = %i",level+6,space,asn1.name,(int)c);
}

void asn1_context_per::write_int8(class asn1_int8 & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    dword c;

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

    c = (dword)(uintp)b->content;

    out.align();
    out.octet((byte)c);

    if(trace_on()) debug->iprintf("%.*sint8: %s = %i",level,space,asn1.name,(int)c);
    else if(dbg) dbg->iprintf("\n%.*sint8: %s = %i",level+6,space,asn1.name,(int)c);
}

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

void asn1_context_per::read_int16(class asn1_int16 & asn1, class asn1_in & in)
{
    dword c;
    struct asn1_tag * b;

    in.align();
    c = in.octet();
    c<<=8;
    c|=in.octet();

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

    void ** p = &b->content;
    *(dword *)p = c;

    if(trace_on()) debug->iprintf("%.*sint16: %s = %i",level,space,asn1.name,c);
    else if(dbg) dbg->iprintf("\n%.*sint16: %s = %i",level+6,space,asn1.name,c);
}

void asn1_context_per::write_int16(class asn1_int16 & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    dword c;

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

    c = (dword)(uintp)b->content;

    out.align();
    out.octet((byte)(c>>8));
    out.octet((byte)c);

    if(trace_on()) debug->iprintf("%.*sint16: %s = %i",level,space,asn1.name,(int)c);
    else if(dbg) dbg->iprintf("\n%.*sint16: %s = %i",level+6,space,asn1.name,(int)c);
}

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

void asn1_context_per::read_bitstring(class asn1_bitstring & asn1, class asn1_in & in)
{
    int l;
    struct asn1_tag * b;
    struct octet_string_buffer * c;

    l = per_read_int(in,asn1.len_bits);
    in.align();
    l+=asn1.min_length;

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

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

    c->len = l;
    c->b_ptr = c->b;
    in.get_reference(c->in_buffer,c->in_offset);
    in.octet_field(c->b,l/8);

    if(trace_on()) debug->iprintf("%.*sbitstring: %s(%i)",level,space,asn1.name,l);
    else if(dbg) dbg->iprintf("\n%.*sbitstring: %s(%i)",level+6,space,asn1.name,l);
}

void asn1_context_per::write_bitstring(class asn1_bitstring & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    struct octet_string_buffer * c;
    int l;

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

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

    l = c->len-asn1.min_length;
    per_write_int(out,asn1.len_bits,l);
    out.align();
    out.get_reference(c->in_buffer,c->in_offset);
    out.octet_field(c->b_ptr,c->len/8);

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

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

void asn1_context_per::read_enumerated(class asn1_enumerated & asn1, class asn1_in & in)
{
    struct asn1_tag * b;
    dword enumerated = 0;
    bool extended = false;

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

    if(asn1.properties & ASN1_EXTENSION) {
        if(in.bit()) extended = true;
    }

    if(!extended) {
        
        enumerated = in.bit_field(asn1.count_bits);
        void ** p = &b->content;
        *(dword *)p = enumerated;

        if(trace_on()) {
            if(level==0) debug->iprintf("asn1-read");
            debug->iprintf("%.*senumerated: %s = %i",level,space,asn1.name,enumerated);
        }
        else if(dbg) {
            if(level==0) dbg->iprintf("\n      asn1-read");
            dbg->iprintf("\n%.*senumerated: %s = %i",level+6,space,asn1.name,enumerated);
        }
    }
    else {
            // >63?
        if(in.bit()) {
            // !!!!
        }
        else {
            enumerated = in.bit_field(6);
        }
        void ** p = &b->content;
        *(dword *)p = asn1.count+enumerated;

        if(trace_on()) {
            if(level==0) debug->iprintf("asn1-read");
            debug->iprintf("%.*schoice: %s = %iExt",level,space,asn1.name,enumerated);
        }
        else if(dbg) {
            if(level==0) dbg->iprintf("\n      asn1-read");
            dbg->iprintf("\n%.*schoice: %s = %iExt",level+6,space,asn1.name,enumerated);
        }
    }
}

void asn1_context_per::write_enumerated(class asn1_enumerated & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    dword enumerated;
    bool extended = false;

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

    void ** p = &b->content;
    enumerated = *(dword *)p;

    if(asn1.properties &ASN1_EXTENSION) {
        if(enumerated<asn1.count) {
            out.bit(0);
        }
        else {
            out.bit(1);
            extended = true;
        }
    }
    if(!extended) {
        if(trace_on()) {
            if(level==0) debug->iprintf("asn1-write");
            debug->iprintf("%.*senumerated: %s = %i",level,space,asn1.name,enumerated);
        }
        else if(dbg) {
            if(level==0) dbg->iprintf("\n      asn1-write");
            dbg->iprintf("\n%.*senumerated: %s = %i",level+6,space,asn1.name,enumerated);
        }
        out.bit_field(enumerated,asn1.count_bits);
    }
    else {
        if(trace_on()) {
            if(level==0) debug->iprintf("asn1-write");
            debug->iprintf("%.*senumerated: %s = %iExt",level,space,asn1.name,enumerated-asn1.count);
        }
        else if(dbg) {
            if(level==0) dbg->iprintf("\n      asn1-write");
            dbg->iprintf("\n%.*senumerated: %s = %iExt",level+6,space,asn1.name,enumerated-asn1.count);
        }
        out.bit(0);
        out.bit_field(enumerated-asn1.count,6);
    }
}

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

void asn1_context_per::read_octet_array(class asn1_octet_array & asn1, class asn1_in & in)
{
    struct asn1_tag * b;

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

    in.align();
    in.octet_field((byte *)b->content,asn1.length);

    if(trace_on()) debug->iprintf("%.*soctet_array: %s(%i)",level,space,asn1.name,asn1.length);
    else if(dbg) dbg->iprintf("\n%.*soctet_array: %s(%i)",level+6,space,asn1.name,asn1.length);
}

void asn1_context_per::write_octet_array(class asn1_octet_array & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    byte * c;

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

    c = (byte *)b->content;

    out.align();
    out.octet_field(c,asn1.length);

    if(trace_on()) debug->iprintf("%.*soctet_array: %s(%i)",level,space,asn1.name,asn1.length);
    else if(dbg) dbg->iprintf("\n%.*soctet_array: %s(%i)",level+6,space,asn1.name,asn1.length);
}

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

void asn1_context_per::read_ia5_string(class asn1_ia5_string & asn1, class asn1_in & in)
{
    dword l;
    struct asn1_tag * b;
    struct octet_string_buffer * c;

    if(!asn1.min_length || (asn1.min_length!=asn1.max_length)) {
        l = per_read_int(in,asn1.len_bits);
        l+=asn1.min_length;
        if(asn1.max_length*asn1.char_bits>=16) in.align();
    }
    else {
        l = asn1.min_length;
        if(asn1.min_length*asn1.char_bits>16) in.align();
    }

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

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

    c->len = l;
    c->b_ptr = c->b;
    if(asn1.from) {
        for(dword i=0; i<l; i++) {
            c->b[i] = asn1.from[in.bit_field(asn1.char_bits)];
        }
    }
    else {
        for(dword i=0; i<l; i++) {
            c->b[i] = (byte)in.bit_field(asn1.char_bits);
        }
    }

    if(trace_on()) debug->iprintf("%.*sia5_string: %s(%i)",level,space,asn1.name,l);
    else if(dbg) dbg->iprintf("\n%.*sia5_string: %s(%i)",level+6,space,asn1.name,l);
}

void asn1_context_per::write_ia5_string(class asn1_ia5_string & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    struct octet_string_buffer * c;
    int l;

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

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

    if(!asn1.min_length || (asn1.min_length!=asn1.max_length)) {
        l = c->len;
        per_write_int(out,asn1.len_bits,l-asn1.min_length);
        if(asn1.max_length*asn1.char_bits>=16) out.align();
    }
    else {
        l = asn1.min_length;
        if(asn1.max_length*asn1.char_bits>16) out.align();
    }

    if(asn1.from) {
        for(int i=0; i<l; i++) {
            byte v;
            for(v=0; asn1.from[v] && (byte)asn1.from[v]!=c->b_ptr[i]; v++);
            out.bit_field(v,asn1.char_bits);
        }
    }
    else {
        for(int i=0; i<l; i++) {
            out.bit_field(c->b[i],asn1.char_bits);
        }
    }

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

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

void asn1_context_per::read_octet_string(class asn1_octet_string & asn1, class asn1_in & in)
{
    int l;
    struct asn1_tag * b;
    struct octet_string_buffer * c;

    l = per_read_int(in,asn1.len_bits);
    in.align();
    l+=asn1.min_length;

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

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

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

    if(trace_on()) debug->iprintf("%.*soctet_string: %s(%i)",level,space,asn1.name,l);
    else if(dbg) dbg->iprintf("\n%.*soctet_string: %s(%i)",level+6,space,asn1.name,l);
}

void asn1_context_per::write_octet_string(class asn1_octet_string & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    struct octet_string_buffer * c;
    int l;

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

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

    l = c->len-asn1.min_length;
    per_write_int(out,asn1.len_bits,l);
    out.align();
    out.octet_field(c->b_ptr,c->len);

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

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

void asn1_context_per::read_word_string(class asn1_word_string & asn1, class asn1_in & in)
{
    int l;
    struct asn1_tag * b;
    struct word_string_buffer * c;

    l = per_read_int(in,asn1.len_bits);
    in.align();

    l+=asn1.min_length;

    b = new_tag(asn1.tag,sizeof(struct word_string_buffer)+(l*2),3);
    if(!b) {
        in.abort();
        return;
    }

    c = (struct word_string_buffer *)b->content;
    c->b_ptr = c->b;
    c->len = l;

    for(int i=0; i<l; i++) {
        c->b[i] = in.octet();
        c->b[i] <<=8;
        c->b[i] |=in.octet();
    }

    if(trace_on()) debug->iprintf("%.*sword_string: %s(%i)",level,space,asn1.name,l);
    else if(dbg) dbg->iprintf("\n%.*sword_string: %s(%i)",level+6,space,asn1.name,l);
}

void asn1_context_per::write_word_string(class asn1_word_string & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    struct word_string_buffer * c;
    int l;

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

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

    l = c->len-asn1.min_length;
    per_write_int(out,asn1.len_bits,l);
    out.align();

    for(int i=0; i<c->len; i++) {
        out.octet(c->b_ptr[i]>>8);
        out.octet((byte)c->b_ptr[i]);
    }

    if(trace_on()) debug->iprintf("%.*sword_string: %s(%i)",level,space,asn1.name,c->len);
    else if(dbg) dbg->iprintf("\n%.*sword_string: %s(%i)",level+6,space,asn1.name,c->len);
}

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

void asn1_context_per::read_object_object_identifier(class asn1_object_identifier & asn1, class asn1_in & in)
{
    byte len;

    struct asn1_tag * b;
    byte * c;

    in.align();
    len = in.octet();

    b = new_tag(asn1.tag,len+1,0);
    if(!b) {
        in.abort();
        return;
    }

    c = (byte *)b->content;

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

    if(trace_on()) debug->iprintf("%.*sobject_id: %s(%i)",level,space,asn1.name,len);
    else if(dbg) dbg->iprintf("\n%.*sobject_id: %s(%i)",level+6,space,asn1.name,len);
}

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

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

    c = (byte *)b->content;

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

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

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

void asn1_context_per::read_sequence(class asn1_sequence & asn1, class asn1_in & in)
{
    int i;
    struct asn1_tag * b;
    bool present[100];

    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;
    }
    else if(dbg) {
        if(level==0) dbg->iprintf("\n      asn1-read");
        dbg->iprintf("\n%.*ssequence: %s",level+6,space,asn1.name);
        level +=ASN1_TAB;
    }
    if(asn1.properties &ASN1_EXTENSION) {
        if(in.bit()) b->content = (void *)ASN1_EXTENDED;
    }
    for(i=0;i<asn1.count;i++) {
        present[i] = true;
        if(asn1.flags[i] &ASN1_OPTIONAL) {
            if(!in.bit()) {
                present[i] = false;
            }
        }
    }
    for(i=0;i<asn1.count;i++) {
        if(in.left()<0) return;
        if(present[i]) {
            if(!asn1.table[i]) {
                in.abort();
                return;
            }
            read(*asn1.table[i],in);
        }
    }

    if((((byte *)b->content) - ((byte *)0)) &ASN1_EXTENDED) {
        int c;
        dword mask;
        int i;
        dword ext_mask;

        if(trace_on()) debug->iprintf("%.*sext",level-ASN1_TAB,space);
        else if(dbg) dbg->iprintf("\n%.*sext",level-ASN1_TAB+6,space);

        c = in.bit_field(7);
//        ASSERT(count<32);
        mask = in.bit_field(c+1);
        for(i=0, ext_mask=(1<<c);i<c+1;i++,ext_mask>>=1) {
            int len;

            if(in.left()<0) return;

            if(mask &ext_mask) {
                in.align();
                len = in.octet();
                if(len &0x80) {
                    len &=0x3f;
                    len <<=8;
                    len |=in.octet();
                }
                if((i<asn1.ext_count) && asn1.table[asn1.count+i]) {
                    int c = in.get_count()+len;
                    read(*asn1.table[asn1.count+i],in);
                    in.align();
                    in.set_count(c);
                }
                else {
                    if(trace_on()) debug->iprintf("%.*sunknown ext=%i, len=%i",level,space,i,len);
                    else if(dbg) dbg->iprintf("\n%.*sunknown ext=%i, len=%i",level+6,space,i,len);

                    in.octet_field(0,len);
                }
            }
        }
    }
    if(trace_on()) level-=ASN1_TAB;
    else if(dbg) level-=ASN1_TAB;
}

void asn1_context_per::write_sequence(class asn1_sequence & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    int i;
    bool * present = (bool *)alloca(asn1.count + asn1.ext_count);
    bool extended = false;

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

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

    memset(present, 0, asn1.count + asn1.ext_count);
    for(i=0; i<asn1.count+asn1.ext_count; i++) {
        if(asn1.table[i] && asn1.table[i]->is_present(*this)) {
            present[i] = true;
            if(i>=asn1.count) extended = true;
        }
    }

    if(asn1.properties &ASN1_EXTENSION) {
        if(extended) out.bit(1);
        else out.bit(0);
    }
    for(i=0;i<asn1.count;i++) {
        if(asn1.flags[i] &ASN1_OPTIONAL) {
            if(present[i]) out.bit(true);
            else out.bit(false);
        }
    }
    for(i=0;i<asn1.count;i++) {
        write(*asn1.table[i],out);
    }

    if(extended) {

        if(trace_on()) debug->iprintf("%.*sext",level-ASN1_TAB,space);
        else if(dbg) dbg->iprintf("\n%.*sext",level-ASN1_TAB+6,space);

        out.bit_field(asn1.ext_count-1,7);
        for(i=0; i<asn1.ext_count; i++) {
            if(present[asn1.count+i]) {
                out.bit(true);
            }
            else {
                out.bit(false);
            }
        }

        for(i=0; i<asn1.ext_count; i++) {
            if(present[asn1.count+i]) {
                void * save_out;
                dword len;

                out.align();
                save_out = out.new_stream(0);
                write(*asn1.table[asn1.count+i],out);
                out.align();
                len = out.length();
                save_out = out.new_stream(save_out);
                if(len>=128) {
                    out.octet((byte)(len>>8)|0x80);
                }
                if(len==0) {
                    out.octet((byte)1);
                    out.octet((byte)0);
                    out.append(save_out);
                }
                else {
                    out.octet((byte)len);
                    out.append(save_out);
                }
            }
        }
    }
    if(trace_on()) level-=ASN1_TAB;
    else if(dbg) level-=ASN1_TAB;
}

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

void asn1_context_per::read_choice(class asn1_choice & asn1, class asn1_in & in)
{
    struct asn1_tag * b;
    dword choice=0;
    bool extended = false;

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

    if(asn1.properties & ASN1_EXTENSION) {
        if(in.bit()) extended = true;
    }

    if(!extended) {
        
        choice = in.bit_field(asn1.count_bits);
        if(choice>=(dword)asn1.count) {
            debug->iprintf("%s:choice-error %i>=%i",asn1.name,choice,asn1.count);
            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;
        }
        else if(dbg) {
            if(level==0) dbg->iprintf("\n      asn1-read");
            dbg->iprintf("\n%.*schoice: %s = %i",level+6,space,asn1.name,choice);
            level +=ASN1_TAB;
        }

        if(asn1.table) {
            if(asn1.table[choice]) {
                read(*asn1.table[choice],in);
            }
        }
    }
    else {
        int len;

            // >63?
        if(in.bit()) {
            // !!!!
        }
        else {
            choice = in.bit_field(6);
        }
        void ** p = &b->content;
        *(dword *)p = asn1.count+choice;

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

        in.align();
        len = in.octet();
        if(len &0x80) {
            len &=0x3f;
            len <<=8;
            len |=in.octet();
        }
        if(choice<asn1.ext_count && asn1.table && asn1.table[asn1.count+choice]) {
            int c = in.get_count()+len;
            read(*asn1.table[asn1.count+choice],in);
            in.align();
            in.set_count(c);
        }
        else {
            in.octet_field(0,len);
        }
    }
    if(trace_on()) level -=ASN1_TAB;
    else if(dbg) level -=ASN1_TAB;
}

void asn1_context_per::write_choice(class asn1_choice & asn1, class asn1_out & out)
{
    struct asn1_tag * b;
    dword choice;
    bool extended = false;

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

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

    if(asn1.properties &ASN1_EXTENSION) {
        if(choice<asn1.count) {
            out.bit(0);
        }
        else {
            out.bit(1);
            extended = true;
        }
    }
    if(!extended) {
        if(trace_on()) {
            if(level==0) debug->iprintf("asn1-write");
            debug->iprintf("%.*schoice: %s = %i",level,space,asn1.name,choice);
            level +=ASN1_TAB;
        }
        else if(dbg) {
            if(level==0) dbg->iprintf("\n      asn1-write");
            dbg->iprintf("\n%.*schoice: %s = %i",level+6,space,asn1.name,choice);
            level +=ASN1_TAB;
        }
        out.bit_field(choice,asn1.count_bits);

        if(asn1.table) {
            if(asn1.table[choice])
                write(*asn1.table[choice],out);
        }
    }
    else {
        void * save_out=0;
        dword len=0;

        if(trace_on()) {
            if(level==0) debug->iprintf("asn1-write");
            debug->iprintf("%.*schoice: %s = %iE",level,space,asn1.name,choice-asn1.count);
            level +=ASN1_TAB;
        }
        else if(dbg) {
            if(level==0) dbg->iprintf("\n      asn1-write");
            dbg->iprintf("\n%.*schoice: %s = %iE",level+6,space,asn1.name,choice-asn1.count);
            level +=ASN1_TAB;
        }
        out.bit(0);
        out.bit_field(choice-asn1.count,6);
        out.align();
        if(choice<(dword)(asn1.count+asn1.ext_count) && asn1.table && asn1.table[choice]) {
            save_out = out.new_stream(0);
            write(*asn1.table[choice],out);
            out.align();
            len = out.length();
            save_out = out.new_stream(save_out);
        }
        if(len>=128) {
            out.octet((byte)(len>>8)|0x80);
        }
        out.octet((byte)len);
        if(save_out) out.append(save_out);
    }
    if(trace_on()) level -=ASN1_TAB;
    else if(dbg) level -=ASN1_TAB;
}

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

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

    if(!asn1.count_bits) {
        in.align();
        count = in.octet();
        if(count &0x80) {
            //!!!
        }
    }
    else if(asn1.count_bits<=8) {
        count = in.bit_field(asn1.count_bits);
    }
    else if(asn1.count_bits==9) {
        in.align();
        count = in.octet();
    }
    else if(asn1.count_bits==16) {
        in.align();
        count = in.bit_field(16);
    }
    count +=asn1.min_length;

    if(trace_on()) {
        if(level==0) debug->iprintf("asn1-read");
        debug->iprintf("%.*ssequence_of: %s(%i)",level,space,asn1.name,count);
        level +=ASN1_TAB;
    }
    else if(dbg) {
        if(level==0) dbg->iprintf("\n      asn1-read");
        dbg->iprintf("\n%.*ssequence_of: %s(%i)",level+6,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;
        m = (~save_seq_mask) & (save_seq_mask<<1);
        seq_of = save_seq_of + (i*m);
        read(*asn1.seq,in);
    }

    seq_of = save_seq_of;
    seq_mask = save_seq_mask;

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

void asn1_context_per::write_sequence_of(class asn1_sequence_of & asn1, class asn1_out & out)
{
    dword count=0;
    struct asn1_tag * b;
    dword save_seq_of;
    dword save_seq_mask;
    dword mask;

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

    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;
    }
    else if(dbg) {
        if(level==0) dbg->iprintf("\n      asn1-write");
        dbg->iprintf("\n%.*ssequence_of: %s(%i)",level+6,space,asn1.name,count);
        level +=ASN1_TAB;
    }

    count -=asn1.min_length;

    if(!asn1.count_bits) {
        out.align();
        if(count<128) {
            out.octet((byte)count);
        }
        else {
            //!!
        }
    }
    else if(asn1.count_bits<=8) {
        out.bit_field(count,asn1.count_bits);
    }
    else if(asn1.count_bits==9) {
        out.align();
        out.octet((byte)count);
    }
    else if(asn1.count_bits==16) {
        out.align();
        out.bit_field(count,16);
    }
    save_seq_of = seq_of;
    save_seq_mask = seq_mask;

    seq_mask = mask;

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

    seq_of = save_seq_of;
    seq_mask = save_seq_mask;

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

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

void asn1_context_per::read_any(class asn1_any & asn1, class asn1_in & in)
{
    class asn1 * actual = asn1.get_actual(*this);
    int len = in.octet();

    if(len &0x80) {
        len &=0x3f;
        len <<=8;
        len |=in.octet();
    }
    if(actual) {
        int c = in.get_count()+len;
        read(*actual,in);
        in.align();
        in.set_count(c);
    }
    else {
        if(trace_on()) debug->iprintf("%.*sunknown op, len=%i",level,space,len);
        else if(dbg) dbg->iprintf("\n%.*sunknown op, len=%i",level+6,space,len);

        in.octet_field(0,len);
    }
}

void asn1_context_per::write_any(class asn1_any & asn1, class asn1_out & out)
{
    class asn1 * actual = asn1.get_actual(*this);
    void * save_out=0;
    dword len=0;

    if(actual && actual->is_present(*this)) {
        save_out = out.new_stream(0);
        write(*actual,out);
        out.align();
        len = out.length();
        save_out = out.new_stream(save_out);
    }
    if(len>=128) {
        out.octet((byte)(len>>8)|0x80);
    }
    out.octet((byte)len);
    if(save_out) out.append(save_out);
}

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

static dword per_read_int(class asn1_in & in, int size)
{
    dword i=0;

    if(size==0) {
        in.align();
        i = in.octet();
        if(i&0x80) {
            i &=0x7f;
            i<<=8;
            i |=in.octet();
        }
    }
    else if(size<=8) {
        i = in.bit_field(size);
    }
    else if(size==9) {
        in.align();
        i = in.octet();
    }
    else if(size==16) {
        in.align();
        i = in.octet();
        i<<=8;
        i |=in.octet();
    }
    else {
        int l;
        l = in.bit_field(2);
        in.align();

        for(int j=0; j<l+1; j++) {
            i <<=8;
            i |=in.octet();
        }
    }
    return i;
}

static void per_write_int(class asn1_out &out, int size, dword c)
{
    if(size==0) {
        out.align();
        if(c>=128) {
            dword o;
            o = c>>8;
            o |=0x80;
            out.octet((byte)o);
        }
        out.octet((byte)c);
    }
    else if(size<=8) {
        out.bit_field(c,size);
    }
    else if(size==9) {
        out.align();
        out.octet((byte)c);
    }
    else if(size==16) {
        out.align();
        out.bit_field(c,16);
    }
    else {
        byte o[4];

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

        for(c=0;!o[c] && c<3; c++);
        out.bit_field(3-c,2);
        out.align();
        for(;c<4;c++) out.octet(o[c]);
    }
}
