SEQUENCE/SET

An ASN.1 SEQUENCE or SET type is a constructed type consisting of a series of element definitions; each element has an assigned ASN.1 type. An ASN.1 SEQUENCE or SET is mapped to a Go struct type with each ASN.1 element having an equivalent Go element definition. The general form is as follows:

ASN.1 production:

   <name> ::= SEQUENCE {
   <element1-name> <element1-type>,
   <element2-name> <element2-type>,
   ...
   }

Generated Go code:

   type <name> struct {
   <element1-name> <type1>
   <element2-name> <type2> 
   ...
   }

The <type1> and <type2 placeholders represent the Go types for the ASN.1 types <element1-type> and <element1-type>, respectively.

Here is an example:

ASN.1:

   A ::= SEQUENCE {
     x  SEQUENCE {
        a1 INTEGER,
        a2 BOOLEAN
     },
     y OCTET STRING (SIZE (10))
     } 

Generated Go code:

   type AX struct {
      A1 int64
      A2 bool
   }

   type A struct {
      X AX
      Y asn1rt.OctetString
   }

First, note that the Go field names in the struct begin with uppercase letters. This means the names are exported for use outside of the package in which they are defined.

Second, notice that we generated type AX, even though no such type was defined in the ASN.1. This is the result of using a nested, constructed type in the ASN.1. The elements in a SEQUENCE or SET can be of any ASN.1 type, including other constructed types. Sometimes, one constructed type definition is nested inside another, rather than defining a separate type and referencing it by name. For example, it is possible to nest a SEQUENCE definition within another SEQUENCE definition (see element x in the above example).

The ASN1C compiler first recursively pulls all nested constructed elements out of the SEQUENCE and forms new internal types. The internal ASN.1 names of these types are of the form <name>_<element-name1>_<elementname2>_ ...<element-nameN>. In the above example, the compiler internally created a type for element x: A_x. The compiler then generates code as if the ASN.1 had been written without nesting, using the type definitions it created internally. Transforming the ASN.1 name to a Go name produced AX.

In the case of nesting levels greater than two, all of the intermediate element names are used to form the final name. For example, consider the following type definition that contains three nesting levels:

   X ::= SEQUENCE {
      a SEQUENCE {
         aa SEQUENCE { x INTEGER, y BOOLEAN },
         bb INTEGER
      }
   }

In this case, the generation of compiler-generated types results in the following equivalent type definitions:

   X-a-aa ::= SEQUENCE { x INTEGER, y BOOLEAN }

   X-a ::= SEQUENCE { aa X-a-aa, bb INTEGER }

   X ::= SEQUENCE { X-a a }

Note that the name for the aa element type is X-a-aa. It contains both the name for a (at level 1) and aa (at level 2). The concatanation of all of the intermediate element names can lead to very long names in some cases. To get around the problem, the -shortnames command-line option can be used to form shorter names. In this case, only the type name and the last element name are used. In the example above, this would lead to an element name of X-aa. The disadvantage of this is that the names may not always be unique. If using this option results in non-unique names, an _n suffix is added where n is a sequential number to make the names unique.

Although the compiler can handle embedded constructed types within productions, it is generally not considered good style to define productions this way. It is much better to manually define the constructed types for use in the final production definition. For example, the production defined at the start of this section can be rewritten as the following set of productions:

   X ::= SEQUENCE {
      a1 INTEGER,
      a2 BOOLEAN
   }

   A ::= SEQUENCE {
      x X,
      y OCTET STRING (SIZE (10))
   }

This makes the generated code easier to understand for the end user.

OPTIONAL keyword

Elements within a sequence can be declared to be optional using the OPTIONAL keyword. This indicates that the element is not required. Optional elements are handled in Go using pointer types. If the pointer to the element value is nil, it means the element is not present.

For example, the elements in the following production are marked as optional:

   Aseq ::= SEQUENCE {
      x      INTEGER OPTIONAL,
      y      BOOLEAN OPTIONAL
   }

In this case, the following Go type is generated:

   type Aseq struct {
      X *int64
      Y *bool
   }

When this structure is populated for encoding, if the element is present, the developer must set the pointer value to point at a value. This can be done using new or by assigning the value to a local variable and then storing the address of the variable in the generated structure. Conversely, when a message is decoded into this structure, the developer must test to see if the elements are nil to see if they were present in the message.

DEFAULT keyword

The DEFAULT keyword allows a default value to be specified for elements within the SEQUENCE. ASN1C will generate a non-pointer type for elements using DEFAULT if the type is one of several simple types (BOOLEAN, INTEGER, ENUMERATED, REAL, BIT STRING, OCTET STRING, and character string types). In other cases, ASN1C will generate a pointer type, the same as it does for an element with OPTIONAL; a nil pointer is interpreted as representing the default value.

For PER, elements set to their default value MUST be encoded as ABSENT. In the cases where ASN1C generates a non-pointer type, the compiler automatically generates code to meet this requirement. In the cases where ASN1C generates a pointer type, the programmer must set the pointer to nil rather than populate the field with the default value.

Extension Elements

If the SEQUENCE type contains an open extension field (i.e., a ... at the end of the specification or a ..., ... in the middle), a special element will be inserted to capture encoded extension elements for inclusion in the final encoded message. This element will be of type [][]byte and have the name ExtElem1. This holds an array of encoded components.

The -noOpenExt command line option can be used to alter this default behavior. If this option is specified, the ExtElem1 element is not included in the generated structure; any extension data that may be present in a decoded message is skipped.

If the SEQUENCE type contains an extension marker and extension elements, then the actual extension elements will be present in addition to the ExtElem1 element. These elements will be treated as optional elements whether they were declared that way or not. The reason is because a version 1 message could be received that does not contain the elements.

If version brackets are present, a struct is generated for each extension element group. This struct has type name of the form <container>ExtGrpV<n> where <container> is the name of the container type and <n> is the version number of the group. This type is then referenced as an optional element in the container type.

An example of a type with version brackets is as follows:

   TestSequence ::= SEQUENCE {
      item-code    INTEGER (0..254),
      item-name    IA5String (SIZE (3..10)) OPTIONAL,
      ... ! 1,
      urgency      ENUMERATED { normal, high } DEFAULT normal,
      [[ alternate-item-code       INTEGER (0..254),
         alternate-item-name       IA5String (SIZE (3..10)) OPTIONAL
      ]]
   }

In this case, a structure named TestSequenceExtGrpV3 is generated for the items in the version brackets. The number 3 is used because 2 would be the version extension number of the urgency field. The generated Go types for this construct would be as follows:

   type TestSequenceExtGrpV3 struct {
	AlternateItemCode uint64
	AlternateItemName *string
   }

   type TestSequence struct {
	ItemCode uint64
	ItemName *string
	Urgency *uint64
	ExtGrpV3 *TestSequenceExtGrpV3
	ExtElem1 [][]byte
   }

The presence or absence of the entire version block would be determined by whether the ExtGrpV3 element pointer is set.