Generated C++ Decode Method Format and Calling Parameters

Generated decode functions are invoked through the class interface by calling the base class Decode method. The calling sequence for this method is as follows:

   status = <object>.Decode ();

In this definition, <object> is an instance of the control class (i.e., ASN1C_<prodName>) generated for the given production

An ASN1BERDecodeBuffer object reference is a required argument to the <object> constructor. This is where the message start address and length are specified

The message length argument is used to specify the size of the message, if it is known. In ASN.1 BER or DER encoded messages, the overall length of the message is embedded in the first few bytes of the message, so this variable is not required. It is used as a test mechanism to determine if a corrupt or partial message was received. If the parsed message length is greater than this value, an error is returned. If the value is specified to be zero (the default), then this test is bypassed.

The function result variable status returns the status of the decode operation. The return status will be zero if decoding is successful or a negative value if an error occurs. Return status values are defined in Appendix A of the C/ C++ Common Functions Reference Manual and online in the asn1type.h include file.

Procedure for Using the C++ Control Class Decode Method

Normally when a message is received and read into a buffer, it can be one of several different message types. So the first job a programmer has before calling a decode function is determining which function to call. The ASN1BERDecodeBuffer class has a standard method for parsing the initial tag/length from a message to determine the type of message received. This call is used in conjunction with a switch statement on generated tag constants for the known message set in order to pick a decoder to call.

Once it is known which type of message has been received, an instance of a generated message class can be instantiated and the decode function called. The start of message pointer and message length (if known) must be specified either in the constructor call or in the call to the decode function itself.

A program fragment that could be used to decode an employee record is as follows:

   #include employee.h             // include file generated by ASN1C

   main ()
   {
      OSOCTET   msgbuf[1024];
      ASN1TAG   msgtag;
      int       msglen, status;

      .. logic to read message into msgbuf ..

      // Use the ASN1BERDecodeBuffer class to parse the initial
      // tag/length from the message..

      ASN1BERDecodeBuffer decodeBuffer (msgbuf, len);

      status = decodeBuffer.ParseTagLen (msgtag, msglen);

      if (status != 0) {
         // handle error
         ...
      }

      // Now switch on initial tag value to determine what type of
      // message was received..

      switch (msgtag)
      {
         case TV_PersonnelRecord: // compiler generated constant
         {
            ASN1T_PersonnelRecord msgData;
            ASN1C_PersonnelRecord employee (decodeBuffer, msgData);

            if ((status = employee.Decode ()) == 0)
            {
               // decoding successful, data in msgData

               process received data..
            }
            else
               error processing...
         }

         case TV_ ...// handle other known messages

Note that the call to free memory is not required to release dynamic memory when using the C++ interface. This is because the control class hides all of the details of managing the context and releasing dynamic memory. The memory is automatically released when both the message buffer object (ASN1BERMessageBuffer) and the control class object (ASN1C_<ProdName>) are deleted or go out of scope. Reference counting of a context variable shared by both interfaces is used to accomplish this.

Decoding a Series of Messages Using the C++ Control Class Interface

The above example is fine as a sample for decoding a single message, but what happens in the more typical scenario of having a long-running loop that continuously decodes messages? The logic shown above would not be optimal from a performance standpoint because of the constant creation and destruction of the message processing objects. It would be much better to create all of the required objects outside of the loop and then reuse them to decode and process each message.

A code fragment showing a way to do this is as follows:

   #include employee.h             // include file generated by ASN1C

   main ()
   {
      OSOCTET  msgbuf[1024];
      ASN1TAG  msgtag;
      int      msglen, status;

      // Create message buffer, ASN1T, and ASN1C objects

      ASN1BERDecodeBuffer decodeBuffer (msgbuf, len);
      ASN1T_PersonnelRecord employeeData;
      ASN1C_PersonnelRecord employee (decodeBuffer, employeeData);

      for (;;) {

         .. logic to read message into msgbuf ..

         status = decodeBuffer.ParseTagLen (msgtag, msglen);

         if (status != 0) {
            // handle error
            ...
         }

      // Now switch on initial tag value to determine what type of
      // message was received..

      switch (msgtag)
      {
         case TV_PersonnelRecord: // compiler generated constant
         {
            if ((status = employee.Decode ()) == 0)
            {
               // decoding successful, data in employeeData

               process received data..
            }
            else
               error processing...
         }
         break;

         default:
            // handle unknown message type here

            } // switch

            // Need to reinitialize objects for next iteration

            if (!isLastIteration) employee.memFreeAll ();

         } // end of loop

This is quite similar to the first example. Note that we have pulled the ASN1T_Employee and ASN1C_Employee object creation logic out of the switch statement and moved it above the loop. These objects can now be reused to process each received message.

The only other change was the call to employee.memFreeAll that was added at the bottom of the loop. Since we can’t count on the objects being deleted to automatically release allocated memory, we need to do it manually. This call will free all memory held within the decoding context. This will allow the loop to start again with no outstanding memory allocations for the next pass.

If the buffer already contains multiple BER messages encoded back-to-back then it is necessary to modify the buffer pointer in each iteration. The getByteIndex method should be used at the end of loop to get the current offset in the buffer. This offset should be used with the decode buffer object’s setBuffer method call at the beginning of the loop to determine the correct buffer pointer and length:

   OSUINT32 offset = 0;
   for ( ; offset < msglen;) {

      // set buffer pointer and its length to decode

      decodeBuffer.setBuffer (&msgbuf[offset], msglen - offset);
      int curlen = (int)(msglen - offset);

      status = decodeBuffer.ParseTagLen (msgtag, curlen);

      if (status != 0) {
         // handle error
         ...
      }

      // Now switch on initial tag value to determine what type of
      // message was received..

      switch (msgtag)
      {
         case TV_PersonnelRecord: // compiler generated constant
         {
            if ((status = employee.Decode ()) == 0)
            {
               // decoding successful, data in employeeData

               process received data..
            }
            else
               error processing...
         }
         break;

         default:
         // handle unknown message type here
      
      } // switch

      // get new offset
      offset += decodeBuffer.getByteIndex ();

      // Need to reinitialize objects for next iteration (if it is not
      // last iteration)

      if (offset < msglen) employee.memFreeAll ();
   } // end of loop