[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

ASN.1 Encoding Technique



Love,

I was reading your Blog. I got the impression that you were still working
on your ASN.1 compiler. I have a little bit of experience here. I have
written an MSRPC compiler. The principles are a little similar. Of
course ASN.1 has it's own problems. At one time I looked at ASN.1 and
I had some thoughts about how best to encode and decode it. I thought I
would share with you those thoughts. Perhaps it will give you some ideas.

Anyway, I think since I'm really just tring to advocate my encoding /
decode technique, I should probably just present a code example. Consider
the following ASN.1:

Record ::= SEQUENCE {
    name [0] Name OPTIONAL,
    oid  [1] OBJECT IDENTIFER
}

This might be encoded/decoded in DER w/ something like the following
ultra-minimalistic C. This only shows the ASN.1 routines for the Record
type but of course the other objects would be just more of the same.

struct asn1_Record {
    size_t size;
    struct asn1_Name *name;
    struct asn1_OID *oid;
};

size_t
asn1_Record_size(struct asn1_Record *obj)
{
    if (obj->size == 0) {
        size_t s_oid;

        if (obj->name) {
            size_t s_name = asn1_Name_size(obj->name);
                    /* ans1_lenlen returns the number of bytes
                     * required to encode the specified length value
                     */
            obj->size += 1 + asn1_lenlen(s_name) + s_name;
        }

        s_oid = asn1_OID_size(obj->oid);
        obj->size += 1 + asn1_lenlen(s_oid) + s_oid;
    }

    return obj->size;
}
void
asn1_Record_encode(struct asn1 *asn1, struct asn1_Record *obj, buf_t *dst)
{
    size_t s_oid;

    buf_enc_intn(dst, 0x60, 1); /* encode 0x60 in 1 bytes */
    buf_enc_length(dst, asn1_Record_size(obj));

    if (obj->name) {
        size_t s_name = asn1_Name_size(obj->name);
        buf_enc_intn(dst, 0xA0, 1);
        buf_enc_length(s_name);
        asn1_Name_encode(asn1, obj->name, dst);
    }

    s_oid = asn1_OID_size(obj->oid);
    buf_enc_intn(dst, 0xA1, 1);
    buf_enc_length(dst, s_oid);
    asn1_OID_encode(asn1, obj->oid, dst);
}
void
asn1_Record_decode(struct asn1 *asn1, struct asn1_Record *obj, buf_t *src)
{
    buf_dec_intn(src, 1);
    buf_dec_length(src);
    for ( ;; ) {
        int tag = buf_dec_intn(src, 1) & 0xF;
        buf_dec_length(src);
        switch (tag) {
            case 0:
                obj->name = asn1_Name_new(asn1);
                asn1_Name_decode(asn1, obj->name, src);
                break;
            case 1:
                ...
    }
}

I think the most important idea here is the use of dynamic programming to
precomupute and save the sizes of all objects thereby making it possible
to encode everything without copying data or allocating intermediary
buffers. Also noteworthy is the use of the buf_t type to to hide a lot
of stuff about advancing pointers, checking bounds, endianness, etc. And
the asn1 context is nice because it can be used to hold encoding state.

Anyway, if I were to write an ASN.1 compiler I think this is what I
would like my code to look like.

Mike