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