Consider the following scenario:

  • You want to send a CSTA phase 3 SetDisplay message encoded as ASN.1 to your PBX to tell it to display "Hello there" on extension 301's display.
  • Your PBX requires a vendor-specific extension to the standard CSTA SetDisplay message.
  • You know the specific bytes that comprise this vendor-specific extension, but you don't have any context or definition around them.

So, how do you properly incorporate the bytes that you have into your CSTA SetDisplay message?

First, let's look at the definition of the CSTA SetDisplay message.  The best place to look for this definition is the 4th edition of the Ecma-269 document, "Services for Computer Supported Telecommunications Applications (CSTA) Phase III".  This document is available here:  http://www.ecma-international.org/publications/standards/Ecma-269-arch.htm.  The ASN.1 definitions for CSTA messages are based on this 4th edition of this document.

The SetDisplay message is defined in section 21.1.15.1 of this document.  The important pieces to note are the following:

  • The message component named "device", of type DeviceID, is mandatory.  This is the device whose display you want to manipulate.
  • The component named contentsOfDisplay is also mandatory and can be up to 240 characters.  This is the text of the message you want to display.
  • The last component of the message is named privateData and is of type CSTAPrivateData.  This will be the component you use to include your vendor-specific byte sequence.

Now let's peel off a layer of abstraction and look at the ASN.1 specification for the SetDisplay message.  The source files for the ASN.1 specifications for phase 3 CSTA messages are available through standard Ecma-285:

http://www.ecma-international.org/publications/standards/Ecma-285.htm.

We can see from looking at the ASN.1 specifications that SetDisplayArgument is defined as follows:

SetDisplayArgument ::= SEQUENCE {
   device    DeviceID,
   display    DisplayID   OPTIONAL,
   physicalBaseRowNumber   [0] IMPLICIT INTEGER    OPTIONAL,
   physicalBaseColumnNumber   [1] IMPLICIT INTEGER    OPTIONAL,
   contentsOfDisplay   IA5String (SIZE(0..240)),
   offset   [2] IMPLICIT INTEGER   OPTIONAL,
   extensions    CSTACommonArguments    OPTIONAL
}

We can see that the last item is called "extensions" with a type of CSTACommonArguments.  The ASN.1 definition of CSTACommonArguments is as follows:

CSTACommonArguments ::= [APPLICATION 30] IMPLICIT SEQUENCE {
   security       [0] IMPLICIT    CSTASecurityData                OPTIONAL,
   privateData    [1] IMPLICIT    SEQUENCE OF CSTAPrivateData     OPTIONAL
}

The second item, named "privateData", is a sequence of CSTAPrivateData items. And the ASN.1 definition of CSTAPrivateData is as follows:

CSTAPrivateData ::= CHOICE {
   string   OCTET STRING,
   private  NULL    -- The actual encoding is added here,
   -- replacing NULL with another valid ASN.1 type.
}

Now let's pull off another layer of abstraction and look at some code that would be generated by these definitions using ASN1C. For the sake of these examples we'll use C# code.

The C# definition for SetDisplayArgument would look like this:

public class SetDisplayArgument : Asn1Type {
   public DeviceID device;
   public DisplayID display;  // optional
   public Asn1Integer physicalBaseRowNumber;  // optional
   public Asn1Integer physicalBaseColumnNumber;  // optional
   public Asn1IA5String contentsOfDisplay;
   public Asn1Integer offset;  // optional
   public CSTACommonArguments extensions;  // optional
   .
   .
   .
}

Again the last piece is named "extensions" and is of type CSTACommonArguments. The C# definition for CSTACommonArguments would look like this:

public class CSTACommonArguments : Asn1Type {
   public new readonly static Asn1Tag _TAG = new Asn1Tag (Asn1Tag.APPL, Asn1Tag.CONS, 30);
   public CSTASecurityData security;  // optional
   public _SeqOfCSTAPrivateData privateData;  // optional
   .
   .
   .
}

The piece we're interested in is the privateData member, which is of type _SeqOfCSTAPrivateData. This class is defined as follows:

public class _SeqOfCSTAPrivateData : Asn1Type {
   public CSTAPrivateData[] elements;
   .
   .
   .
}

Basically it's just an array of CSTAPrivateData items. So if we look at the CSTAPrivateData definition, we see that it looks like this:

public class CSTAPrivateData : Asn1Choice {
   // Choice element identifier constants
   public const byte _STRING_ = 1;
   public const byte _PRIVATE_ = 2;

   static CSTAPrivateData ()
   {
   }

   public CSTAPrivateData () : base()
   {
   }
   .
   .
   .
   public void Set_string_ (Asn1OctetString value) {
      SetElement (_STRING_, value);
   }
   .
   .
   .
}

The main thing to observe here is that there is a method called Set_string_() that takes an Asn1OctetString object as its argument. And Asn1OctetString, which is part of the ASN1C C# run-time, has a constructor that accepts a byte array. So it appears we have a solution.

So let's start writing a program to encode our SetDisplayArgument message:

static void Main(string[] args)
{
   byte[] vendorSpecific = { 0x08, 0x03, 0x41, 0x42, 0x43 };

These are the bytes of the vendor-specific piece of the SetDisplayArgument message.

CSTAPrivateData pd = new CSTAPrivateData();
pd.Set_string_(new Asn1OctetString(vendorSpecific));

These lines create a CSTAPrivateData object and populate it with the vendor-specific byte sequence.

_SeqOfCSTAPrivateData seq = new _SeqOfCSTAPrivateData(1);
seq.elements[0] = pd;

These lines create a _SeqOfCSTAPrivateData object using the constructor that allows us to specify how many CSTAPrivateData elements we'll want it to encapsulate. In our case we need just one. The second line sets element 0 of the array of CSTAPrivateData objects to the object we created for our vendor-specific byte sequence.

CSTACommonArguments ca = new CSTACommonArguments();
ca.privateData = seq;

These lines create a CSTACommonArguments object and set its privateData member equal to the _SeqOfCSTAPrivateData object.

DeviceID targetDeviceID = new DeviceID();
DeviceID_deviceIdentifier deviceIdent = new DeviceID_deviceIdentifier();
deviceIdent.Set_dialingNumber(new NumberDigits("301"));
targetDeviceID.deviceIdentifier = deviceIdent;

These lines encapsulate the device identifier, which is 301, into a DeviceID object.

SetDisplayArgument sda = new SetDisplayArgument();
sda.device = targetDeviceID;
sda.contentsOfDisplay = new Asn1IA5String("Hello there");
sda.extensions = ca;

The first line in the sequence above creates a SetDisplayArgument object. The second line sets its "device" member to the DeviceID object that encapsulates extension 301. The third line sets the contentsOfDisplay member to the text we want to display on the device. And the fourth line sets the "extensions" member to our CSTACommonArguments object, which ultimately contains the vendor-specific byte sequence that must be included in a SetDisplayArgument message sent to this PBX.

Asn1BerEncodeBuffer eb = new Asn1BerEncodeBuffer();
int len = sda.Encode(eb);

These lines create an encode buffer object and encode the populated SetDisplayArgument object using Basic Encoding Rules (BER).

for (ushort i = 0; i < len; i++)
{
   Console.Write(eb.MsgCopy[i].ToString("X2") + " ");
}

Finally this loop displays the bytes of the encoded message in hex, separated by a space, and all on the same line.

If we run this program, we get the following sequence of bytes for the encoded message:

30 1F 30 05 80 03 33 30 31 16 0B 48 65 6C 6C 6F 20 74 68 65 72 65 7E 09 A1 07 04
05 08 03 41 42 43

If we display the message using an ASN.1 viewer like ASN1VE, we see the following:

SetDisplayArgument {
   device {
      deviceIdentifier {
         dialingNumber = 301
      }
   }
   contentsOfDisplay = Hello there
   extensions {
      privateData {
         CSTAPrivateData {
            string = 0803414243
         }
      }
   }
}

So, will sending this SetDisplayArgument message to the PBX achieve the desired results? The answer depends on the PBX model. So you might be wondering why it wouldn't work in some cases. Let's look at the encoded message.

The last five bytes, 08 03 41 42 43, are the five bytes of the vendor-specific piece for which we have no definition or context. The two bytes before it, 04 05, indicate that it's an OCTET STRING of length 5. And it's actually these two bytes that might cause the difficulty.

Recall that the definition of CSTAPrivateData that we're using has an item of type OCTET STRING. We populated this item in our C# code when we used the Set_string_() method of our CSTAPrivateData instance. Because OCTET STRING is a recognized ASN.1 type, the five bytes of our vendor-specific sequence are identified as an OCTET STRING in the encoded message.

The PBX may not accept this message. It may want to see just the five bytes as the private data portion of the message, not an OCTET STRING that contains the five bytes.

One way to solve this problem would be to modify the bytes of the encoded message to remove the 04 05. This could get messy, as previous length bytes within the encoded message may need to be adjusted down by 2.

But suppose the ASN.1 definition of CSTAPrivateData looked like this:

CSTAPrivateData ::= ANY

The C# code that would be generated by ASN1C for such a definition would look like this:

public class CSTAPrivateData : Asn1OpenType {
   static CSTAPrivateData ()
   {
   }

   public CSTAPrivateData () : base()
   {
   }

   public CSTAPrivateData (byte [] value_) : base (value_) {
   }
}

Note that we now have simply a no-argument constructor and a constructor that takes a byte array. To make use of this class in our program, instead of creating an empty CSTAPrivateData object and populating it with the Set_string_() method, we would use the constructor that takes the byte array:

CSTAPrivateData pd = new CSTAPrivateData(vendorSpecific);

Everything else in our sample program would stay the same. Now if we encode a SetDisplayArgument object that contains a CSTAPrivateData object populated in this way, we would get this series of bytes:

30 1D 30 05 80 03 33 30 31 16 0B 48 65 6C 6C 6F 20 74 68 65 72 65 7E 07 A1 05 08
03 41 42 43

Notice that the 04 05 before our five-byte vendor-specific sequence is no longer there. If we display this message using ASN1VE, we get this:

SetDisplayArgument {
   device {
      deviceIdentifier {
         dialingNumber = 301
      }
   }
   contentsOfDisplay = Hello there
   extensions {
      privateData {
         CSTAPrivateData = 0803414243
      }
   }
}

Notice the difference? The private data consists of our vendor-specific blob and nothing else; there is no OCTET STRING definition surrounding it. This message is more likely to produce the desired results when sent to the PBX.

So how does all of this affect you if you find yourself in a situation like that described above? If you have ASN1C, then if necessary you can modify whatever definition of CSTAPrivateData you're using so it will accept a vendor-specific blob. Once you modify the ASN.1 specification, you can use ASN1C to regenerate your code.

However, there are a couple of CSTA-related products offered by Objective Systems that don't include ASN1C. For example, you might have a production version of the C++ CSTA API kit for one or more of the CSTA phases. Or you might have the CSTADLL .Net API kit. Depending on the product and the phase of CSTA that you're using, the definition of CSTAPrivateData may or may not be defined as ANY. If you find yourself in a situation where (1) you don't have ASN1C, (2) the definition of CSTAPrivateData that is active for your situation doesn't allow a vendor-specific blob to be specified unaltered, and (3) you need to specify a vendor-specific sequence of bytes unaltered in your message, contact Objective Systems Support (support@obj-sys.com). We will most likely be able to create a kit that provides the functionality that you need.


Published

Category

CSTA