Microsoft Lync and Skype for Business have a rich set of .NET APIs which make it easy to extend the platform and integrate it with other applications. This blog helps explain how to use those APIs.

Manipulating SIP headers with UCMA

Posted: October 7th, 2011 | Author: | Filed under: UCMA 2.0, UCMA 3.0 | Tags: , | 5 Comments »

For the most part, UCMA keeps Lync operations at a high level, and allows you to ignore the details of the SIP messages that are going back and forth. At times, however, you may need to look at the actual SIP message that a UCMA application is sending or receiving, or add a specific header to an outgoing message. This post shows how to do both of these things.

The key UCMA classes for working with messages at the SIP level are SipRequestData and its cousin SipResponseData. SipRequestData has properties that hold various details of a SIP request (such as an INVITE request or a SUBSCRIBE request), whether it is outgoing from the application or incoming to the application. SipResponseData, as you may have guessed, does the same for SIP responses, like 200 OK.

The workings of SIP messages and how Lync uses them is a whole other topic, and I won’t go into too much detail here; if you need a primer, there are plenty of resources on SIP on the Internet, and my book also has a chapter which covers Lync’s use of SIP. To make a long story short, when a Lync endpoint wants to start a call (audio/video, IM, or application sharing), it sends a SIP INVITE request to the call destination, via the proxy server (in Lync, the Front End Server). The request has various headers which contain information relevant to the call, and it also has a message body which contains information on how the media will be transmitted. The call recipient then responds with SIP responses such as 180 Ringing and 200 OK.

For the most part, UCMA does the work of building outgoing SIP requests and interpreting incoming ones. Let’s say, though, that your application is receiving calls to which a special header has been added by an MSPL application running on the Front End Server; or maybe it’s a standalone UCMA application which is taking calls from an IP-PBX system and looking at SIP headers added by the PBX for caller information. In cases like these, you can go directly to SipRequestData and look at the SIP headers or the SIP message body in your code.

Two components of SipRequestData are particularly handy here. The first is SipRequestData.SignalingHeaders, which is a collection of SignalingHeader objects that represent individual headers on the SIP request. Headers in a SIP message look like this:

To: sip:yourapplication@domain.local

The first part, in this case “To:”, is the name of the header, while the rest is the value. The header name is accessible through the SignalingHeader.Name property, while to get the value you can call SignalingHeader.GetValue().

The other useful element of SipRequestData is the GetMessageBodyString method, which you can call to get the actual body of the SIP message. For an INVITE request, this is the Session Description Protocol (SDP) information that describes the media portion of the call. There is also a corresponding GetMessageBody method which returns the body as a byte array in case you need to read the content in thas format.

Here’s an example of what inspecting SIP request data looks like for an incoming call. The code below is an incoming call event handler which loops through the SIP headers on the incoming INVITE message and writes each one to the console. It then also writes the message body to the console for good measure.

void OnCallReceived(object sender, CallReceivedEventArgs<AudioVideoCall> e)
{
    foreach (SignalingHeader header in e.RequestData.SignalingHeaders)
    {
        Console.WriteLine("{0}: {1}", header.Name, header.GetValue());
    }
    Console.WriteLine(e.RequestData.GetMessageBodyString());

    AcceptCall();
}

If you call this application, you will get console output that looks something like the following:

ALLOW: INVITE
ALLOW: ACK
ALLOW: CANCEL
ALLOW: OPTIONS
ALLOW: BYE
ALLOW: REFER
ALLOW: NOTIFY
ALLOW: MESSAGE
ALLOW: SUBSCRIBE
ALLOW: INFO
CALL-ID: NDEwMTlkNmE0M2FmZjIzZDUwNGE2NWI3MDFlMDUxMTE.
CONTACT: <sip:t@192.168.0.12:1088;transport=TCP>
CONTENT-LENGTH: 406
CONTENT-TYPE: application/sdp
CSEQ: 1 INVITE
FROM: "Greenlee"<sip:t@192.168.0.12>;tag=6f6fe071
MAX-FORWARDS: 70
SUPPORTED: replaces
TO: <sip:htns@192.168.0.12:5060>
USER-AGENT: X-Lite 4 release 4.1 stamp 63214
VIA: SIP/2.0/TCP 192.168.0.12:1088;branch=z9hG4bK-d8754z-95acf35738789d8e-1---d8
754z-;rport
v=0
o=- 12962505634775207 1 IN IP4 192.168.0.12
s=CounterPath X-Lite 4.1
c=IN IP4 192.168.0.12
t=0 0
a=ice-ufrag:6c3d1c
a=ice-pwd:67b942ce283ee9d50fe8fc4dd2b67a04
m=audio 49492 RTP/AVP 107 0 8 101
a=rtpmap:107 BV32/16000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
a=sendrecv
a=candidate:1 1 UDP 659136 192.168.0.12 49492 typ host
a=candidate:1 2 UDP 659134 192.168.0.12 49493 typ host

As you can see from the User-Agent header in this message, in this example I used X-Lite to call my UCMA application, and you can glean some interesting details from reading through the headers, such as the port X-Lite is using (1088) and the fact that it is asking the UCMA app to send audio media to it using UDP at 192.168.0.12 on either port 49492 or 49493 (that’s what those lines beginning with a=candidate mean).

If I were looking for a specific SIP header, I could look for it in the SignalingHeaders collection like so:

SignalingHeader headerToInspect =
    e.RequestData.SignalingHeaders.FirstOrDefault(h => h.Name == "Special-Data");

if (headerToInspect != null)
{
    // Do something useful with the header value.
}

Now, looking at SIP headers on an incoming call is all well and good, but say you want to add your own headers to an outbound call. The class you need here is CallEstablishOptions. By adding a SignalingHeader instance to the collection in CallEstablishOptions.Headers, you can tell UCMA to add your extra header along with all of the others it generates itself for the outgoing INVITE message.

private void PlaceCall()
{
    Conversation conversation = new Conversation(_endpoint);

    AudioVideoCall avCall = new AudioVideoCall(conversation);

    SignalingHeader headerToAdd = new SignalingHeader("Favourite-Colour", "unknown");
    CallEstablishOptions options = new CallEstablishOptions();
    options.Headers.Add(headerToAdd);

    try
    {
        avCall.BeginEstablish("sip:t@192.168.0.12:1088", options,
            ar =>
            {
                try
                {
                    avCall.EndEstablish(ar);
                }
                catch (RealTimeException ex)
                {
                    Console.WriteLine(ex);
                }
            },
            null);
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine(ex);
    }
}

If you look at the INVITE request sent by the application when it places this call, you will see your extra header along with the rest.

Many other UCMA methods have associated “Options” classes that have this same Headers collection, allowing you to add your own SIP headers to the request or response that UCMA sends out. For example, take a look at CallTransferOptions, CallDeclineOptions, CallAcceptOptions, SendMessageOptions, AudioVideoMcuDialOutOptions, etc. The “Options” classes are interesting to explore for their other properties as well. Some have rarely used but potentially useful features: for instance, the CallAcceptOptions class has a RedirectDueToBandwithPolicyEnabled property which allows you to set whether or not the call your application is accepting can be redirected because of the Lync bandwidth policy or whether the bandwidth policy’s redirect recommendations will be ignored.

There are plenty of things you can do by manipulating SIP headers in your UCMA application, some more obvious than others. I’ll leave further exploration of the possibilities to future posts. Meanwhile, if you have any questions about this topic, feel free as always to comment or send me an email.


5 Comments on “Manipulating SIP headers with UCMA”

  1. 1 David Gray said at 1:37 pm on August 7th, 2012:

    Hi Mike,

    Is there a way in UCMA w/o Lync Server support to perform a ReInvite so that I might change SDP. I want to perform the Re-Invite to work around a network element’s inability to support SIP Notify.

    Please Advise…

    Thanks!

    – Dave

  2. 2 Michael said at 11:11 am on August 13th, 2012:

    Hi Dave,
    If you’re looking to modify SDP and have fine-grained control over signaling, you’ll probably need to fall back on the Microsoft.Rtc.Signaling namespace and work with classes like SignalingSession rather than the classes in the Collaboration layer. You can’t really tell UCMA what specific SIP messages to send when dealing with Collaboration classes (AudioVideoCall, Conversation, etc.) because the purpose of that layer is to abstract away details of SIP signaling.
    Let me know if you have more questions about this.
    Michael

  3. 3 David Gray said at 10:40 am on August 22nd, 2012:

    I had looked at SignalingSession but it wasn’t apparent how one could obtain the specific instance of SignalingSession associated with an AudioVideoCall. Our application is an outbound dialer and it relies upon the features of the collaboration layer (AudioVideoCall, AudioVideoFlow, Player, Recorder) and we’d only want to operate at the signaling layer to initiate the re-invite. I does seem that there are parallels between the SignalingSession and the MediaProvider so one could create a custom MediaProvider and implementing the SDP customization logic in the BeginGetOffer() method. The abstract methods of the MediaProvider class are similar to the IOfferAnswer interface of the SignalingSession so that’s what lead me to this conclusion. I’d then kick off the re-invite by calling the BeginSdpRenegotiation() and rely on my custom BeginGetOffer() and SetAnswer() to do their thing. It’s a lot of work especially since you can’t derive behavior from AudioVideoProvider but it seems like anything involving UCMA that is off the beaten path is a lot of work. Let me know if you have a better approach or if somehow you can obtain a SignalingSession instance from an AudioVideoCall or comparable collaboration object.

  4. 4 David Gray said at 9:11 am on August 23rd, 2012:

    AudioVideoFlow contains a member called SignalingContext which is of type CallDialogContext class. The CallDialogContext class has a member called SessionKey which identifies the SignalingSession associated with the call. Can I use this key to get the SignalingSession instance associated with the flow so I can bind an IOfferAnswer through OfferAnswerNegotiation() or access its other methods? If so, what is the lookup mechanism that takes this SessionKey and returns a SignalingSession instance?

  5. 5 Volkan Nazmi Metin said at 5:00 am on May 4th, 2015:

    Hi,

    In our lync application, we would like to control a users presence (bot user at Lync).

    By controlling, I mean;
    Let’s say our bot is John Doe and we would like to have the ability to show John Doe’s presence for me as DND, but for you as Available.

    Is it possible to do something like this via handling sip notify or benotif messages to the other users?

    Please show us a direction to accomplish this if possible.

    Thanks


Leave a Reply

  • Note: Comment moderation is in use because of excessive spam. Your comment may not appear immediately.

  •