YAZ User's Guide and Reference
Prev | Chapter 9. The ODR Module | Next

# Programming with ODR

The API of ODR is designed to reflect the structure of ASN.1, rather than BER itself. Future releases may be able to represent data in other external forms.

The interface is based loosely on that of the Sun Microsystems XDR routines. Specifically, each function which corresponds to an ASN.1 primitive type has a dual function. Depending on the settings of the ODR stream which is supplied as a parameter, the function may be used either to encode or decode data. The functions that can be built using these primitive functions, to represent more complex data types, share this quality. The result is that you only have to enter the definition for a type once - and you have the functionality of encoding, decoding (and pretty-printing) all in one unit. The resulting C source code is quite compact, and is a pretty straightforward representation of the source ASN.1 specification.

In many cases, the model of the XDR functions works quite well in this role. In others, it is less elegant. Most of the hassle comes from the optional SEQUENCE members which don't exist in XDR.

## The Primitive ASN.1 Types

ASN.1 defines a number of primitive types (many of which correspond roughly to primitive types in structured programming languages, such as C).

### INTEGER

The ODR function for encoding or decoding (or printing) the ASN.1 INTEGER type looks like this:

    int odr_integer(ODR o, int **p, int optional, const char *name);

(we don't allow values that can't be contained in a C integer.)

This form is typical of the primitive ODR functions. They are named after the type of data that they encode or decode. They take an ODR stream, an indirect reference to the type in question, and an `optional` flag (corresponding to the OPTIONAL keyword of ASN.1) as parameters. They all return an integer value of either one or zero. When you use the primitive functions to construct encoders for complex types of your own, you should follow this model as well. This ensures that your new types can be reused as elements in yet more complex types.

The `o` parameter should obviously refer to a properly initialized ODR stream of the right type (encoding/decoding/printing) for the operation that you wish to perform.

When encoding or printing, the function first looks at `* p`. If `* p` (the pointer pointed to by `p`) is a null pointer, this is taken to mean that the data element is absent. If the `optional` parameter is nonzero, the function will return one (signifying success) without any further processing. If the `optional` is zero, an internal error flag is set in the ODR stream, and the function will return 0. No further operations can be carried out on the stream without a call to the function `odr_reset()`.

If `*p` is not a null pointer, it is expected to point to an instance of the data type. The data will be subjected to the encoding rules, and the result will be placed in the buffer held by the ODR stream.

The other ASN.1 primitives have similar functions that operate in similar manners:

### BOOLEAN

    int odr_bool(ODR o, bool_t **p, int optional, const char *name);

### REAL

Not defined.

### NULL

    int odr_null(ODR o, bool_t **p, int optional, const char *name);

In this case, the value of **p is not important. If `*p` is different from the null pointer, the null value is present, otherwise it's absent.

### OCTET STRING

    typedef struct odr_oct
    {
        unsigned char *buf;
        int len;
        int size;
    } Odr_oct;

    int odr_octetstring(ODR o, Odr_oct **p, int optional,
                        const char *name);

The `buf` field should point to the character array that holds the octetstring. The `len` field holds the actual length, while the `size` field gives the size of the allocated array (not of interest to you, in most cases). The character array need not be null terminated.

To make things a little easier, an alternative is given for string types that are not expected to contain embedded NULL characters (eg. VisibleString):

          int odr_cstring(ODR o, char **p, int optional, const char *name);

Which encoded or decodes between OCTETSTRING representations and null-terminates C strings.

Functions are provided for the derived string types, eg:

    int odr_visiblestring(ODR o, char **p, int optional,
                          const char *name);

### BIT STRING

    int odr_bitstring(ODR o, Odr_bitmask **p, int optional,
                      const char *name);

The opaque type `Odr_bitmask` is only suitable for holding relatively brief bit strings, eg. for options fields, etc. The constant `ODR_BITMASK_SIZE` multiplied by 8 gives the maximum possible number of bits.

A set of macros are provided for manipulating the `Odr_bitmask` type:

    void ODR_MASK_ZERO(Odr_bitmask *b);

    void ODR_MASK_SET(Odr_bitmask *b, int bitno);

    void ODR_MASK_CLEAR(Odr_bitmask *b, int bitno);

    int ODR_MASK_GET(Odr_bitmask *b, int bitno);

The functions are modeled after the manipulation functions that accompany the `fd_set` type used by the `select(2)` call. `ODR_MASK_ZERO` should always be called first on a new bitmask, to initialize the bits to zero.

### OBJECT IDENTIFIER

    int odr_oid(ODR o, Odr_oid **p, int optional, const char *name);

The C OID representation is simply an array of integers, terminated by the value -1 (the `Odr_oid` type is synonymous with the `int` type). We suggest that you use the OID database module (see section Object Identifiers) to handle object identifiers in your application.

## Tagging Primitive Types

The simplest way of tagging a type is to use the `odr_implicit_tag()` or `odr_explicit_tag()` macros:

    int odr_implicit_tag(ODR o, Odr_fun fun, int class, int tag,
                         int optional, const char *name);

    int odr_explicit_tag(ODR o, Odr_fun fun, int class, int tag,
                         int optional, const char *name);

To create a type derived from the integer type by implicit tagging, you might write:

         MyInt ::= [210] IMPLICIT INTEGER

In the ODR system, this would be written like:

    int myInt(ODR o, int **p, int optional, const char *name)
    {
        return odr_implicit_tag(o, odr_integer, p,
    			    ODR_CONTEXT, 210, optional, name);
    }

The function `myInt()` can then be used like any of the primitive functions provided by ODR. Note that the behavior of `odr_explicit()` and `odr_implicit()` macros act exactly the same as the functions they are applied to - they respond to error conditions, etc, in the same manner - they simply have three extra parameters. The class parameter may take one of the values: `ODR_CONTEXT`, `ODR_PRIVATE`, `ODR_UNIVERSAL`, or `/ODR_APPLICATION`.

## Constructed Types

Constructed types are created by combining primitive types. The ODR system only implements the SEQUENCE and SEQUENCE OF constructions (although adding the rest of the container types should be simple enough, if the need arises).

For implementing SEQUENCEs, the functions

    int odr_sequence_begin(ODR o, void *p, int size, const char *name);
    int odr_sequence_end(ODR o);

are provided.

The `odr_sequence_begin()` function should be called in the beginning of a function that implements a SEQUENCE type. Its parameters are the ODR stream, a pointer (to a pointer to the type you're implementing), and the `size` of the type (typically a C structure). On encoding, it returns 1 if `* p` is a null pointer. The `size` parameter is ignored. On decoding, it returns 1 if the type is found in the data stream. `size` bytes of memory are allocated, and `*p` is set to point to this space. `odr_sequence_end()` is called at the end of the complex function. Assume that a type is defined like this:

    MySequence ::= SEQUENCE {
         intval INTEGER,
         boolval BOOLEAN OPTIONAL
    }

The corresponding ODR encoder/decoder function and the associated data structures could be written like this:

    typedef struct MySequence
    {
        int *intval;
        bool_t *boolval;
    } MySequence;

    int mySequence(ODR o, MySequence **p, int optional, const char *name)
    {
        if (odr_sequence_begin(o, p, sizeof(**p), name) == 0)
            return optional && odr_ok(o);
        return
            odr_integer(o, &(*p)->intval, 0, "intval") &&
            odr_bool(o, &(*p)->boolval, 1, "boolval") &&
            odr_sequence_end(o);
    }

Note the 1 in the call to `odr_bool()`, to mark that the sequence member is optional. If either of the member types had been tagged, the macros `odr_implicit()` or `odr_explicit()` could have been used. The new function can be used exactly like the standard functions provided with ODR. It will encode, decode or pretty-print a data value of the `MySequence` type. We like to name types with an initial capital, as done in ASN.1 definitions, and to name the corresponding function with the first character of the name in lower case. You could, of course, name your structures, types, and functions any way you please - as long as you're consistent, and your code is easily readable. `odr_ok` is just that - a predicate that returns the state of the stream. It is used to ensure that the behavior of the new type is compatible with the interface of the primitive types.

## Tagging Constructed Types

> Note: See section Tagging Primitive types for information on how to tag the primitive types, as well as types that are already defined.

### Implicit Tagging

Assume the type above had been defined as

    MySequence ::= [10] IMPLICIT SEQUENCE {
          intval INTEGER,
          boolval BOOLEAN OPTIONAL
    }

You would implement this in ODR by calling the function

    int odr_implicit_settag(ODR o, int class, int tag);

which overrides the tag of the type immediately following it. The macro `odr_implicit()` works by calling `odr_implicit_settag()` immediately before calling the function pointer argument. Your type function could look like this:

    int mySequence(ODR o, MySequence **p, int optional, const char *name)
    {
        if (odr_implicit_settag(o, ODR_CONTEXT, 10) == 0 ||
            odr_sequence_begin(o, p, sizeof(**p), name) == 0)
            return optional && odr_ok(o);
        return
            odr_integer(o, &(*p)->intval, 0, "intval") &&
            odr_bool(o, &(*p)->boolval, 1, "boolval") &&
            odr_sequence_end(o);
    }

The definition of the structure `MySequence` would be the same.

### Explicit Tagging

Explicit tagging of constructed types is a little more complicated, since you are in effect adding a level of construction to the data.

Assume the definition:

    MySequence ::= [10] IMPLICIT SEQUENCE {
       intval INTEGER,
       boolval BOOLEAN OPTIONAL
    }

Since the new type has an extra level of construction, two new functions are needed to encapsulate the base type:

    int odr_constructed_begin(ODR o, void *p, int class, int tag,
                              const char *name);

    int odr_constructed_end(ODR o);

Assume that the IMPLICIT in the type definition above were replaced with EXPLICIT (or that the IMPLICIT keyword were simply deleted, which would be equivalent). The structure definition would look the same, but the function would look like this:

    int mySequence(ODR o, MySequence **p, int optional, const char *name)
    {
        if (odr_constructed_begin(o, p, ODR_CONTEXT, 10, name) == 0)
            return optional && odr_ok(o);
        if (o->direction == ODR_DECODE)
            *p = odr_malloc(o, sizeof(**p));
        if (odr_sequence_begin(o, p, sizeof(**p), 0) == 0)
        {
            *p = 0; /* this is almost certainly a protocol error */
            return 0;
        }
        return
            odr_integer(o, &(*p)->intval, 0, "intval") &&
            odr_bool(o, &(*p)->boolval, 1, "boolval") &&
            odr_sequence_end(o) &&
            odr_constructed_end(o);
    }

Notice that the interface here gets kind of nasty. The reason is simple: Explicitly tagged, constructed types are fairly rare in the protocols that we care about, so the esthetic annoyance (not to mention the dangers of a cluttered interface) is less than the time that would be required to develop a better interface. Nevertheless, it is far from satisfying, and it's a point that will be worked on in the future. One option for you would be to simply apply the `odr_explicit()` macro to the first function, and not have to worry about `odr_constructed_*` yourself. Incidentally, as you might have guessed, the `odr_sequence_` functions are themselves implemented using the `/odr_constructed_` functions.

## SEQUENCE OF

To handle sequences (arrays) of a specific type, the function

    int odr_sequence_of(ODR o, int (*fun)(ODR o, void *p, int optional),
                        void *p, int *num, const char *name);

The `fun` parameter is a pointer to the decoder/encoder function of the type. `p` is a pointer to an array of pointers to your type. `num` is the number of elements in the array.

Assume a type

    MyArray ::= SEQUENCE OF INTEGER

The C representation might be

    typedef struct MyArray
    {
        int num_elements;
        int **elements;
    } MyArray;

And the function might look like

    int myArray(ODR o, MyArray **p, int optional, const char *name)
    {
        if (o->direction == ODR_DECODE)
            *p = odr_malloc(o, sizeof(**p));
        if (odr_sequence_of(o, odr_integer, &(*p)->elements,
            &(*p)->num_elements, name))
            return 1;
        *p = 0;
            return optional && odr_ok(o);
    }

## CHOICE Types

The choice type is used fairly often in some ASN.1 definitions, so some work has gone into streamlining its interface.

CHOICE types are handled by the function:

    int odr_choice(ODR o, Odr_arm arm[], void *p, void *whichp,
                   const char *name);

The `arm` array is used to describe each of the possible types that the CHOICE type may assume. Internally in your application, the CHOICE type is represented as a discriminated union. That is, a C union accompanied by an integer (or enum) identifying the active 'arm' of the union. `whichp` is a pointer to the union discriminator. When encoding, it is examined to determine the current type. When decoding, it is set to reference the type that was found in the input stream.

The Odr_arm type is defined thus:

    typedef struct odr_arm
    {
        int tagmode;
        int class;
        int tag;
        int which;
        Odr_fun fun;
        char *name;
    } Odr_arm;

The interpretation of the fields are:

tagmode

  Either `ODR_IMPLICIT`, `ODR_EXPLICIT`, or `ODR_NONE` (-1) to mark no tagging.

which

  The value of the discriminator that corresponds to this CHOICE element. Typically, it will be a #defined constant, or an enum member.

fun

  A pointer to a function that implements the type of the CHOICE member. It may be either a standard ODR type or a type defined by yourself.

name

  Name of tag.

A handy way to prepare the array for use by the `odr_choice()` function is to define it as a static, initialized array in the beginning of your decoding/encoding function. Assume the type definition:

    MyChoice ::= CHOICE {
        untagged INTEGER,
        tagged   [99] IMPLICIT INTEGER,
        other    BOOLEAN
    }

Your C type might look like

    typedef struct MyChoice
    {
        enum
        {
            MyChoice_untagged,
            MyChoice_tagged,
            MyChoice_other
        } which;
        union
        {
            int *untagged;
            int *tagged;
            bool_t *other;
        } u;
    };

And your function could look like this:

    int myChoice(ODR o, MyChoice **p, int optional, const char *name)
    {
        static Odr_arm arm[] =
        {
          {-1, -1, -1, MyChoice_untagged, odr_integer, "untagged"},
          {ODR_IMPLICIT, ODR_CONTEXT, 99, MyChoice_tagged, odr_integer,
          "tagged"},
          {-1, -1, -1, MyChoice_other, odr_boolean, "other"},
          {-1, -1, -1, -1, 0}
        };

        if (o->direction == ODR_DECODE)
            *p = odr_malloc(o, sizeof(**p);
        else if (!*p)
            return optional && odr_ok(o);

        if (odr_choice(o, arm, &(*p)->u, &(*p)->which), name)
            return 1;
        *p = 0;
            return optional && odr_ok(o);
    }

In some cases (say, a non-optional choice which is a member of a sequence), you can "embed" the union and its discriminator in the structure belonging to the enclosing type, and you won't need to fiddle with memory allocation to create a separate structure to wrap the discriminator and union.

The corresponding function is somewhat nicer in the Sun XDR interface. Most of the complexity of this interface comes from the possibility of declaring sequence elements (including CHOICEs) optional.

The ASN.1 specifications naturally requires that each member of a CHOICE have a distinct tag, so they can be told apart on decoding. Sometimes it can be useful to define a CHOICE that has multiple types that share the same tag. You'll need some other mechanism, perhaps keyed to the context of the CHOICE type. In effect, we would like to introduce a level of context-sensitiveness to our ASN.1 specification. When encoding an internal representation, we have no problem, as long as each CHOICE member has a distinct discriminator value. For decoding, we need a way to tell the choice function to look for a specific arm of the table. The function

    void odr_choice_bias(ODR o, int what);

provides this functionality. When called, it leaves a notice for the next call to `odr_choice()` to be called on the decoding stream `o` that only the `arm` entry with a `which` field equal to `what` should be tried.

The most important application (perhaps the only one, really) is in the definition of application-specific EXTERNAL encoders/decoders which will automatically decode an ANY member given the direct or indirect reference.

Prev | Home | Next
Using ODR | Up | Debugging