The format of the name of each generated encode function is as follows:
asn1E_[<prefix>]<prodName>
where <prodName>
is the name of the ASN.1 production for which the
function is being generated and <prefix>
is an optional prefix that can
be set via a configuration file setting. The configuration setting used to set the
prefix is the <typePrefix> element. This element specifies a prefix that will be
applied to all generated typedef names and function names for the production.
The calling sequence for each encode function is as follows:
len = asn1E_<name> (OSCTXT* pctxt, <name>* pvalue, ASN1TagType tagging);
In this definition, <name>
denotes the prefixed production name
defined above.
The pctxt
argument is used to hold a context pointer to keep track of
encode parameters. This is a basic "handle" variable that is used to make the function
reentrant so it can be used in an asynchronous or threaded application. The user is
required to supply a pointer to a variable of this type declared somewhere in his or her
program. The variable should be initialized using the rtInitContext run-time library function (see the C/C++ Common Run-Time Library Reference Manual for a complete description
of this function).
The pvalue
argument holds a pointer to the data to be encoded and is of
the type generated from the ASN.1 production.
The tagging
argument is for internal use when calls to encode functions
are nested to accomplish encoding of complex variables. It indicates whether the tag
associated with the production should be applied or not (implicit versus explicit
tagging). At the top level, the tag should always be applied so this parameter should
always be set to the constant ASN1EXPL (for EXPLICIT).
The function result variable len
returns the length of the data actually
encoded or an error status code if encoding fails. Error status codes are negative to
tell them apart from length values. Return status values are defined in the
asn1type.h
include file.
This section describes the step-by-step procedure for calling a C BER or DER encode function. This method must be used if C code generation was done. This method can also be used as an alternative to using the control class interface if C++ code generation was done. Note that the procedures described here cannot be used if stream-based encoding is to be done (specified by the use of the -stream ASN1C command-line option). In this case, the procedures described in the Generated BER Streaming Encode Functions section.
Before any encode function can be called; the user must first initialize an encoding context. This is a variable of type OSCTXT. This variable holds all of the working data used during the encoding of a message. The context variable is declared as a normal automatic variable within the top-level calling function. It must be initialized before use. This can be accomplished by using the rtInitContext function as follows:
OSCTXT ctxt; if (rtInitContext (&ctxt) != 0) { /* initialization failed, could be a license problem */ printf (“context initialization failed (check license)\n”); return –1; }
The next step is to specify an encode buffer into which the message will be encoded. This is accomplished by calling the xe_setp run-time function. The user can either pass the address of a buffer and size allocated in his or her program (referred to as a static buffer), or set these parameters to zero and let the encode function manage the buffer memory allocation (referred to as a dynamic buffer). Better performance can normally be attained by using a static buffer because this eliminates the high-overhead operation of allocating and reallocating memory.
After initializing the context and populating a variable of the structure to be
encoded, an encode function can be called to encode the message. If the return status
indicates success (positive length value), the run-time library function
xe_getp
can be called to obtain the start address of the encoded
message. Note that the returned address is not the start address of the target
buffer. BER encoded messages are constructed from back to front (i.e., starting at
the end of the buffer and working backwards) so the start point will fall somewhere
in the middle of the buffer after encoding is complete. This is illustrated in the
following diagram:
In this example, a 1K encode buffer is declared which happens to start at address
0x100. When the context is initialized with a pointer to this buffer and size equal
to 1K, it positions the internal encode pointer to the end of the buffer (address
0x500). Encoding then proceeds from back-to-front until encoding of the message is
complete. In this case, the encoded message turned out to be 0x300 (768) bytes in
length and the start address fell at 0x200. This is the value that would be returned
by the xe_getp
function.
A program fragment that could be used to encode an employee record is as follows:
#include employee.h /* include file generated by ASN1C */ int main () { OSOCTET msgbuf[1024], *msgptr; int msglen; OSCTXT ctxt; Employee employee; /* typedef generated by ASN1C */ /* Step 1: Initialize the context and set the buffer pointer */ if (rtInitContext (&ctxt) != 0) { /* initialization failed, could be a license problem */ printf (“context initialization failed (check license)\n”); return –1; } xe_setp (&ctxt, msgbuf, sizeof(msgbuf)); /* Step 2: Populate the structure to be encoded */ employee.name.givenName = "SMITH"; ... /* Step 3: Call the generated encode function */ msglen = asn1E_Employee (&ctxt, &employee, ASN1EXPL); /* Step 4: Check the return status (note: the test is * > 0 because the returned value is the length of the * encoded message component).. */ if (msglen > 0) { /* Step 5: If encoding is successful, call xe_getp to * fetch a pointer to the start of the encoded message. */ msgptr = xe_getp (&ctxt); ... } else { rtxErrPrint (&ctxt); return msglen; } }
In general, static buffers should be used for encoding messages where possible as they offer a substantial performance benefit over dynamic buffer allocation. The problem with static buffers, however, is that you are required to estimate in advance the approximate size of the messages you will be encoding. There is no built-in formula to do this; the size of an ASN.1 message can vary widely based on data types and the number of tags required.
If performance is not a significant issue, then dynamic buffer allocation is a good alternative. Setting the buffer pointer argument to NULL in the call to xe_setp specifies dynamic allocation. This tells the encoding functions to allocate a buffer dynamically. The address of the start of the message is obtained as before by calling xe_getp. Note that this is not the start of the allocated memory; that is maintained within the context structure. To free the memory, either the rtxMemFree function may be used to free all memory held by the context or the xe_free function used to free the encode buffer only.
The following code fragment illustrates encoding using a dynamic buffer:
#include employee.h /* include file generated by ASN1C */ main () { OSOCTET* msgptr; int msglen; OSCTXT ctxt; Employee employee; /* typedef generated by ASN1C */ if (rtInitContext (&ctxt) != 0) { /* initialization failed, could be a license problem */ printf (“context initialization failed (check license)\n”); return –1; } xe_setp (&ctxt, NULL, 0); employee.name.givenName = "SMITH"; ... msglen = asn1E_Employee (&ctxt, &employee, ASN1EXPL); if (msglen > 0) { msgptr = xe_getp (&ctxt); ... rtxMemFree (&ctxt); /* don’t call free (msgptr); !!! */ } else error processing... }
A common application of BER encoding is the repetitive encoding of a series of the same type of message over and over again. For example, a TAP3 batch application might read billing data out of a database table and encode each of the records for a batch transmission.
If a user was to repeatedly allocate/free memory and reinitialize the C objects involved in the encoding of a message, performance would suffer. This is not necessary however, because the C objects and memory heap can be reused to allow multiple messages to be encoded. As example showing how to do this is as follows:
#include employee.h /* include file generated by ASN1C */ main () { const OSOCTET* msgptr; OSOCTET msgbuf[1024]; int msglen; OSCTXT ctxt; PersonnelRecord data; /* Init context structure */ if ((stat = rtInitContext (&ctxt)) != 0) { printf ("rtInitContext failed; stat = %d\n", stat); return -1; } /* Encode loop starts here, this will repeatedly use the * objects declared above to encode the messages */ for (;;) { xe_setp (&ctxt, msgbuf, sizeof(msgbuf)); /* logic here to read record from some source (database, * flat file, socket, etc.).. */ /* populate structure with data to be encoded */ data.name = “SMITH”; ... /* call encode function */ if ((msglen = asn1E_PersonnelRecord (&ctxt, &data, ASN1EXPL)) > 0) { /* encoding successful, get pointer to start of message */ msgptr = xe_getp (&ctxt); /* do something with the encoded message */ ... } else error processing... /* Call rtxMemReset to reset the memory heap for the next * iteration. Note, all data allocated by rtxMemAlloc will * become invalid after this call. */ rtxMemReset (&ctxt); } rtFreeContext (&ctxt); }
The rtxMemReset call does not free memory; instead, it marks it as empty so that it may be reused in the next iteration. Thus, all memory allocated by rtxMemAlloc will be overwritten and data will be lost.