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 is 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 impacts 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 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 );