16. A Sequential Format Example
The example illustrated in this appendix is a format definition for decoding a shortened version of EWSD AMA (Automatic Message Accounting).
All incoming records are of the same record type, however their content varies. The first four fields are present in all records, the fifth field varies depending on the content of the field RecordOwnerTypePresent
. The last three fields are optional.
A schematic representation of the example discussed in this section
external
Since the last three optional fields (including RecordOwnerType
and RecordOwnerDN
) consist of several fields, each one will be defined as its own type. That is, an external sequential format.
Note!
The FillerRecord_0x00_EXT
construct. This is the padding which may be present between records. Hence, it is defined as a record type identified by the decoder, however not routed on to the subsequent agent (see the in_map
definitions).
If the format is only used for decoding (which is the normal case for switch output formats), the encoding instructions (encode_value
) in the following code is skipped.
external AMARecord_EXT: identified_by(RecordIdentifier == 0x84), dynamic_size(RecordLength) { int(little_endian) RecordIdentifier :static_size(1), external_only, encode_value(0x84); int(little_endian) RecordLength :static_size(2), external_only, encode_value(udr_size);
This byte contains several flags, from which only one is of interest. Therefore, if RecordOwnerTypePresent
is set, then the RecordOwnerType
is present, or else the RecordOwnerDN
data is. See the presence specifications in the following code.
bit_block :static_size(1) { int(little_endian) RecordOwnerTypePresent: msb(7),lsb(7), external_only, encode_value( (field_present(RecordOwnerType)?1:0)); };
Since three bytes of unwanted data is present, it is specified as external_only
to stop the field from getting automatically generated in the target internal. No encoding is specified (0 padding is used).
byte ignoredFields: static_size(3), external_only;
Either RecordOwnerType
or RecordOwnerDN
is present. To encode them, it is important to note that exactly one of these fields must be present in the output data.
int(little_endian) RecordOwnerType: static_size(1), present if(RecordOwnerTypePresent == 1); RecordOwnerDN_EXT RecordOwnerDN: present if(RecordOwnerTypePresent == 0);
The rest of the record consists of optional packages with additional information. The full AMA format contains lots of other packages (and additional information in the header), however in this example, only three packages are included. Any unrecognized package leads to failure of the decoding, since the size of the set is specified (all the remaining data must be handled by the set decoding).
set: dynamic_size(remaining_size) { Package_100_EXT DateTimeDuration : optional; Package_101_EXT PartnerDirectoryNumber : optional; Package_102_EXT ServiceInfo : optional; }; }; external RecordOwnerDN_EXT { bit_block : static_size(1) { int LACLength : msb(7), lsb(4); int OwnerIDLength : msb(3), lsb(0), external_only, encode_value( strLength( LACAndDN )); };
The following is a typical construction for BCD data with a nibble length specification. Both nibble size (native_size
) and field size (dynamic_size
) must be specified.
bcd LACAndDN : dynamic_size((OwnerIDLength+1)>>1), // Alternative syntax: // dynamic_size((OwnerIDLength+1)/2) native_size(OwnerIDLength); }; //Package with PackageNumber 100 (0x64): external Package_100_EXT: identified_by(PackageNumber == 0x64) { int(little_endian) PackageNumber : static_size(1), external_only, encode_value( 0x64 ); int(little_endian) Year : static_size(1); int(little_endian) Month : static_size(1); int(little_endian) Day : static_size(1); int(little_endian) Hour : static_size(1); int(little_endian) Minutes : static_size(1); int(little_endian) Seconds : static_size(1); int(little_endian) Flags : static_size(1); int(little_endian) Duration : static_size(3); }; // Package with PackageNumber 101 (0x65). external Package_101_EXT: identified_by(PackageNumber == 0x65) { int(little_endian) PackageNumber: static_size(1), external_only, encode_value( 0x65 ); int(little_endian) NumberOfDigits: static_size(1), external_only, encode_value( strLength( Digits )); // Again the typical BCD decoding specification bcd Digits: dynamic_size((NumberOfDigits+1)/2), native_size(NumberOfDigits); }; // Package with PackageNumber 102 (0x66). external Package_102_EXT: identified_by(PackageNumber == 0x66) { int(little_endian) PackageNumber : static_size(1), external_only, encode_value( 0x66 ); int(little_endian) ServiceIndicator : static_size(1); int(little_endian) AdditionalInformation : static_size(1); int(little_endian) Flags : static_size(1); };
Note!
The identified_by
expression, which must be specified for any format used in a set
construct.
/ Filler needed to be able to recognize on the input stream: external FillerRecord_0x00_EXT: identified_by(RecordIdentifier == 0x00), static_size(32) { int(little_endian) RecordIdentifier : static_size(1), external_only, encode_value(0x00); // Note that no other fields are specified. // The UDR size (32) will be consumed and discarded (see in_map). };
Alternative Syntax
An alternative to use the set
construct, in the external definition for AMARecord_ext
, switched_set
could be used. This will impact the syntax for the Package_*_EXT
types. Only the syntax differing from the original example is shown. The main reason for using switched_set
instead of set
is when performance must be increased.
external AMARecord_EXT: // ... // All preceding fields according to the original specification. // Only the set construct is replaced with switched_set. // ... switched_set(PackageNumber): dynamic_size(remaining_size) { case( 0x64 ) { Package_100_EXT DateTimeDuration; }; case( 0x65 ) { int(little_endian) NumberOfDigits_101: static_size(1), external_only, encode_value( strLength( Digits_101 )); // Again the typical BCD decoding specification. bcd Digits_101: dynamic_size((NumberOfDigits_101+1)/2), native_size(NumberOfDigits_101); }; case( 0x66 ) { int(little_endian) ServiceIndicator_102 : static_size(1); int(little_endian) AdditionalInformation_102 : static_size(1); int(little_endian) Flags_102 : static_size(1); }; }; }; external RecordOwnerDN_EXT { bit_block : static_size(1) { int LACLength : msb(7), lsb(4); int OwnerIDLength : msb(3), lsb(0), external_only, encode_value( strLength( LACAndDN )); };
The following is a typical construction for BCD data with a nibble length specification. Both nibble size (native_size
) and field size (dynamic_size
) must be specified.
bcd LACAndDN : dynamic_size((OwnerIDLength+1)>>1), native_size(OwnerIDLength); }; // Package with PackageNumber 100 (0x64). external Package_100_EXT { int(little_endian) Year : static_size(1); int(little_endian) Month : static_size(1); int(little_endian) Day : static_size(1); int(little_endian) Hour : static_size(1); int(little_endian) Minutes : static_size(1); int(little_endian) Seconds : static_size(1); int(little_endian) Flags : static_size(1); int(little_endian) Duration : static_size(3); };
Note!
No identified_by
or PackageNumber is present, since this is handled in the containing switched_set
.
internal
No internal
is used. In this case, the target_internal
is sufficient; all field names and field types are in order and there is only one type of record present in the input, and no additional fields are required.
in_map
The padding in the records is recognized by the decoder, however it is not actually mapped in to the system due to the use of the discard_output
flag.
The AMARecord_Map
contains sub-automatic specifications (the target_internal
specifications within the automatic
block), which will give five additional internal
formats (other than the AMARecord
). This is useful when you want to route them as individual records.
in_map FillerRecord_0x00_Map : external(FillerRecord_0x00_EXT), target_internal(FillerRecord_0x00), discard_output { automatic; }; in_map AMARecord_Map : external(AMARecord_EXT), target_internal( AMARecord ) { automatic { RecordOwnerType_EXT : target_internal( RecordOwnerType ); RecordOwnerDN_EXT : target_internal( RecordOwnerDN ); Package_100_EXT : target_internal( Package_100 ); Package_101_EXT : target_internal( Package_101 ); Package_102_EXT : target_internal( Package_102 ); }; };
decoder
The padding pseudo-records and data records can arrive in any order, therefore there is no need to define a constructed decoder.
decoder AMAFile : in_map( AMARecord_Map ), in_map( FillerRecord_0x00_Map );