Generated C Function Format and Calling Parameters

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.

Procedure for Calling C Encode Functions

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...
   }

Encoding a Series of Messages Using the C Encode Functions

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.