encode_atype(asn1buf *buf, const void *val, const struct atype_info *a,
             taginfo *tag_out)
{
    krb5_error_code ret;

    if (val == NULL)
        return ASN1_MISSING_FIELD;

    switch (a->type) {
    case atype_fn: {
        const struct fn_info *fn = a->tinfo;
        assert(fn->enc != NULL);
        return fn->enc(buf, val, tag_out);
    }
    case atype_sequence:
        assert(a->tinfo != NULL);
        ret = encode_sequence(buf, val, a->tinfo);
        if (ret)
            return ret;
        tag_out->asn1class = UNIVERSAL;
        tag_out->construction = CONSTRUCTED;
        tag_out->tagnum = ASN1_SEQUENCE;
        break;
    case atype_ptr: {
        const struct ptr_info *ptr = a->tinfo;
        assert(ptr->basetype != NULL);
        return encode_atype(buf, LOADPTR(val, ptr), ptr->basetype, tag_out);
    }
    case atype_offset: {
        const struct offset_info *off = a->tinfo;
        assert(off->basetype != NULL);
        return encode_atype(buf, (const char *)val + off->dataoff,
                            off->basetype, tag_out);
    }
    case atype_optional: {
        const struct optional_info *opt = a->tinfo;
        assert(opt->is_present != NULL);
        if (opt->is_present(val))
            return encode_atype(buf, val, opt->basetype, tag_out);
        else
            return ASN1_OMITTED;
    }
    case atype_counted: {
        const struct counted_info *counted = a->tinfo;
        const void *dataptr = (const char *)val + counted->dataoff;
        size_t count;
        assert(counted->basetype != NULL);
        ret = load_count(val, counted, &count);
        if (ret)
            return ret;
        return encode_cntype(buf, dataptr, count, counted->basetype, tag_out);
    }
    case atype_nullterm_sequence_of:
    case atype_nonempty_nullterm_sequence_of:
        assert(a->tinfo != NULL);
        ret = encode_nullterm_sequence_of(buf, val, a->tinfo,
                                          a->type ==
                                          atype_nullterm_sequence_of);
        if (ret)
            return ret;
        tag_out->asn1class = UNIVERSAL;
        tag_out->construction = CONSTRUCTED;
        tag_out->tagnum = ASN1_SEQUENCE;
        break;
    case atype_tagged_thing: {
        const struct tagged_info *tag = a->tinfo;
        size_t oldcount = buf->count;
        ret = encode_atype(buf, val, tag->basetype, tag_out);
        if (ret)
            return ret;
        if (!tag->implicit) {
            ret = make_tag(buf, tag_out, buf->count - oldcount);
            if (ret)
                return ret;
            tag_out->construction = tag->construction;
        }
        tag_out->asn1class = tag->tagtype;
        tag_out->tagnum = tag->tagval;
        break;
    }
    case atype_bool:
        k5_asn1_encode_bool(buf, load_int(val, a->size));
        tag_out->asn1class = UNIVERSAL;
        tag_out->construction = PRIMITIVE;
        tag_out->tagnum = ASN1_BOOLEAN;
        break;
    case atype_int:
        k5_asn1_encode_int(buf, load_int(val, a->size));
        tag_out->asn1class = UNIVERSAL;
        tag_out->construction = PRIMITIVE;
        tag_out->tagnum = ASN1_INTEGER;
        break;
    case atype_uint:
        k5_asn1_encode_uint(buf, load_uint(val, a->size));
        tag_out->asn1class = UNIVERSAL;
        tag_out->construction = PRIMITIVE;
        tag_out->tagnum = ASN1_INTEGER;
        break;
    case atype_int_immediate: {
        const struct immediate_info *imm = a->tinfo;
        k5_asn1_encode_int(buf, imm->val);
        tag_out->asn1class = UNIVERSAL;
        tag_out->construction = PRIMITIVE;
        tag_out->tagnum = ASN1_INTEGER;
        break;
    }
    default:
        assert(a->type > atype_min);
        assert(a->type < atype_max);
        abort();
    }

    return 0;
}