Unions Table Constraint Model

The unions table constraint model originated from common patterns used in a series of ASN.1 specifications in-use in 3rd Generation Partnership Project (3GPP) standards. These standards include Node Application Part B (NBAP) and S1AP and X2AP protocols in 4G network (LTE) standards. This pattern is repeated in 5G standards such as NGAP.

Generated Go Type Definitions for Message Types

The standard message type used by many specifications that employ table constraints is usually a SEQUENCE type with elements that use a relational table constraint that uses fixed-type and type fields. The general form is as follows:

   <Type> ::= SEQUENCE {
      <element1> <Class>.&<fixed-type-field> ({<ObjectSet>}), 
      <element2> <Class>.&<fixed-type-field> ({<ObjectSet>)){@element1}
      <element3> <Class>.&<type-field> ({<ObjectSet>)){@element1}
}

In this definition, <Class> would be replaced with a reference to an Information Object Class, <fixed-type-field> would be a fixed-type field wtihin that class, and <type-field> would be a type field within the class. <ObjectSet> would be a reference to an Information Object Set which would define all of the possibilities for content within the message. The first element (<element1>) would be used as the index element in the object set relation.

An example of this pattern from the S1AP LTE specification is as follows:

   InitiatingMessage ::= SEQUENCE {
      procedureCode   S1AP-ELEMENTARY-PROCEDURE.&procedureCode   
                         ({S1AP-ELEMENTARY-PROCEDURES}),
      criticality     S1AP-ELEMENTARY-PROCEDURE.&criticality      
                         ({S1AP-ELEMENTARY-PROCEDURES}{@procedureCode}),
      value           S1AP-ELEMENTARY-PROCEDURE.&InitiatingMessage   
                         ({S1AP-ELEMENTARY-PROCEDURES}{@procedureCode})
   }

In this definition, procedureCode and criticality are defined to be enumerated fixed types, and value is defined to be an open type field to hold variable content as defined in the object set definition.

The generated Go structure is similar to what is used to represent a CHOICE type. The structure consists of a choice selector value (T) followed by an inner structure (U) containing pointers to all of the alternative types that can appear in the field. This is the general form:

   type <Type> struct {
      <element1> <Element1Type>
      <element2> <Element2Type>

      /**
       * information object selector
       */
      T uint64
   
      /**
       * <ObjectSet> information objects
       */
      U struct {
         /**
          * <element1> : <object1-element1-value>
          * <element2> : <object1-element2-value>
          */
         <object1-name> *<object1-element3-type>
   
         /**
          * <element1> : <object2-element1-value>
          * <element2> : <object2-element2-value>
          */
         <object2-name>; *<object2-element3-type>
   
         ...
      }
   }

In this definition, the first two elements of the sequence would use the equivalent Go type as defined in the fixed-type field in the information object. The open type field (element3) would be expanded into the union structure as is shown. The T value would hold a sequential number which is generated to represent each of the choices in the referenced information object set. The union struct then contains an entry for each of the possible types as defined in the object set that can be used in the open type field. Comments are used to list the fixed-type fields corresponding to each open type field.

An example of the code that is generated from the S1AP sample ASN.1 snippet above is as follows:

const (
	S1APPDUDescriptionsS1APELEMENTARYPROCEDURESUNDEFTAG = 0
	S1APPDUDescriptionsS1APELEMENTARYPROCEDURESHandoverPreparationTAG = 1
	S1APPDUDescriptionsS1APELEMENTARYPROCEDURESHandoverResourceAllocationTAG = 2
	S1APPDUDescriptionsS1APELEMENTARYPROCEDURESPathSwitchRequestTAG = 3
        etc..
    )

    type InitiatingMessage struct {
        ProcedureCode uint64
        Criticality uint64
        Value struct {
            /**
             * information object selector
             */
            T uint64

	          /**
             * S1AP-ELEMENTARY-PROCEDURES information objects
             */
            U struct {
                /**
                 * procedureCode: id-HandoverPreparation
                 * criticality: CriticalityReject
                 */
                // T = 1
                HandoverPreparation *HandoverRequired

                /**
                 * procedureCode: id-HandoverResourceAllocation
                 * criticality: CriticalityReject
                 */
                // T = 2
                HandoverResourceAllocation *HandoverRequest

                /**
                 * procedureCode: id-PathSwitchRequest
                 * criticality: CriticalityReject
                 */
                // T = 3
                PathSwitchRequest *PathSwitchRequest
   
                etc..
    
            }
        }

Note that the long names generated for the TAG constants can be reduced by using the <alias> configuration element.

Generated Go Type Definitions for Information Element (IE) Types

In addition to message types, another common pattern in 3GPP specifications is protocol information element (IE) types. The general form of these types is a list of information elements as follows:

   <ProtocolIEsType> ::= <ProtocolIE-ContainerType> { <ObjectSet> }

   <ProtocolIE-ContainerType> { <Class> : <ObjectSetParam> } ::=
      SEQUENCE (SIZE (<size>)) OF <ProtocolIE-FieldType> {{ObjectSetParam}}
   
   <ProtocolIE-FieldType> { <Class> : <ObjectSetParam> } ::= SEQUENCE {
      <element1> <Class>.&<fixed-type-field> ({ObjectSetParam}), 
      <element2> <Class>.&<fixed-type-field> ({ObjectSetParam}{@element1}), 
      <element3> <Class>.&<Type-field> ({ObjectSetParam}{@element1})
   }

There are a few different variations of this, but the overall pattern is similar in all cases. A parameterized type is used as a shorthand notation to pass an information object set into a container type. The container type holds a list of the IE fields. The structure of an IE field type is similar to a message type: the first element is used as an index element to the remaining elements. That is followed by one or more fixed type or variable type elements. In the case defined above, only a single fixed-type and variable type element is shown, but there may be more.

An example of this pattern from the S1AP LTE specification follows:

   HandoverRequired ::= SEQUENCE {
      protocolIEs   ProtocolIE-Container   { { HandoverRequiredIEs} },
      ...
   }

   ProtocolIE-Container {S1AP-PROTOCOL-IES : IEsSetParam} ::= 
         SEQUENCE (SIZE (0..maxProtocolIEs)) OF ProtocolIE-Field {{IEsSetParam}}
   
   ProtocolIE-Field {S1AP-PROTOCOL-IES : IEsSetParam} ::= SEQUENCE {
         id             S1AP-PROTOCOL-IES.&id            ({IEsSetParam}),
         criticality    S1AP-PROTOCOL-IES.&criticality   ({IEsSetParam}{@id}),
         value          S1AP-PROTOCOL-IES.&Value         ({IEsSetParam}{@id})
   }

In this case, standard parameterized type instantiation is used to create a type definition for the protocolIEs element. This results in the following element declaration in the HandoverRequired type:

    ProtocolIEs []HandoverRequiredProtocolIEsElement

The type for the protocol IE list element is created in much the same way as the main message type was above:

    type HandoverRequiredProtocolIEsElement struct {
        Id uint64
        Criticality uint64
        Value struct {
            /**
             * information object selector
             */
            T uint64

            /**
             * HandoverRequiredIEs information objects
             */
            U struct {
                /**
                 * id: id-MME-UE-S1AP-ID
                 * criticality: CriticalityReject
                 * presence: PresenceMandatory
		             */
                // T = 1
                HandoverRequiredIEsIdMMEUES1APID *uint64
                
                /**
                 * id: id-eNB-UE-S1AP-ID
                 * criticality: CriticalityReject
                 * presence: PresenceMandatory
                 */
                // T = 2
                HandoverRequiredIEsIdENBUES1APID *uint64

                ...
            }
        }
    }

In this case, the protocol IE id field and criticality are generated as usual using the fixed-type field type definitions. The open type field once again results in the generation of a union structure of all possible type fields that can be used.

Note in this case the field names are automatically generated (HandoverRequiredIEsIdMMEUES1APID, etc.). The reason for this was the use of inline information object definitions in the information object set as opposed to defined object definitions. This is a sample from that set:

   HandoverRequiredIEs S1AP-PROTOCOL-IES ::= {   
      { ID id-MME-UE-S1AP-ID         CRITICALITY reject   TYPE MME-UE-S1AP-ID   PRESENCE mandatory   } |
      { ID id-HandoverType           CRITICALITY reject   TYPE HandoverType     PRESENCE mandatory   } |
   ...

In this case, the name is formed by combining the information object set name with the name of each key field within the set.