UCMA 2.0 - Part 1.6 Introduction to SDP

We are almost at the point where our Powershell commandlet will be useful, but first we need to learn about establishing the session protocol. You see, SIP stands for “Session Initiation Protocol”. The key here is the word “Initiation”. Yup, SIP just manages telling so and so that so and so wants to talk to them. It doesn’t actually do the communication.

Imagine this scenario. You want to talk to someone, let’s call him Iggy, who lives on the distant planet of Flurgleblarf. You have no idea where exactly Iggy lives though, so you call your friend Sip over. Sip knows how to figure out who is where on Flurgleblarf and responds that “yes. I can find Iggy”. That’s fine, so you can now figure out where Iggy is.

However, Flurgleblarf is far from a homogenous planet. Well over one hundred mutually unintelligible languages are spoken there and you know only two of them – Flurglussian and Blarfag. If Iggy speaks a different language than these two, you’ll need to find a translator. Luckily, you and everyone on Flurgleblarf knows someone who can speak Sdp. You go to your local Sdp expert and ask him to ask Iggy’s Sdp expert what languages he knows. Even better, you tell your Sdp expert to tell Iggy’s that you can speak Flurglussian and Blarfag. If Iggy can’t speak one of those, he can suggest an alternate language.

So that is basically what SDP is. It stands for “Session Description Protocol” and allows you to setup text, voice, and media communications. For now, we are only going to talk about text communications, but we will discuss the others in future posts.

SDP Parts

SDP consists of different lines, beginning with a letter and an equal sign that describe the media types supported. For a good short description of the information one can express with SDP, see the Wikipedia entry.

Lucky for us, UCMA provides support for SDP so we don’t need to worry about its syntax. The interface that we will need to provide SDP support is IOfferAnswer. In order for our commandlet to partake in media negotiation, it must implement this interface.

public class SendInstantMessageCommand : PSCmdlet, IOfferAnswer

This interface contains five methods. Two of them, HandleReInviteOfferReceived and HandleThirdPartyControlOfferReceived apply only to reinvites and we will not discuss them today. A ReINVITE occurs when one of the parties wishes to change media. An example of this is two parties switching from an IM session to a video session.

The other three methods are of interest to us today.

·         GetOffer – This method is called when you are initiating the INVITE and allows you to specify what type of media to use for the dialog.

·         GetAnswer – This method is called when you are receiving the INVITE and the other party did not specify an offer.

·         SetAnswer – This method is called when you are receiving an INVITE and the other party did specify an offer. In this case your job is to accept the offer or specify that certain media types are not acceptable.

In order to get UCMA to call your IOfferAnswer methods, you must set the OfferAnswerNegotiation property of the SignalingSession before you call BeginParticipate.

_session.OfferAnswerNegotiation = this;

As mentioned before, you do not need to implement HandleReInviteOfferReceived or HandleThirdPartyControlOfferReceived because they only apply to reinvites. We will have the same response to both GetAnswer and GetOffer so let’s create a method that returns a ContentDescription we can use for either one. Below is the code for it, which I will proceed to explain. You will need to add a using statement for System.Net.Mime to get this to compile.

/// <summary>

/// Retrieves the content description for offers and answers

/// </summary>

/// <param name="session">The session</param>

/// <returns>The content description</returns>

private ContentDescription GetContentDescription(SignalingSession session)

{

    IPAddress ipAddress;

    // This method is called back every time an outbound INVITE is sent.

    WriteVerbose("Getting the content description.");

    if (_session.Connection != null)

    {

        ipAddress = _session.Connection.LocalEndpoint.Address;

    }

    else

    {

        ipAddress = IPAddress.Any;

    }

    Sdp<SdpGlobalDescription, SdpMediaDescription> sessionDescription = new Sdp<SdpGlobalDescription, SdpMediaDescription>();

    //Set the origin line of the SDP

    //s, t, and v lines are automatically constructed

    sessionDescription.GlobalDescription.Origin.Version = 0;

    sessionDescription.GlobalDescription.Origin.SessionId = "0";

    sessionDescription.GlobalDescription.Origin.UserName = "-";

    sessionDescription.GlobalDescription.Origin.Connection.Set(ipAddress.ToString());

    //Set the connection line

    sessionDescription.GlobalDescription.Connection.TrySet(ipAddress.ToString());

    SdpMediaDescription mditem = new SdpMediaDescription("message");

    mditem.Port = 5061;

    mditem.TransportProtocol = "sip";

    mditem.Formats = "null";

    SdpAttribute aitem = new SdpAttribute("accept-types", "text/plain");

    mditem.Attributes.Add(aitem);

    //Append the Media description to the Global description

    sessionDescription.MediaDescriptions.Add(mditem);

    ContentType ct = new ContentType("application/sdp");

    WriteVerbose("Created content description");

    return new ContentDescription(ct, sessionDescription.GetBytes());

}

Understanding the ContentDescription Code

In the following sections, let’s break this down into individual pieces that we can understand.

Connection Property

We need to specify the IP address for the connection piece in SDP. If we have a connection associated with our session, we use its IP address. If we do not have one, we play it safe and use IPAddress.Any.

if (_session.Connection != null)

{

    ipAddress = _session.Connection.LocalEndpoint.Address;

}

else

{

    ipAddress = IPAddress.Any;

}

Session Description Helper

This creates the session description helper that we will use to create our SDP response.

Sdp<SdpGlobalDescription, SdpMediaDescription> sessionDescription = new Sdp<SdpGlobalDescription, SdpMediaDescription>();

GlobalDescription Property

The following four statements construct the o= section of the SDP offer, or in other words the origin. The version is the version of SDP and should always be 0. We do not care about the session Id so we set it to 0. The – for the user name indicates we do not care about it. Finally, we set the connection address to the IP address we previously obtained.

sessionDescription.GlobalDescription.Origin.Version = 0;

sessionDescription.GlobalDescription.Origin.SessionId = "0";

sessionDescription.GlobalDescription.Origin.UserName = "-";

sessionDescription.GlobalDescription.Origin.Connection.Set(ipAddress.ToString());

Set the IP Address

This sets the c= line of the SDP offer. The TrySet method makes sure the IP address is recognized but does not throw an exception if it is not.

sessionDescription.GlobalDescription.Connection.TrySet(ipAddress.ToString());

Define Acceptable Media

The SDPMediaDescription class creates perhaps the most important piece of the SDP – stating what types of media we accept. There are a number of valid values for it and what is most important is the two peers agree on the names. “Audio” and “video” are common values but here we will use “message”.

SdpMediaDescription mditem = new SdpMediaDescription("message");

Configure the Port

We will set the port to 5061 because this commandlet will always use Tls. For Tcp, you would want to use port 5060. The transport protocol should almost always be “sip” in the case of messages, though it could be some other protocol. For video and audio, it will most certainly not be sip. The message media type does not have any format information so we can leave that null – though note that we cannot just simply set it to null.

We will set the Formats property to the string null, but these are tokens found at the end of the “m” line. For Rtp, this may be a list of payload types.

mditem.Port = 5061;

mditem.TransportProtocol = "sip";

mditem.Formats = "null";

Set the MIME Type

This is a very important line that states that the MIME type we will use is text/plain.

SdpAttribute aitem = new SdpAttribute("accept-types", "text/plain");

Add Final Session Attributes

These last lines basically add the MIME type description to our media description, then add the media description to our SDP (it is possible to have multiple media descriptions) and then return a new ContentDescription instance that accepts the MIME type of the content description (which should always be “application/sdp”) and the bytes of the session description we just created.

mditem.Attributes.Add(aitem);

//Append the Media description to the Global description

sessionDescription.MediaDescriptions.Add(mditem);

ContentType ct = new ContentType("application/sdp");

GetOffer and GetAnswer Methods

You can now set the method bodies of GetOffer and GetAnswer to the following.

return GetContentDescription((SignalingSession)sender);

SetAnswerMethod

We are now left only with SetAnswer. In this case the client has sent us the offer and we need to respond to it. For the short term, this code will not be called because our commandlet is not set up to be called itself. It only sends messages and cannot receive them. In the future though, this code will be helpful.

For SetAnswer we need to parse the response and see if it supports our “message” content type with the SIP protocol. If for some reason we are unable to parse, we must terminate the session. The code for SetAnswer is the following.

public void SetAnswer(object sender, ContentDescription answer)

{

    SignalingSession session = sender as SignalingSession;

    // Verify that the answer received is consistent

    WriteVerbose("Setting the media answer.");

    byte[] Answer = answer.GetBody();

  if (Answer != null)

    {

        Sdp<SdpGlobalDescription, SdpMediaDescription> sessionDescription = new Sdp<SdpGlobalDescription, SdpMediaDescription>();

        if (!sessionDescription.TryParse(Answer))

        {

            Cleanup();

            Error("CannotParseAnswer", "Unable to parse the answer.");

            return;

        }

        else

        {

            IList<SdpMediaDescription> ActiveMediaTypes = sessionDescription.MediaDescriptions;

            if ((ActiveMediaTypes.Count == 1) &&

                (ActiveMediaTypes[0].MediaName.Equals("message", StringComparison.Ordinal)) &&

                (ActiveMediaTypes[0].Port > 0) &&

                (ActiveMediaTypes[0].TransportProtocol.Equals("sip", StringComparison.OrdinalIgnoreCase)))

            {

                WriteVerbose("Supported media type");

            }

            else

            {

                Cleanup();

                Error("UnsupportedMediaType", "Unsupported media type");

            }

        }

    }

}

Now that I have explained it, this code probably does not look very scary. We first try to parse the SDP and if that fails we terminate the session. We don’t need to worry about the callback here because we don’t care about this session any more. We also do not need to worry about removing it from our dictionary because EndParticipate will fail.

After we have successfully parsed the SDP, we make sure that we define a port, use the “message” media type, and use “sip” as the transport protocol. By using SIP as the transport protocol, we are saying that we will use the SIP MESSAGE message to communicate.

Of course, our commandlet still can’t do much that is interesting. In the next post though, we’ll finally send our message.

UCMABlog.zip