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. That is, it will generate a C structure containing a static byte array of size 32 as follows:
typedef struct OctetString32 { OSUINT32 numocts; OSOCTET data[32]; } OctetString32;
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 C typedef for
SignedName
:
typedef struct SignedName { Name toBeSigned; ASN1OBJID algorithmOID; Params paramS; ASN1DynBitStr signature; } SignedName;
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 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 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
typedef.
If -tables is specified, the parameters are used and a new type instance is created in accordance with the rules above.