How to Use It

To define event handlers, two things must be done:

  1. One or more new classes must be derived from the Asn1NamedEventHandler and/or the Asn1ErrorHandler base classes. All pure virtual methods must be implemented.

  2. Objects of these classes must be created and registered prior to calling the generated decode method or function.

The best way to illustrate this procedure is through examples. We will first show a simple event handler application to provide a customized formatted printout of the fields in a PER message. Then we will show a simple error handler that will ignore unrecognized fields in a BER message.

Example 1: A Formatted Print Handler

The ASN1C evaluation and distribution kits include a sample program for doing a formatted print of parsed data. This code can be found in the cpp/sample_per/eventHandler directory. Parts of the code will be reproduced here for reference, but refer to this directory to see the full implementation.

The format for the printout will be simple. Each element name will be printed followed by an equal sign (=) and an open brace ({) and newline. The value will then be printed followed by another newline. Finally, a closing brace (}) followed by another newline will terminate the printing of the element. An indentation count will be maintained to allow for a properly indented printout.

A header file must first be created to hold our print handler class definition (or the definition could be added to an existing header file). This file will contain a class derived from the Asn1NamedEventHandler base class as follows:

   class PrintHandler : public Asn1NamedEventHandler {
     protected:
        const char* mVarName;
        int mIndentSpaces;
     public:
        PrintHandler (const char* varName);
        ~PrintHandler ();
        void indent ();
        virtual void startElement (const char* name, int index = -1);
        virtual void endElement (const char* name, int index = -1);
        virtual void boolValue (OSBOOL value);

        ... other virtual contents method declarations
   }

In this definition, we chose to add the mVarName and mIndentSpaces member variables to keep track of these items. The user is free to add any type of member variables he or she wants. The only firm requirement in defining this derived class is the implementation of the virtual methods.

We implement these virtual methods as follows:

In startElement, we print the name, equal sign, and opening brace:

   void PrintHandler::startElement (const char* name, int index)
   {
      indent();
      printf (“%s = {\n”, name);
      mIndentLevel++;
   }

In this simplified implementation, we simply indent (this is another private method within the class) and print out the name, equal sign, and opening brace. We then increment the indent level. Note that this is a highly simplified form. We don’t even bother to check if the index argument is greater than or equal to zero. This would determine if a ‘[x]’ should be appended to the element name. In the sample program that is included with the compiler distribution, the implementation is complete.

In endElement, we simply terminate our brace block as follows:

   void PrintHandler::endElement (const char* name, int index)
   {
      mIndentLevel--;
      indent();
      printf (“}\n”);
   }

Next, we need to create an object of our derived class and register it prior to invoking the decode method. In the reader.cpp program, the following lines do this:

   // Create and register an event handler object

   PrintHandler* pHandler = new PrintHandler ("employee");
   decodeBuffer.addEventHandler (pHandler);

The addEventHandler method defined in the Asn1MessageBuffer base class is the mechanism used to do this. Note that event handler objects can be stacked. Several can be registered before invoking the decode function. When this is done, the entire list of event handler objects is iterated through and the appropriate event handling callback function invoked whenever a defined event is encountered.

The implementation is now complete. The program can now be compiled and run. When this is done, the resulting output is as follows:

   employee = {
      name = {
         givenName = {
            "John"
         }
         initial = {
            "P"
         }
         familyName = {
            "Smith"
         }
      }
      ...

This can certainly be improved. For one thing it can be changed to print primitive values out in a “name = value” format (i.e., without the braces). But this should provide the general idea of how it is done.

Example 2: An Error Handler

Despite the addition of things like extensibility and version brackets, ASN.1 implementations get out-of-sync. For situations such as this, the user needs some way to intervene in the parsing process to set things straight. This is faulttolerance – the ability to recover from certain types of errors.

The error handler interface is provided for this purpose. The concept is simple. Instead of throwing an exception and immediately terminating the parsing process, a user defined callback function is first invoked to allow the user to check the error. If the user can fix the error, all he or she needs to do is apply the appropriate patch and return a status of 0. The parser will be none the wiser. It will continue on thinking everything is fine.

This interface is probably best suited for recovering from errors in BER or DER instead of PER. The reason is the TLV format of BER makes it relatively easy to skip an element and continue on. It is much more difficult to find these boundaries in PER.

Our example can be found in the cpp/sample_ber/errorHandler subdirectory. In this example, we have purposely added a bogus element to one of the constructs within an encoded employee record. The error handler will be invoked when this element is encountered. Our recovery action will simply be to print out a warning message, skip the element, and continue.

As before, the first step is to create a class derived from the Asn1ErrorHandler base class. This class is as follows:

   class MyErrorHandler : public Asn1ErrorHandler {
     public:

        // The error handler callback method. This is the method
        // that the user must override to provide customized
        // error handling..

        virtual int error (OSCTXT* pCtxt, ASN1CCB* pCCB, int stat);
   } ;

Simple enough. All we are doing is providing an implementation of the error method.

Implementing the error method requires some knowledge of the run-time internals. In most cases, it will be necessary to somehow alter the decoding buffer pointer so that the same field isn’t looked at again. If this isn’t done, an infinite loop can occur as the parser encounters the same error condition over and over again. The run-time functions xd_NextElement or xd_OpenType might be useful in the endeavor as they provide a way to skip the current element and move on to the next item.

Our sample handler corrects the error in which an unknown element is encountered within a SET construct. This will cause the error status ASN_E_NOTINSET to be generated. When the error handler sees this status, it prints information on the error that was encountered to the console, skips to the next element, and then returns an 0 status that allows the decoder to continue. If some other error occurred (i.e., status was not equal to ASN_E_NOTINSET), then the original status is passed out which forces the termination of the decoding process.

The full text of the handler is as follows:

   int MyErrorHandler::error (OSCTXT* pCtxt, ASN1CCB* pCCB, int stat)
   {

      // This handler is set up to look explicitly for ASN_E_NOTINSET
      // errors because we know the SET might contain some bogus elements..

      if (stat == ASN_E_NOTINSET) {
         // Print information on the error that was encountered

         printf ("decode error detected:\n");
         rtErrPrint (pCtxt);
         printf ("\n");

         // Skip element

         xd_NextElement (pCtxt);

         // Return an OK status to indicate parsing can continue

         return 0;
      }

      else return stat; // pass existing status back out
   }

Now we need to register the handler. Unlike event handlers, only a single error handler can be registered. The method to do this in the message buffer class is setErrorHandler. The following two lines of code in the reader program register the handler:

   MyErrorHandler errorHandler;

   decodeBuffer.setErrorHandler (&errorHandler);

The error handlers can be as complicated as you need them to be. You can use them in conjunction with event handlers in order to figure out where you are within a message in order to look for a specific error at a specific place. Or you can be very generic and try to continue no matter what.

Example 3: A Pure Parser to Convert PER-encoded Data to XML

A pure-parser is created by using the -notypes option along with the -events option. In this case, no backing data types to hold decoded data are generated. Instead, parsing functions are generated that store the data internally within local variables inside the parsing functions. This data is dispatched to the callback functions and immeditely disposed of upon return from the function. It is up to the user to decide inside the callback handler what they want to keep and they must make copies at that time. The result is a very fast and low-memory consuming parser that is ideal for parsing messages in which only select parts of the messages are of interest.

Another use case for pure-parser functions is validation. These functions can be used to determine if a PER message is valid without going through the high overhead operation of decoding. They can be used on the front-end of an application to reject invalid messages before processing of the messages is done. In some cases, this can result in significantly increased performance.

An example of a pure-parser can be found in the cpp/sample_per/per2xmlEH directory. This program uses a pure-parser to convert PER-encoded data into XML. The steps in creating an event handler are the same as in Example 1 above. An implementation of the Asn1NamedEventHandler interface must be created. This is done in the xmlHandler.h and xmlHandler.cpp files. A detailed discussion of this code will not be provided here. What it does in a nutshell is adds the angle brackets (< >) around the element names in the startElement and endElement callbacks. The data callbacks simply output a textual representation of the data as they do in the print handler case.

The only difference in reader.cpp from the other examples is that:

  1. There is no declaration of an employee variable to hold decoded data because no type for this variable was generated, and

  2. The Parse method is invoked instead of the Decode method. This is the generated method definition for a pureparser. If one examines the generated class definitions, they will see that no Encode or Decode methods were generated.

Compiling and running this program will show the encoded PER message written to stdout as an XML message. The resulting message is also saved in the message.xml file.