Parameterized Types

The ASN1C compiler can parse parameterized type definitions and references as specified in the X.683 standard. These types allow dummy parameters to be declared that will be replaced with actual parameters when the type is referenced. This is similar to templates in C++.

A simple and common example of the use of parameterized types is for the declaration of an upper bound on a sized type as follows:

   SizedOctetString{INTEGER:ub} ::= OCTET STRING (SIZE (1..ub))

In this definition, ‘ub’ would be replaced with an actual value when the type is referenced. For example, a sized octet string with an upper bound of 32 would be declared as follows:

   OctetString32 ::= SizedOctetString{32}

The compiler would handle this in the same way as if the original type was declared to be an octet string of size 1 to 32. In the case of C#, this would result in size constraint checks being added to the generated encode and decode methods for the type.

Another common example of parameterization is the substitution of a given type inside a common container type. For example, security specifications frequently contain a ‘signed’ parameterized type that allows a digital signature to be applied to other types. An example of this would be as follows:

   SIGNED { ToBeSigned } ::= SEQUENCE {
      toBeSigned    ToBeSigned,
      algorithmOID  OBJECT IDENTIFIER,
      paramS        Params,
      signature     BIT STRING
   }

An example of a reference to this definition would be as follows:

   SignedName ::= SIGNED { Name }

where ‘Name’ would be another type defined elsewhere within the module.

ASN1C performs the substitution to create the proper C# class definition for SignedName:

   public class SignedName : Asn1Type {
     public Name toBeSigned;
     public Asn1ObjectIdentifier algorithmOID;
     public Params paramS;
     public Asn1BitString signature;
     ...
   }

When processing parameterized type definitions, ASN1C will first look to see if the parameters are actually used in the final generated code. If not, they will simply be discarded and the parameterized type converted to a normal type reference. For example, when used with information objects, parameterized types are frequently used to pass information object set definitions to impose table constraints on the final type. Since table constraints do not affect the code that is generated by the compiler, the parameterized type definition is reduced to a normal type definition and references to it are handled in the same way as defined type references. This can lead to a significant reduction in generated code in cases where a parameterized type is referenced over and over again.

For example, consider the following often-repeated pattern from the UMTS 3GPP specs:

   ProtocolIE-Field {RANAP-PROTOCOL-IES : IEsSetParam} ::= SEQUENCE {
      id             RANAP-PROTOCOL-IES.&id          ({IEsSetParam}),
      criticality    RANAP-PROTOCOL-IES.&criticality ({IEsSetParam}{@id}),
      value          RANAP-PROTOCOL-IES.&Value       ({IEsSetParam}{@id})
   }

In this case, IEsSetParam refers to an information object set specification that constrains the values that are passed for any given instance of a type referencing a ProtocolIE-Field. The compiler does not add any extra code to check for these values, so the parameter can be discarded. After processing the Information Object Class references within the construct (refer to the Information Objects section for information on how this is done), the reduced definition for ProtocolIE-Field becomes the following:

   ProtocolIE-Field ::= SEQUENCE {
      id ProtocolIE-ID,
      criticality Criticality,
      value ASN.1 OPEN TYPE
   }

References to the field are simply replaced with a reference to the generated ProtocolID-Field class.