TOC PREV NEXT INDEX


Generated Information Object Table Structures



Information Objects and Classes are used to define multi-layer protocols in which "holes" are defined within ASN.1 types for passing message components to different layers for processing. These items are also used to define the contents of various messages that are allowed in a particular exchange of messages. The ASN1C compiler extracts the types involved in these message exchanges and generates encoders/decoders for them. The "holes" in the types are accounted for by adding open type holders to the generated structures. These open type holders consist of a byte count and pointer for storing information on an encoded message fragment for processing at the next level.

The ASN1C compiler is capable of generating code in one of two forms for information in an object specification:

1. Simple form: in this form, references to variable type fields within standard types are simply treated as open types and an open type placeholder is inserted.

2. Table form: in this form, all of the classes, objects, and object sets within a specification result in the generation of code for parsing and formatting the information field references within standard type structures.

The second form is selected by specifying the -tables command line option.

To better understand the support in this area, the individual components of Information Object specifications are examined. We begin with the "CLASS" specification that provides a schema for Information Object definitions. A sample class specification is as follows:

      OPERATION ::= CLASS {
 
         &operationCode         CHOICE { local  INTEGER, 
 
                                         global OBJECT IDENTIFIER }
 
         &ArgumentType,
 
         &ResultType,
 
         &Errors                ERROR       OPTIONAL
 
      }
 

Users familiar with ASN.1 will recognize this as a simplified definition of the ROSE OPERATION MACRO using the Information Object format. When a class specification such as this is parsed, information on its fields is maintained in memory for later reference. In the simple form of code generation, the class definition itself does not result in the generation of any corresponding C or C++ code. It is only an abstract template that will be used to define new items later on in the specification. In the table form, if C++ is specified, an abstract base class is generated off of which other classes are derived for information object specifications.

Fields from within the class can be referenced in standard ASN.1 types. It is these types of references that the compiler is mainly concerned with. These are typically "header" types that are used to add a common header to a variety of other message body types. An example would be the following ASN.1 type definition for a ROSE invoke message header:

	Invoke ::= SEQUENCE {
 
	   invokeID     INTEGER,
 
	   opcode       OPERATION.&operationCode,
 
	   argument     OPERATION.&ArgumentType
 
	}
 

This is a very simple case that purposely omits a lot of additional information such as Information Object Set constraints that are typically a part of definitions such as this. The reason this information is not present is because we are just interested in showing the items that the compiler is concerned with. We will use this type to demonstrate the simple form of code generation. We will then add table constraints and discuss what changes when the -tables command line options is used.

The opcode field within this definition is an example of a fixed type field reference. It is known as this because if you go back to the original class specification, you will see that operationCode is defined to be of a specific type (namely a choice between a local and global value). The generated typedef for this field will contain a reference to the type from the class definition.

The argument field is an example of a variable type field.. In this case, if you refer back to the class definition, you will see that no type is provided. This means that this field can contain an instance of any encoded type (note: in practice, table constraints can be used with Information Object Sets to limit the message types that can be placed in this field). The generated typedef for this field contains an "open type" (ASN1OpenType) reference to hold a previously encoded component to be specified in the final message.

Simple Form Code Generation

In the simple form of information object code generation, the Invoke type above would result in the following C or C++ typedefs being generated:

      typedef struct Invoke ::= SEQUENCE {
 
         OSINT32        invokeID;
 
         OPERATION_operationCode opcode;
 
         ASN1OpenType   argument;
 
      }
 

The following would be the procedure to add the Invoke header type to an ASN.1 message body:


In this case, the amount of code generated to support the information object references is minimal. The amount of coding required by a user to encode or decode the variable type field elements, however, can be rather large. This is a tradeoff that exists between using the compiler generated table constraints solution (as we will see below) and using the simple form.

Table Form Code Generation

If we now add table constraints to our original type definition, it might look as follows:

     Invoke ::= SEQUENCE {
 
       invokeID   INTEGER,
 
       opcode     OPERATION.&operationCode ({My-ops}),
 
       argument   OPERATION.&ArgumentType ({My-ops}{@opcode})
 
     }
 

The "{My-ops}" constraint on the opcode element specifies an information object set (not shown) that constrains the element value to one of the values in the object set. The {My-ops}{@opcode} constraint on the argument element goes a step further - it ties the type of the field to the type specified in the row that matches the given opcode value.

ASN1C generates an in-memory table (either an array or a list of structures) for each of the items in the information object sets defined in a specification. In the example above, a table would be generated for the My-ops information object set. The code generated for the type would then use this table to verify that the given items in a structure that reference this table match the constraints.

The C or C++ type generated for the SEQUENCE above when -tables is specified would be as follows:

    typedef struct Invoke {
 
       OSINT32        invokeID;
 
       OPERATION_operationCode opcode;
 
       ASN1Object     argument;
 
    } Invoke;
 

This is almost identical to the type generated in the simple case. The difference is the ASN1Object type (or ASN1TObject for C++) that is used instead of ASN1OpenType. This type is defined in the asn1type.h run-time header file as follows:

    typedef struct ASN1Object {
 
       ASN1OpenType  encoded;
 
       void*         decoded;
 
       OSINT32       index;
 
    }
 

This holds the value to be encoded or decoded in both encoded or decoded form. The way a user uses this to encode a value of this type is as follows:


Note that in this case, the intermediate type does not need to be manually encoded by the user. The generated encoder has logic built-in to encode the complete message using the information in the generated tables.


Additional Code Generated with the -tables option

When the -tables command line option is used, additional code is generated to support the additional processing required to verify table constraints. This code varies depending on whether C or C++ code generation is selected. The C++ code is designed to take advantage of the object-oriented capabilities of C++. These capabilities are well suited for modeling the behavior of information objects in practice. The following subsections describe the code generated for each of these languages.

The code generated to support these constraints is intended for use only in compiler-generated code. Therefore, it is not necessary for the average user to understand the mappings in order to use the product. The information presented here is informative only to provide a better understanding of how the compiler handles table constraints.

C Code Generation

For C, code is generated for the Information Object Sets defined within a specification in the form of a global array of structures. Each structure in the array is an equivalent C structure representing the corresponding ASN.1 information object.

Additional encode and decode functions are also generated for each type that contains table constraints. These functions have the following prototypes:

BER/DER

int asn1ETC_<ProdName> (OSCTXT* pctxt, <ProdName>* pvalue);
 

 
int asn1DTC_<ProdName> (OSCTXT* pctxt, <ProdName>* pvalue);
 

 
PER

int asn1PETC_<ProdName> (OSCTXT* pctxt, <ProdName>* pvalue);
 

 
int asn1PDTC_<ProdName> (OSCTXT* pctxt, <ProdName>* pvalue);
 

The purpose of these functions is to verify the fixed values within the table constraints are what they should be and to encode or decode the open type fields using the encoder or decoder assigned to the given table row. Calls to these functions are automatically built into the standard encode or decode functions for the given type. They should be considered hidden functions not for use within an application that uses the API.

C++ Code Generation

For C++, code is generated for ASN.1 classes, information objects, and information object sets. This code is then referenced when table constraint processing must be performed.

Each of the generated C++ classes builds on each other. First, the classes generated that correspond to ASN.1 CLASS definitions form the base class foundation. Then C++ classes derived from these base classes corresponding to the information objects are generated. Finally, C++ singleton classes corresponding to the information object sets are generated. Each of these classes provides a container for a collection of C++ objects that make up the object set.

Additional encode and decode functions are also generated as they were in the C code generation case for interfacing with the object definitions above. These functions have the following prototypes:

BER/DER

int asn1ETC_<ProdName> (OSCTXT* pctxt, 
 
                        <ProdName>* pvalue, 
 
                        <ClassName>* pobject);
 

 
int asn1DTC_<ProdName> (OSCTXT* pctxt, 
 
                        <ProdName>* pvalue, 
 
                        <ClassName>* pobject);
 

PER

int asn1PETC_<ProdName> (OSCTXT* pctxt, 
 
                         <ProdName>* pvalue, 
 
                         <ClassName>* pobject);
 

 
int asn1PDTC_<ProdName> (OSCTXT* pctxt, 
 
                         <ProdName>* pvalue, 
 
                         <ClassName>* pobject);
 

These prototypes are identical to the prototypes generated in C code generation case except for the addition of the pobject argument. This argument is for a pointer to the information object that matches the key field value for a given encoding. These functions have different logic for processing Relative and Simple table constraints. The logic associated with each case is as follows:

On the encode side:

Relative Table Constraint:

Simple Table Constraint:


The normal encode logic is then performed to encode all of the standard and open type fields in the message.

On the decode side, the logic is reversed:

The normal decode logic is performed to populate the standard and open type fields in the generated structure.

Relative Table Constraint:

Simple Table Constraint:


General Procedure for Table Constraint Encoding

The general procedure to encode an ASN.1 message with table constraints is the same as without table constraints. The only difference is in the open type data population procedure. The -tables option will cause ASN1TObject fields to be inserted in the generated code instead of Asn1OpenType declarations.

Refer to the BER/DER/PER encoding procedure for further information.

The procedure to populate the value for an ASN1TObject item is as follows:

1. Check the ASN.1 specification or generated C code for the type of the type field value in the information object set that corresponds to the selected key field value.

2. Create a variable of that type and assign a pointer to it to the Asn1Object.decoded member variable as void*.

3. Follow the common BER/PER/DER encode procedure.

A complete example showing how to assign an open type value is as follows:

Test DEFINITIONS ::= BEGIN
 

 
   ATTRIBUTE ::= CLASS {
 
        &Type,
 
        &id             OBJECT IDENTIFIER UNIQUE }
 
   WITH SYNTAX {
 
        WITH SYNTAX &Type ID &id }
 

 
   name ATTRIBUTE  ::=  {
 
        WITH SYNTAX     VisibleString
 
        ID              { 0 1 1 } }
 

 
   name ATTRIBUTE  ::=  {
 
        WITH SYNTAX     INTEGER
 
        ID              { 0 1 2 } }
 

 
   SupportedAttributes  ATTRIBUTE  ::= { name | commonName }
 

 
   Invoke ::= SEQUENCE {
 
      opcode	   ATTRIBUTE.&id   ({SupportedAttributes}),
 
      argument 	ATTRIBUTE.&Type ({SupportedAttributes}{@opcode})
 
   }
 

 
END

In the above example, the Invoke type contains a table constraint. Its element opcode refers to the ATTRIBUTE id field and argument element refers to the ATTRIBUTE Type field. The opcode element is an index element for the Invoke type's table constraint. The argument element is an open type whose type is determined by the opcode value. In this example, opcode is the key field.

The opcode element can have only two possible values: { 0 1 1 } or { 0 1 2 }. If the opcode value is { 0 1 1} then argument will have a VisibleString value and if the opcode value is { 0 1 2 } then argument will have an INTEGER value. Any other value of the opcode element will be violation of the Table Constraint.

If the SupportedAttributes information object set was extensible (indicated by a ",..." at the end of the definition), then the argument element may have a value of a type that is not in the defined set. In this case, if the index element value is outside the information object set, then the argument element will be assumed to be an Asn1OpenType. The Invoke type encode function call will use the value from argument.encoded.data field (i.e. it will have to be pre-encoded because the encode function will not be able to determine from the table constraint how to encode it).

A C++ program fragment that could be used to encode an instance of the Invoke type is as follows:

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

 
    main ()
 
    {
 
        const OSOCTET* msgptr;
 
        OSOCTET msgbuf[1024];
 
        int       msglen;
 

 
        // step 1: construct ASN1C C++ generated class. 
 
        // this specifies a static encode message buffer
 

 
        ASN1BEREncodeBuffer encodeBuffer (msgbuf, sizeof(msgbuf));
 
 
 
        // step 2: populate msgData structure with data to be encoded
 

 
        ASN1T_Invoke msgData;
 
        ASN1C_Invoke invoke (encodeBuffer, msgData);
 

 
        msgData.opcode.numids = 3;
 
        msgData.opcode.subid[0] = 0;
 
        msgData.opcode.subid[1] = 1;
 
        msgData.opcode.subid[2] = 1;
 
        ASN1VisibleString argument = "objsys";
 
        msgData.argument.decoded = (void*) &argument;
 
        // note: opcode value is  {0 1 1 }, so argument must be 
 
        // ASN1VisibleString type
 

 
        // step 3: invoke Encode method 
 

 
        if ((msglen = invoke.Encode ()) > 0) {
 
           // encoding successful, get pointer to start of message
 
           msgptr = encodeBuffer.getMsgPtr();
 
        }
 
        else
 
          error processing...
 
    }
 

The encoding procedure for C requires one extra step. This is a call to the module initialization functions after context initialization is complete. All module initialization functions for all modules in the project must be invoked. The module initialization function definitions can be found in the <ModuleName>Table.h file.

The format of each module initialization function name is as follows:

void <ModuleName>_init (OSCTXT* pctxt)
 

Here ModuleName would be replaced with name of the module.

A C program fragment that could be used to encode the Invoke record defined above is as follows:

    #include TestTable.h         /* include file generated by ASN1C */
 

 
    int main ()
 
    {
 
        OSOCTET msgbuf[1024], *msgptr;
 
        int       msglen;
 
        OSCTXT  ctxt;
 
        Invoke    invoke;  	/* 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: call module initialization functions */
 

 
	  Test_init (&ctxt);
 

 
        /* Step 3: Populate the structure to be encoded */
 

 
        msgData.opcode.numids = 3;
 
        msgData.opcode.subid[0] = 0;
 
        msgData.opcode.subid[1] = 1;
 
        msgData.opcode.subid[2] = 1;
 
        //note: opcode value is  {0 1 1 }, so argument must be 
 
        //ASN1VisibleString type
 
        ASN1VisibleString argument = "objsys";
 
        msgData.argument.decoded = (void*) &argument;
 
        ...
 

 
        /* Step 4: Call the generated encode function */
 

 
        msglen = asn1E_Invoke (&ctxt, &invoke, ASN1EXPL);
 

 
        /* Step 5: 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 6: If encoding is successful, call xe_getp to 	*/
 
          /* fetch a pointer to the start of the encoded message.	*/
 

 
          msgptr = xe_getp (&ctxt);
 
          ...
 
        }
 
        else
 
          error processing...
 
    }
 



Objective Systems, Inc.

55 Dowlin Forge Road
Exton, Pennsylvania 19341
http://www.obj-sys.com
Phone: (484) 875-9841
Toll-free: (877) 307-6855 (US only)
Fax: (484) 875-9830
info@obj-sys.com
TOC PREV NEXT INDEX