NAME="GENERATOR" CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+ ">

Using ODR

Memory Management

Two forms of memory management take place in the ODR system. The first one, which has to do with allocating little bits of memory (sometimes quite large bits of memory, actually) when a protocol package is decoded, and turned into a complex of interlinked structures. This section deals with this system, and how you can use it for your own purposes. The next section deals with the memory management which is required when encoding data - to make sure that a large enough buffer is available to hold the fully encoded PDU.

The ODR module has its own memory management system, which is used whenever memory is required. Specifically, it is used to allocate space for data when decoding incoming PDUs. You can use the memory system for your own purposes, by using the function

     void *odr_malloc(ODR o, int size);

You can't use the normal free(2) routine to free memory allocated by this function, and ODR doesn't provide a parallel function. Instead, you can call

     void odr_reset(ODR o, int size);

when you are done with the memory: Everything allocated since the last call to odr_reset() is released. The odr_reset() call is also required to clear up an error condition on a stream.

The function

     int odr_total(ODR o);

returns the number of bytes allocated on the stream since the last call to odr_reset().

The memory subsystem of ODR is fairly efficient at allocating and releasing little bits of memory. Rather than managing the individual, small bits of space, the system maintains a free-list of larger chunks of memory, which are handed out in small bits. This scheme is generally known as a nibble memory system. It is very useful for maintaining short-lived constructions such as protocol PDUs.

If you want to retain a bit of memory beyond the next call to odr_reset(), you can use the function

     ODR_MEM odr_extract_mem(ODR o);

This function will give you control of the memory recently allocated on the ODR stream. The memory will live (past calls to odr_reset()), until you call the function

     void odr_release_mem(ODR_MEM p);

The opaque ODR_MEM handle has no other purpose than referencing the memory block for you until you want to release it.

You can use odr_extract_mem() repeatedly between allocating data, to retain individual control of separate chunks of data.

Encoding and Decoding Data

When encoding data, the ODR stream will write the encoded octet string in an internal buffer. To retrieve the data, use the function

     char *odr_getbuf(ODR o, int *len, int *size);

The integer pointed to by len is set to the length of the encoded data, and a pointer to that data is returned. *size is set to the size of the buffer (unless size is null, signaling that you are not interested in the size). The next call to a primitive function using the same ODR stream will overwrite the data, unless a different buffer has been supplied using the call

     void odr_setbuf(ODR o, char *buf, int len, int can_grow);

which sets the encoding (or decoding) buffer used by o to buf, using the length len. Before a call to an encoding function, you can use odr_setbuf() to provide the stream with an encoding buffer of sufficient size (length). The can_grow parameter tells the encoding ODR stream whether it is allowed to use realloc(2) to increase the size of the buffer when necessary. The default condition of a new encoding stream is equivalent to the results of calling

     odr_setbuf(stream, 0, 0, 1);

In this case, the stream will allocate and reallocate memory as necessary. The stream reallocates memory by repeatedly doubling the size of the buffer - the result is that the buffer will typically reach its maximum, working size with only a small number of reallocation operations. The memory is freed by the stream when the latter is destroyed, unless it was assigned by the user with the can_grow parameter set to zero (in this case, you are expected to retain control of the memory yourself).

To assume full control of an encoded buffer, you must first call odr_getbuf() to fetch the buffer and its length. Next, you should call odr_setbuf() to provide a different buffer (or a null pointer) to the stream. In the simplest case, you will reuse the same buffer over and over again, and you will just need to call odr_getbuf() after each encoding operation to get the length and address of the buffer. Note that the stream may reallocate the buffer during an encoding operation, so it is necessary to retrieve the correct address after each encoding operation.

It is important to realize that the ODR stream will not release this memory when you call odr_reset(): It will merely update its internal pointers to prepare for the encoding of a new data value. When the stream is released by the odr_destroy() function, the memory given to it by odr_setbuf will be released only if the can_grow parameter to odr_setbuf() was nonzero. The can_grow parameter, in other words, is a way of signaling who is to own the buffer, you or the ODR stream. If you never call odr_setbuf() on your encoding stream, which is typically the case, the buffer allocated by the stream will belong to the stream by default.

When you wish to decode data, you should first call odr_setbuf(), to tell the decoding stream where to find the encoded data, and how long the buffer is (the can_grow parameter is ignored by a decoding stream). After this, you can call the function corresponding to the data you wish to decode (eg, odr_integer() odr z_APDU()).

Examples of encoding/decoding functions:

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

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

If the data is absent (or doesn't match the tag corresponding to the type), the return value will be either 0 or 1 depending on the optional flag. If optional is 0 and the data is absent, an error flag will be raised in the stream, and you'll need to call odr_reset() before you can use the stream again. If optional is nonzero, the pointer pointed to/ by p will be set to the null value, and the function will return 1. The name argument is used to pretty-print the tag in question. It may be set to NULL if pretty-printing is not desired.

If the data value is found where it's expected, the pointer pointed to by the p argument will be set to point to the decoded type. The space for the type will be allocated and owned by the ODR stream, and it will live until you call odr_reset() on the stream. You cannot use free(2) to release the memory. You can decode several data elements (by repeated calls to odr_setbuf() and your decoding function), and new memory will be allocated each time. When you do call odr_reset(), everything decoded since the last call to odr_reset() will be released.

The use of the double indirection can be a little confusing at first (its purpose will become clear later on, hopefully), so an example is in order. We'll encode an integer value, and immediately decode it again using a different stream. A useless, but informative operation.

void do_nothing_useful(int value)
    ODR encode, decode;
    int *valp, *resvalp;
    char *bufferp;
    int len;
    /* allocate streams */
    if (!(encode = odr_createmem(ODR_ENCODE)))
    if (!(decode = odr_createmem(ODR_DECODE)))

    valp = &value;
    if (odr_integer(encode, &valp, 0, 0) == 0)
        printf("encoding went bad\n");
    bufferp = odr_getbuf(encode, &len);
    printf("length of encoded data is %d\n", len);

    /* now let's decode the thing again */
    odr_setbuf(decode, bufferp, len);
    if (odr_integer(decode, &resvalp, 0, 0) == 0)
        printf("decoding went bad\n");
    printf("the value is %d\n", *resvalp);

    /* clean up */

This looks like a lot of work, offhand. In practice, the ODR streams will typically be allocated once, in the beginning of your program (or at the beginning of a new network session), and the encoding and decoding will only take place in a few, isolated places in your program, so the overhead is quite manageable.

Summary and Synopsis

     #include <odr.h>

     ODR odr_createmem(int direction);

     void odr_destroy(ODR o);

     void odr_reset(ODR o);

     char *odr_getbuf(ODR o, int *len);

     void odr_setbuf(ODR o, char *buf, int len);

     void *odr_malloc(ODR o, int size);

     ODR_MEM odr_extract_mem(ODR o);

     void odr_release_mem(ODR_MEM r);

     int odr_geterror(ODR o);

     void odr_perror(char *message);

     extern char *odr_errlist[];