Conceptually, the ODR stream is the source of encoded data in the decoding mode; when encoding, it is the receptacle for the encoded data. Before you can use an ODR stream it must be allocated. This is done with the function
ODR odr_createmem(int direction);
The odr_createmem()
function takes as argument one
of three manifest constants: ODR_ENCODE
,
ODR_DECODE
, or ODR_PRINT
.
An ODR stream can be in only one mode - it is not possible to change
its mode once it's selected. Typically, your program will allocate
at least two ODR streams - one for decoding, and one for encoding.
When you're done with the stream, you can use
void odr_destroy(ODR o);
to release the resources allocated for the stream.
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, size_t 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);
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
size_t 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.
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()
).
Example 8.1. Encoding and decoding functions
int odr_integer(ODR o, Odr_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.
Example 8.2. Encoding and decoding of an integer
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(Odr_int value) { ODR encode, decode; Odr_int *valp, *resvalp; char *bufferp; int len; /* allocate streams */ if (!(encode = odr_createmem(ODR_ENCODE))) return; if (!(decode = odr_createmem(ODR_DECODE))) return; valp = &value; if (odr_integer(encode, &valp, 0, 0) == 0) { printf("encoding went bad\n"); return; } bufferp = odr_getbuf(encode, &len, 0); printf("length of encoded data is %d\n", len); /* now let's decode the thing again */ odr_setbuf(decode, bufferp, len, 0); if (odr_integer(decode, &resvalp, 0, 0) == 0) { printf("decoding went bad\n"); return; } /* ODR_INT_PRINTF format for printf (such as %d) */ printf("the value is " ODR_INT_PRINTF "\n", *resvalp); /* clean up */ odr_destroy(encode); odr_destroy(decode); }
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.
When an ODR stream is created of type ODR_PRINT
the ODR module will print the contents of a PDU in a readable format.
By default output is written to the stderr
stream.
This behavior can be changed, however, by calling the function
odr_setprint(ODR o, FILE *file);
before encoders or decoders are being invoked. It is also possible to direct the output to a buffer (of indeed another file), by using the more generic mechanism:
void odr_set_stream(ODR o, void *handle, void (*stream_write)(ODR o, void *handle, int type, const char *buf, int len), void (*stream_close)(void *handle));
Here the user provides an opaque handle and two handlers,
stream_write
for writing,
and stream_close
which is supposed
to close/free resources associated with handle.
The stream_close
handler is optional and
if NULL for the function is provided, it will not be invoked.
The stream_write
takes the ODR handle
as parameter, the user defined handle, a type
ODR_OCTETSTRING
, ODR_VISIBLESTRING
which indicates the type of contents is being written.
Another utility useful for diagnostics (error handling) or as part of the printing facilities is:
const char **odr_get_element_path(ODR o);
which returns a list of current elements that ODR deals with at the
moment. For the returned array, say ar
,
ar[0]
is the top level element,
ar[n]
is the last. The last element has the
property that ar[n+1] == NULL
.
Example 8.3. Element Path for record
For a database record part of a PresentResponse the
array returned by odr_get_element
is presentResponse
, databaseOrSurDiagnostics
, ?
, record
, ?
, databaseRecord
. The question mark appears due to
unnamed constructions.
The encoding/decoding functions all return 0 when an error occurs.
Until you call odr_reset()
, you cannot use the
stream again, and any function called will immediately return 0.
To provide information to the programmer or administrator, the function
void odr_perror(ODR o, char *message);
is provided, which prints the message
argument to
stderr
along with an error message from the stream.
You can also use the function
int odr_geterror(ODR o);
to get the current error number from the screen. The number will be one of these constants:
Table 8.1. ODR Error codes
code | Description |
---|---|
OMEMORY | Memory allocation failed. |
OSYSERR | A system- or library call has failed.
The standard diagnostic variable errno should be
examined to determine the actual error. |
OSPACE | No more space for encoding. This will only occur when the user has explicitly provided a buffer for an encoding stream without allowing the system to allocate more space. |
OREQUIRED | This is a common protocol error; A required data element was missing during encoding or decoding. |
OUNEXPECTED | An unexpected data element was found during decoding. |
OOTHER | Other error. This is typically an indication of misuse of the ODR system by the programmer, and also that the diagnostic system isn't as good as it should be, yet. |
The character string array
char *odr_errlist[]
can be indexed by the error code to obtain a human-readable representation of the problem.
#include <yaz/odr.h> ODR odr_createmem(int direction); void odr_destroy(ODR o); void odr_reset(ODR o); char *odr_getbuf(ODR o, int *len, int *size); void odr_setbuf(ODR o, char *buf, int len, int can_grow); void *odr_malloc(ODR o, int size); NMEM odr_extract_mem(ODR o); int odr_geterror(ODR o); void odr_perror(ODR o, const char *message); extern char *odr_errlist[];