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 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 just as if the original type was declared to be an octet string of size 1 to 32. That is, it will map the type to a go byte slice ([]byte) and do constraint checks before encoding and after decoding to ensure the value is within the specified range.

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

The compiler performs the substitution to create the proper Go type for SignedName:

    type SignedName struct {
        ToBeSigned Name
        AlgorithmID []uint64
        ParamS Params
        Signature asn1.BitString
    }

When processing parameterized type definitions, the compiler 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 when table constraint code generation is not enabled, 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 oft-repeated pattern from the UMTS 3GPP specifications:

   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 allowed to be 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 (note that this is not true if the -tables compiler option is specified). After processing the Information Object Class references within the construct (refer to the section on Information Objects 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 ProtocolID-Field type.

If -tables is specified, the parameters are used and a new type instance is created in accordance with the rules above.