Extending Unified Communications Services of UCMA Bots to PIC Clients: Handling SUBSCRIBE Requests (Part 3 of 5)

Summary:   Learn how to extend Microsoft Lync Server 2010 supported unified communications services that are provided by a trusted Microsoft Unified Communications Managed API (UCMA) bot application to Public Internet Cloud (PIC) clients. Without the extension, the unified communications services are restricted to a limited number of Microsoft Lync 2010 clients in a single enterprise network domain. This number is derived from the CategorySubscriptions property of the presence policy that is set by a Lync Server 2010 administrator. Here, PIC clients can include Windows Live Messenger, Skype, and Yahoo! Messenger.

Programmatically, you learn how to enable a Lync Server 2010 application by using the Microsoft Lync Server 2010 SIP Application API and UCMA. Lync Server 2010 SIP Application API is used to intercept a presence subscription from a PIC client and UCMA is used to send the requesting PIC client the online status of an active bot.

Applies to:   Microsoft Unified Communications Managed API (UCMA) 3.0 Core SDK | Microsoft Lync Server 2010 SDK

Published:   May 2012 | Provided by:   Kurt De Ding and Ajay Soni, Microsoft | About the Author

Contents

  • Handling Presence SUBSCRIBE Requests by PIC Clients

  • Determine Online Status of Bots Enabled for PIC User Access

  • Process SUBSCRIBE Request Using SIP Application Managed API

  • Send Bot’s Online Status to PIC Client Using UCMA

  • Send 200 OK Responses to PIC clients Using SIP Application API

  • Send Unhandled Request Back to Lync Server

  • Additional Resources

Download code  Download code

This is the third in a series of five articles about how to extend the services of a Microsoft Unified Communications Managed API (UCMA) bot application to Public Internet Cloud (PIC) clients by enabling an unlimited number of PIC clients that can subscribe to the presence of the UCMA bot.

Handling Presence SUBSCRIBE Requests by PIC Clients

In Extending Unified Communications Services of UCMA Bots to PIC Clients: Intercepting Presence Requests (Part 2 of 5), we showed how Microsoft Lync Server 2010 routes a SIP message to a Lync Server 2010 application for preprocessing by calling a Microsoft SIP Processing Language (MSPL) script embedded in an application manifest and how the MSPL script intercepts SIP SUBSCRIBE requests, selects the request coming from a PIC client, and then calls the Dispatch function to forward the message to a managed application component for additional processing.

In part 3, you learn how the intercepted SUBSCRIBE request is handled by a managed event handler. The argument of the Dispatch function is the name of event handler implemented by the managed application, which is needed because additional processing of the SUBSCRIBE request involves more than simple message filtering, routing or logging. The workflow appearing in figure 1 shows how the managed event handler is handled throughout the process.

Figure 1. Managed event handler workflow

Managed event handler workflow

In this workflow, two more major decisions must be made when the MSPL script determines that a presence SUBSCRIBE originates from a PIC client: The first is to check whether the request is targeted to a bot and the second is to find out whether the request is a terminating one or not. To make the first decision, the application must maintain the knowledge of the installed bots on the system. If not impossible, this task cannot be easily accomplished in a MSPL script. The second decision will result in sending the online status of a bot to the requesting client directly. Again, this cannot be done by using MSPL only.

To help understand the value of a managed event handler and how it is used to solve current problems, you should consider the following programming tasks that are used to complete managed application requests.

  • For the application to determine whether the request is targeted at an enabled bot, it must parse the message’s To header to see whether the To URI value matches that of any the currently enabled application endpoints that are enabled for Internet access. To determine whether the SUBSCRIBE request is a terminating one or not, it must parse the Expires header to see whether the value is zero or not. These can be done in both MSPL and managed code. In managed code, they are done using the Microsoft Lync Server 2010 SDK Microsoft.Rtc.Sip namespace. For more information, see Process SUBSCRIBE Request Using SIP Application Managed API.

  • For the application to maintain a list of the currently enabled bots that are also enabled for PIC users to access, it can periodically poll Lync Server 2010 to obtain the installed trusted application endpoints. The polling can be done by using the Lync Server Management Shell Get-CsTrustedApplicationEndpoint cmdlet. To do so programmatically, the System.Management.Automation and System.Mangement.Automation.Runspaces namespaces must be used. Because a bot can be enabled or deactivated while the Lync Server 2010 application is running, the polling must be performed at the same time during the application run. One way to achieve the concurrency is to create a separate background thread for managing the bot list. These cannot be done in a MSPL script, but can be run by using managed code. For more information, see Determine Online Status of Bots Enabled for PIC User Access later in part 3.

  • For any non-terminating presence SUBSCRIBE request targeted at an enabled bot enabled for PIC access, the application must send the request sender, as identified by the SIP URI value in the From header, a NOTIFY message to return the online presence status of the bot. A non-terminating request is one where the Expires header has a value greater than zero. Because this must involve sending application-constructed SIP messages, through page-mode messaging, this can only be implemented in a managed code by using the UCMA Microsoft.Rtc.Collaboration namespace. For more information, see Send Bot’s Online Status to PIC Client Using UCMA later in part 3.

  • For any request accepted for processing, the application must inform the requesting client of the fact by sending a SIP 200 response in the same transaction initiated by the request. In a managed code, you can do this by using the Lync Server 2010 SDK Microsoft.Rtc.Sip namespace. The required SIP response is created from the intercepted SIP request by calling the CreateResponse method on the Request object and dispatched to the requesting client by calling the SendResponse method on the ServerTransaction instance. Both the Request and ServerTransaction objects are passed in through the RequestReceivedEventArgs parameter.

  • For a PIC client-initiated presence SUBSCRIBE request not targeted to a bot enabled for PIC access, the application must reroute the request to the server for regular processing. This can be done by using the Lync Server 2010 SDK Microsoft.Rtc.Sip namespace. In a managed code scenario, a new client transaction must be created from the server transaction so that the SendRequest method can be invoked against the client transaction. This is equivalent to calling ProxyRequest in a MSPL script. For more information, see Send Unhandled Request Back to Lync Server later in part 3.

Determine Online Status of Bots Enabled for PIC User Access

Bots in a Lync Server 2010 deployment correspond to UCMA applications that are trusted by the Lync Server 2010 computer. A trusted UCMA application runs in the context of a trusted application endpoint. A UCMA application becomes a trusted application when it is enabled to establish the necessary trust relationships between the application and the Lync Server 2010 computer. The activation involves specifying a trusted application pool, adding the application to that pool, and registering the application endpoint as an Active Directory Contact object. Activation of a bot amounts to calling the following Lync Server Management Shell PowerShell cmdlets.

Cmdlet

Description

New-CsTrustedApplicationPool

Create a trusted application pool in the Lync deployment.

New-CsTrustedApplication

Register a UCMA application as a trusted application in a trusted application pool.

New-CsTrustedApplicationEndpoint

Register a trusted UCMA server application as an Active Directory Contact object together with a specified SIP URI to make the application routable.

For more information about using PowerShell cmdlets to configure trusted applications, see Application Management Cmdlets.

Once enabled, a trusted UCMA application becomes an additional presentity in an enterprise network. However, it does not challenge authentication, can impersonate any user, can join a conference without appearing in the roster, and runs in a resilient mode. The operational resiliency implies that the presence status of an enabled bot is almost always online. Any bot not enabled can be considered offline. Thus, determination of a bot’s status amounts to verifying if it is enabled or not.

The verification can be done by using the Get-CsTrustedApplicationEndpoint cmdlet. The result is a list of objects of the Microsoft.Rtc.Management.ADConnect.Schema.OCSADApplicationContact type, each representing an Active Directory Contact object that corresponds to a trusted application. Each returned object consists of a collection of name-value pairs. The supported names include “Identity”, “DisplayName”, and “SipAddress.” For more information about name-value pairs of the Contact object, see Set-CsTrustedApplicationEndpoint cmdlet. The result set can be filtered against a given application’s SIP URI. An empty result set means that the specified SIP URI is not enabled and the presence status of the corresponding bot will be offline. Otherwise, the specified SIP URI corresponds to an online bot. Hence, to determine the presence status of any bot, one must obtain and maintain a list of currently enabled trusted application endpoints.

Programmatically, the Get-CsTrustedApplicationEndpoint cmdlet can be invoked by using the help of the System.Management.Automation and System.Management.Automation.RunSpaces namespaces, making it possible to determine the presence status of a bot, given its SIP URI in a managed application. The following C# code example shows how the Get-CsTrustedApplicationEndpoint cmdlet is invoked to retrieve all the enabled application endpoints.

        private void CheckAndUpdateApplicationEndpoints()
        {
            Console.WriteLine("Checking for new application endpoints...");

            InitialSessionState iss = InitialSessionState.CreateDefault();
            iss.ImportPSModule(new[] { "Lync" });

            using (Runspace myRunSpace = RunspaceFactory.CreateRunspace(iss))
            {
                myRunSpace.Open();

                // Execute the Get-CsTrustedApplication cmdlet.
                using (System.Management.Automation.PowerShell powershell = System.Management.Automation.PowerShell.Create())
                {
                    powershell.Runspace = myRunSpace;
                    powershell.AddCommand("Get-CsTrustedApplicationEndpoint");

                    Collection<PSObject> results = null;
                    Collection<ErrorRecord> errors = null;
                    try
                    {
                        results = powershell.Invoke();
                        errors = powershell.Streams.Error.ReadAll();
                    }

                    catch (Exception ex)
                    {
                        // An error occurred running the cmdlets.
                        // TODO (Left to the implementer): Error handling code.
                        Console.WriteLine("Error: {0}", ex.Message);
                        throw;
                    }
                    
                    if (errors.Count != 0)
                    {
                        // Errors were reported by the cmdlets.
                        // TODO (Left to the implementer): Error handling code.
                    }

                    // Create a new list with the app endpoints
                    Dictionary<string, string> updatedAppEndpoints = new Dictionary<string, string>();

                    // Add new endpoints...
                    foreach (PSObject result in results)
                    {
                        string endpointSipUri = (string) result.Members["SipAddress"].Value;
                        endpointSipUri = endpointSipUri.ToLowerInvariant();

                        // Check if the endpoint is enabled for SIP
                        bool enabledForPIC = (bool)result.Members["EnabledForInternetAccess"].Value;
                        if (enabledForPIC == false)
                        {
                            Console.WriteLine(String.Format("SipAddress: {0} NOT Enabled for PIC. Ignoring...", endpointSipUri));
                            continue;
                        }

                        string displayName = endpointSipUri; // If DisplayName is Empty or null, then return sipaddress
                        
                        if (result.Members["DisplayName"] != null)
                        {
                            string displayNameToSet = (string)result.Members["DisplayName"].Value;

                            if (!String.IsNullOrEmpty(displayName))
                            {
                                displayName = displayNameToSet;
                            }
                        }

                        Console.WriteLine(String.Format("Adding SipAddress: {0}. DisplayName: {1}", endpointSipUri, displayName));

                        updatedAppEndpoints.Add(endpointSipUri, displayName);
                    }

                    // Now replace the old list with the new list
                    // This is sample code. In production you would want to apply the appropriate thread synchronization techniques

                    this.appEndpoints = updatedAppEndpoints;
                }
            }
        }

Before invoking the Get-CsTrustedApplicationEndpoint cmdlet, a System.Management.Automation.Runspaces.RunSpace instance is created and opened against the Lync Server Management PowerShell module, which is imported using an instance of System.Management.Automation.Runspaces.InitialSessionState. In addition, a System.Management.Automation.PowerShell instance must be created, the RunSpace object assigned to the PowerShell instance, and the cmdlet name added to the PowerShell object.

A successful invocation of the Get-CsTrustedApplicationEndpoint cmdlet returns all the currently enabled application endpoints, as a collection of System.Management.Automation.PSObject. The Members property on a PSObject objet is a collection of name-value pairs. The Members[“SipAddress”].Value property yields the SIP URI of an enabled bot and psObject.Members[“DisplayName”].Value is used to obtain the display name of the bot. For each enabled application endpoint found, the SipAddress and DisplayName values are cached in an associated array (this.appEndpoints) of the Dictionary<string, string> type.

Because new bots are enabled and deactivated at any time, the CheckAndUpdateApplicationEndpoints routine should be executed periodically to include any newly enabled bots and to remove any recently deactivated bots. This can be done by repeated calls to the routine appearing in the previous example, one time every five minutes, in a loop. An implementation is shown in the following C# code example.

        private void EndpointChecker()
        {
            AutoResetEvent quit = new AutoResetEvent(false);
            WaitHandle[] waitHandles = new WaitHandle[1];
            waitHandles[0] = quit;

            CheckAndUpdateApplicationEndpoints();

            while (true)
            {
                int result = WaitHandle.WaitAny(waitHandles, new TimeSpan(0, 5, 0));

                // Wake up
                CheckAndUpdateApplicationEndpoints();
            }
        }

To maintain concurrency, this loop should be run in a separate thread. The following C# code example shows how to create a background thread to loop through the EndpointChecker routine that appears in the previous example.

        public void StartManager()
        {
            backgroundThread = new Thread(new ThreadStart(EndpointChecker));
            backgroundThread.Start();

            // This is sample code. In production you would want to apply 
            // the appropriate thread synchronization techniques
        }

To check whether a bot of a given SIP URI is online, the application verifies if the SIP URI is in the cached list of the enabled application endpoints. The following C# routine shows the verification process.

        public bool IsSipUriApplicationEndpoint(string sipUri)
        {
            string sipUriToFind = sipUri.ToLowerInvariant();

            Console.WriteLine("Looking up SipUri: " + sipUriToFind);
            // Look up the sipUri
            if (appEndpoints.ContainsKey(sipUriToFind))
            {
                Console.WriteLine("Found Application Endpoint [sipUri]: " + sipUriToFind);
                return true;
            }
            else
            {
                return false;
            }
        }

In the accompanying application project, the previous features are all implemented in the AppEndpointManager class. The complete code listing to determine the online presence of bots is shown in the AppEndpointManager.cs file.

Process SUBSCRIBE Request Using SIP Application Managed API

To process a SUBSCRIBE message by using Microsoft Lync Server 2010 SIP Application Managed API, a managed application must provide an event handler of the RequestReceivedEventHandlertype. The name of this event handler must be supplied as the input parameter of the MSPL Dispatch function when it is called from a MSPL script. This event handler is the entry point into the managed application from the associated MSPL script. In the MSPL script presented earlier (or elsewhere), the event handler is named OnSubscribe and has the following signature:

public void OnSubscribe(object sender, RequestReceivedEventArgs e);

It must be declared as public, because it is to be called from outside the application’s process boundary. When invoked, the MSPL script passes along the intercepted SIP message and its associated transactional context in the second parameter of the RequestReceivedEventArgs type. The managed application retrieves the SUBSCRIBE request and the associated transaction by reading the Request and ServerTransaction properties, respectively, on the RequestReceivedEventArgs object.

When the OnSubscribe event handler is called, the MSPL script has already determined that the intercepted SIP request is a presence SUBSCRIBE initiated by a PIC user. What the managed event handler must do additionally is to determine whether the SUBSCRIBE request is targeted to an enabled bot or not. It should accept and process the request if the target is an enabled bot. Otherwise, it should leave the request unchanged and forward it back to the server for ordinary processing.

When the OnSubscribe event handler is called, the MSPL script has already determined that the intercepted SIP request is a presence SUBSCRIBE initiated by a PIC user. What the managed event handler needs to do further is to determine whether the SUBSCRIBE request is targeted to an enabled bot or not. It should accept and process the request if the target is an enabled bot. Otherwise, it should leave the request unchanged and forward it back to the server for ordinary processing.

To decide, the event handler can extract the target URI from the message’s To header and then compare it to the SIP URI list of the enabled bots maintained by the application. If the To URI is contained in the cached list, the event handler must accept the request and, according to the protocol, issue a SIP response of 200 OK to the requesting client. As a part of accepting the request, the event handler must send the client an Online presence status for the bot as a SIP NOTIFY message, but only if the SUBSCRIBE request is not a terminating one. A PIC client submits a terminating SUBSCRIBE request when it signs out and sets the Expires header value to zero (0). To ensure that the PIC client receives the bot’s Online status, the NOTIFY message must bypass the server and be sent to the client directly. This involves peer-to-peer messaging and is supported in UCMA.

Note

A Lync 2010 client can set the Expires header value to zero when it submits a SUBSCRIBE request as a query, instead of a subscription.

This logic of message processing is implemented in the OnSubscribe event handler on the PresenceSubInterceptorForBot class of the accompanying application project and is shown in the following example.

public void OnSubscribe(object sender, RequestReceivedEventArgs e)
{
    Console.WriteLine("Intercepted a Subscribe Request...");
    PrintMessage(e.Request);

    try
    {
        var headerValue = e.Request.AllHeaders.FindFirst(Header.StandardHeaderType.To).Value.Split(';')[0];
        string sipUriOfBot = ExtractStringFromFirstAngularBracket(headerValue);
        Console.WriteLine("sipUri of bot: " + sipUriOfBot);

        // If sipUriOfBot is for an activated bot, handle the intercepted subscribe request. 
        // Otherwise, sends the request back to the server for normal processing.
        if (appEndpointManager.IsSipUriApplicationEndpoint(sipUriOfBot))
        {
            // Accept the request with a 200 OK response
            this.Send200OkResponse(e);

            // Send notify to return the Online (Open) presence status of the bot,  
            // if the request is not a terminating SUBSCRIBE, i.e., with expires=0. 
            // PIC client sends SUBSCRIBE with Expires = 0 on logout only.
            if (!IsExpiresZero(e))
            {
                // Get the SIP URI of PIC client from the SIP request
                headerValue = e.Request.AllHeaders.FindFirst(Header.StandardHeaderType.From).Value.Split(';')[0];
                string sipUriOfPicClient = ExtractStringFromFirstAngularBracket(headerValue);
                Console.WriteLine("sipUri of PIC client: " + sipUriOfPicClient);

                // Get the Call-ID value from the SIP request
                string callId = e.Request.AllHeaders.FindFirst(Header.StandardHeaderType.CallID).Value;
                Console.WriteLine("callId: " + callId);

                // Get the CSeq header value from the SIP request
                headerValue = e.Request.AllHeaders.FindFirst(Header.StandardHeaderType.CSeq).Value.Split(' ')[0];
                int cseqNum = int.Parse(headerValue);
                string cseq = cseqNum + 1 + " Notify";
                Console.WriteLine("cseq: " + cseq);

                // Send Online Status of Bot to PIC client
                appEndpointManager.SendBotsOnlineStatusToPicClient(
                    sipUriOfPicClient, sipUriOfBot, callId, cseq);
                Console.WriteLine("Done with appEndpointManager.SendBotsOnlineStatusToPicClient");
            }
        }
        else
        {
            // Send the unhandled SUBSCRIBE to the server for normal processing 
            // because the target of this request is not an activated bot.
            Console.WriteLine("The target of the request is not an activated bot.");
            e.ServerTransaction.EnableForking = false;
            e.ServerTransaction.CreateBranch().SendRequest(e.Request);  
        }
                
    }
    catch (Exception ex)
    {
        // When in error, reroute the message back to the server.
        Console.WriteLine(ex.ToString());
        e.ServerTransaction.EnableForking = false;
        e.ServerTransaction.CreateBranch().SendRequest(e.Request);
    }
}



In the previous code example, message parsing starts by calling the Request.AllRequests.FindFirst(Header.StandardHeaderType.To) method. The input parameter is an enumeration field and specifies that the To header be returned. The format of the To header appears in the following example.

To: [“display name” ]<user@domain>[;tag=123456]

“display name” is a human-readable description of the user (user@domain. The tag parameter identifies a specific endpoint of the given SIP URI. The “display name” and tag parts are optional. The tag parameter is set by the server. Assuming the display name will not contain any bracket, the URI value of the To header can be obtained by calling the ExtractStringFromFirstAngularBracket method, which uses common string manipulations as shown as follows.

Note

The From header of a SIP request identifies the sender of the request whereas the To header of a SIP response identifies the sender of the response. A pair of corresponding SIP requests and responses share the same To and From headers.

private string ExtractStringFromFirstAngularBracket(string headerValue)
{
    if (headerValue.Contains("<"))
    {
        int index1 = headerValue.IndexOf('<');
        int index2 = headerValue.IndexOf('>');
        return headerValue.Substring(index1 + 1, index2 - index1 - 1);
    }
    else return headerValue;
}

Once the To URI of the request is obtained, the IsSipUriApplicationEndpoint method on the ApplicationEndpointManager class is called to check whether the target is an enabled bot (For more information, see Determine Online Status of Bots Enabled for PIC User Access). Because the ApplicationEndpointManager class periodically updates the cached list of the enabled application endpoints in a separate thread, the result from IsSipUriApplicationEndpoint can be considered as the most current enabled bots. If the request’s To URI does not refer to an enabled bot, the original request is rerouted back to the server following the same server transaction from which the request is initiated. This rerouting is completed with the help of the ServerTransaction and ClientTransaction objects exposed in the Microsoft.Rtc.Sip namespace. The e.ServerTransaction.CreateBranch().SendRequest(e.Request); statement ensures that the unhandled request continues to be processed by the server as part of the original dialog. In case of exceptions, the request is treated as unhandled and forwarded for the server to process.

If the request’s To URI does refer to an enabled bot, Send200OkResponse is called to inform the client of the successful receipt of the request. For more information, see Send 200 OK Responses to PIC clients Using SIP Application API, discussed earlier in part 3. Furthermore, if the PIC client is not signing out, that is, the SUBSCRIBE request is not terminating, SendNotifyMessageUsingUCMA is invoked to send a NOTIFY message that contains the online status of the bot to the client directly. For more information, see the next section.

Send Bot’s Online Status to PIC Client Using UCMA

In general, when a client subscribes to the presence of one or more remote presentities, it submits a presence SUBSCRIBE request specifying the interested presence data types and their publisher or publishers. A single publisher may be published in a single SUBSCRIBE whereas multiple publishers can be specified in a batched SUBSCRIBE. The request is sent to the server, which then matches the specified publisher or publishers with the registered presentities, collects the presence data that are published by the matched presentities and that are allowed for access by the subscriber, and then sends the client the collected data, as the payload of NOTIFY messages, on behalf of the publishers. The subscriber does not communicate directly with the publishers.

In the case of a PIC client subscribing to the presence of a bot, the server cannot be used to send the client the Online status of an enabled bot. The application must send the presence data directly to the requesting PIC client on behalf of the bot. In UCMA, a SipPeerToPeerEndpoint object can do exactly that. The presence data, contained in a NOTIFY message, can be sent to the PIC client using the BeginSendMessage method exposed by SipPeerToPeerEndpoint. An application can create a SipPeerToPeerEndpoint object by calling the InnerEndpoint property on an established ApplicationEndpoint object. By using ApplicationEndpoint, the application enjoys the benefits of auto-provisioning. The NOTIFY message dispatched in this manner must still be correlated with the intercepted SUBSCRIBE request in that the two messages share the same Call-Id header value. In addition, the source URI of the SUBSCRIBE becomes the target URI of the NOTIFY and, similarly, the target URI of the SUBSCRIBE becomes the source URI of the NOTIFY.

When a PIC client is involved, the presence data must be described according to the Presence Information Document Format (PIDF). The following example shows a PIDF document representing the online or available presence status of a bot. The bot’s SIP URI is “sip:helpdesk@contoso.com” and its display name is “Help Desk”:

<?xml version="1.0" encoding="UTF-8"?>
<presence xmlns="urn:ietf:params:xml:ns:pidf" 
     xmlns:ep="urn:ietf:params:xml:ns:pidf:status:rpid-status"
     xmlns:et="urn:ietf:params:xml:ns:pidf:rpid-tuple" 
     xmlns:ci="urn:ietf:params:xml:ns:pidf:cipid" 
     entity="sip:helpdesk@contoso.com">
<tuple id="0"> 
     <status>
          <basic>open</basic> 
     </status>    
</tuple>
<ci:icon></ci:icon>
 <ci:display-name>Help Desk</ci:display-name>
</presence>

For an offline presence status, which is handled by the Lync Server 2010 computer, the “open” text value of the <basic> element will be replaced by the “closed” text value.

Now, with an understanding of the basic programmatic requirements for sending the online status of a bot to a PIC client, the following example shows how to send the status.

public void SendBotsOnlineStatusToPicClient(string sipUriOfPicClient, string sipUriOfBot, string callId, string cseq)
{
    Console.WriteLine("Sending Bot's Open status to PIC client as a NOTIFY using UCMA...");

    List<SignalingHeader> headers = new List<SignalingHeader>();
    headers.Add(new SignalingHeader("EVENT", "presence"));
    headers.Add(new SignalingHeader("Subscription-State", "active;expires=32792"));
    headers.Add(new SignalingHeader("Call-Id", callId));
    headers.Add(new SignalingHeader("CSeq", cseq));
    Console.WriteLine("App-configured SignalingHeaders:");
    foreach (SignalingHeader h in headers)
    {
        Console.WriteLine(h.Name + ": " + h.GetValue());
    }
    Console.WriteLine("PIC user URI: " + sipUriOfPicClient);
    Console.WriteLine("Bot Uri: " + sipUriOfBot);

    string displayName = this.GetDisplayNameForApplicationEndpoint(sipUriOfBot);
    System.Net.Mime.ContentType contentType = new System.Net.Mime.ContentType("application/pidf+xml");
    System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
    byte[] notifyBody = enc.GetBytes(String.Format(_pidfNotifyText, "open", displayName, sipUriOfBot));
    Console.WriteLine("PIDFNotifyBody =" + System.Text.UTF8Encoding.UTF8.GetString(notifyBody));

    // Target address of the NOTIFY
    RealTimeAddress targetAddress = new RealTimeAddress(sipUriOfPicClient);

    _superP2PEndpoint.EndSendMessage(
        _superP2PEndpoint.BeginSendMessage(sipUriOfBot, MessageType.Notify, targetAddress, contentType, notifyBody, headers, null, null)
        );
    Console.WriteLine("End calling SendBotsOnlineStatusToPicClient!");
}

To send the presence data in a NOTIFY to the PIC client for a bot, as shown as the previous code example, the SIP URI of the PIC client and that of the bot must be specified. In addition, the Call-ID and CSeq header values must be set also. They are extracted or derived from the original SUBSCRIBE request before the method appearing in the previous example is called. An implementation appears in the following example.

    string headerValue = e.Request.AllHeaders.FindFirst(Header.StandardHeaderType.To).Value.Split(';')[0];
    string sipUriOfBot = ExtractStringFromFirstAngularBracket(headerValue);


    // Get the SIP URI of PIC client from the SIP request
    headerValue = e.Request.AllHeaders.FindFirst(Header.StandardHeaderType.From).Value.Split(';')[0];
    string sipUriOfPicClient = ExtractStringFromFirstAngularBracket(headerValue);

    // Get the Call-ID value from the SIP request
    string callId = e.Request.AllHeaders.FindFirst(Header.StandardHeaderType.CallID).Value;

    // Derive the CSeq header value from the SIP request
    headerValue = e.Request.AllHeaders.FindFirst(Header.StandardHeaderType.CSeq).Value.Split(' ')[0];
    int cseqNum = int.Parse(headerValue);
    string cseq = cseqNum + 1 + " Notify";

The callId and cseq values are used to set the Call-ID and CSeq headers, respectively, of the NOTIFY message to be sent. In addition, a Subscription-State header is set with an “active” value to indicate that processing of the SUBSCRIBE request is authorized and an Event header is set with a “presence” value to indicate that the NOTIFY message payload contains presence data. A PIDF document describing the “open” status of the bot is then created. The display name, if any, and the SIP address of the bot are provisioned by the AppEndpointManager class that is discussed earlier. They are specified when the application endpoint is registered as part of the application activation. The NOTIFY message is then sent by calling the BeginSendMessage, together with its corresponding EndSendMessage, on the SipPeerToPeerEndpoint object, which is obtained when the application is initialized. The SIP Uri of the bot (sipUriOfBot) will be set, by the API, in the From header of the NOTIFY message and the SIP Uri of the PIC client (sipUriOfPicClient) will be set in its To header. For more information, see Extending Unified Communications Services of UCMA Bots to PIC Clients: Building and Deploying Application (Part 5 of 5)

Send 200 OK Responses to PIC clients Using SIP Application API

When a client sends a SIP request to the server, it expects one or more SIP responses with appropriate response codes to indicate the status of the request. A response code is a three-digit decimal number. 1xx means that the response provides provisional information about the request. A 2xx response indicates that the request is accepted without errors. 3xx is for redirection. And 4xx, 5xx, and 6xx describe client, server, and global failures, respectively. For more information, see RFC 3261.

Upon receiving a SUBSCRIBE request, the server sends a 200 OK response to the client when it is ready to process the request. The process involves sending back NOTIFY messages to return the requested data. When the SUSCRIBE request by a PIC client is intercepted and handled by an application, it is the responsibility of the application to send the client a 200 OK response, in addition to sending a NOTIFY to return a bot’s Online status.

When a MSPL script dispatches a client request to a managed event handler, the intercepted SIP request and a server transaction initiated by the request are passed along into the event handler. The request is represented by a Request object and the server transaction by a ServerTransaction object. They can be obtained by reading the Request and ServerTransaction properties, respectively, on a RequestReceivedEventArgs instance.

In the managed event handler, a response following a given request must first be created and then configured before it is sent to the initiating client. Creating the response is completed by calling the CreateResponse method on the corresponding Request object. Configuring the response involves adding or modifying certain response headers. Sending the response amounts to calling the SendResponse method on the ServerTransaction object.

The correlation between a SUBSCRIBE request and the following 200 OK response can be shown by reviewing a log excerpt from a Lync tracing file. The outgoing SUBSCRIBE request by a Lync 2010 client appears in the following example.

SUBSCRIBE sip:bob@cpandl.com SIP/2.0   
Via: SIP/2.0/TLS 192.168.0.136:55339
Max-Forwards: 70
From: <sip:alice@contoso.com>;tag=80806ddbdb;epid=16ce6a1da0
To: <sip:bob@cpandl.com.com>
Call-ID: 153dfa4bcf6d4c1d8d4226bcb55be63f
CSeq: 1 SUBSCRIBE
Contact: <sip:alice@contoso.com;opaque=user:epid:2OMA-5poxVOPb7Z4ozfhpAAA;gruu>
User-Agent: UCCAPI/4.0.7736.0 OC/4.0.7736.0 (Microsoft Lync 2010)
Event: presence
Accept: application/msrtc-event-categories+xml, application/xpidf+xml, text/xml+msrtc.pidf, application/pidf+xml, application/rlmi+xml, multipart/related
Supported: com.microsoft.autoextend
Supported: ms-benotify
Proxy-Require: ms-benotify
Supported: ms-piggyback-first-notify
Proxy-Authorization: TLS-DSK qop="auth", realm="SIP Communications Service", opaque="3336BE68", targetname="000DCO2O40DR13.corp.contoso.com", crand="8cb857ca", cnum="31", response="473b30d3c393c86d912228da44f3df1f12ca87fc"
Content-Type: application/msrtc-adrl-categorylist+xml
Content-Length: 463

<batchSub xmlns="https://schemas.microsoft.com/2006/01/sip/batch-subscribe" uri="sip:alice@contoso.com" name=""><action name="subscribe" id="277464912"><adhocList><resource uri="sip:bob@cpandl.com "/></adhocList><categoryList xmlns="https://schemas.microsoft.com/2006/09/sip/categorylist"><category name="state"/><category name="contactCard"/><category name="note"/><category name="services"/><category name="calendarData"/></categoryList></action></batchSub>

The corresponding 200 OK response by the PIC client appears in the following example.

SIP/2.0 200 OK   
ms-user-logon-data: RemoteUser
Authentication-Info: TLS-DSK qop="auth", opaque="3336BE68", srand="55000E58", snum="25", rspauth="33923661afe2d2cb27e52a340cc3dcc44fb438bd", targetname="000DCO2O40DR13.corp.contoso.com", realm="SIP Communications Service", version=4
From: "Alice"<sip:alice@contoso.com>;tag=80806ddbdb;epid=16ce6a1da0
To: <sip:bob@cpandl.com >;tag=0-13c4-4f25d232-f8b6886-2ef6fe8c
Call-ID: 153dfa4bcf6d4c1d8d4226bcb55be63f
CSeq: 1 SUBSCRIBE
Expires: 12538
Via: SIP/2.0/TLS 192.168.0.136:55339;received=65.55.30.195;ms-received-port=39147;ms-received-cid=2FC3600
Contact: <sip:lcs106.cpandl.com:5061;maddr=lcs106.cpandl.com;transport=tls>
Record-Route: <sip:lcsap.msg.cpandl.com:5061;transport=tls;lr;ms-key-info=mACAANAcP55khJXL2d7MAQECAAADZgAAAKQAAKeA5sCEnj01haP4XlWkmke-jWhyE0X22-BXyqQtaxMQlOWwAfTQRWRnd0AePvawuH4gN4AOW0gryD7cKNxXMNn3l3C3rMJymyY8ma9fPOPqd5E8VJwDpNwXCcAGqOc6j3sRrJWLljogR94yKT42ALRCEzjCOkPnKoD_CWvIqzGkKw2PsGqxq4fnuH7js_B4N2jg5bhUj-glyWnf6TYVFhSCTRC5u7gvi5EilQSoy5MtH1-SRQk3J77C-rcmdc6FRfrtLKSFgOfyEGGxlZm6npPGHgQ0EONFQKpXrLfBxlIC8WjfC1q38QKMFqJvqiCDrm3wn-1_2Afmn-S_nkKX4UMw;ms-route-sig=ea_zURepn5BQp2Ib01NRVsTQpoSPmO>
Record-Route: <sip:000dco2o40ed1.corp.contoso.com:5061;transport=tls;lr>
Content-Length: 0
Record-Route: <sip:000dco2o40dr1.corp.contoso.com:5061;transport=tls;ms-fe=000DCO2O40DR13.corp.contoso.com;lr>
Record-Route: <sip:000dco2o40pl1.corp.contoso.com:5061;transport=tls;ms-fe=000DCO2O40FE13.corp.contoso.com;opaque=state:F;lr>
Record-Route: <sip:000dco2o40dr1.corp.contoso.com:5061;transport=tls;ms-fe=000DCO2O40DR13.corp.contoso.com;lr;received=10.37.38.219;ms-received-cid=5300>
Record-Route: <sip:sipfed.contoso.com:443;transport=tls;opaque=state:Ci.R2fc3600;lr;ms-route-sig=dapuUbZBL-6t9G6b4bNSdFngsMhLiM1pr4X1svNkOenxqvECATpRRH3wAA>
ms-edge-proxy-message-trust: ms-source-type=AuthorizedServer;ms-ep-fqdn=000dco2o40ed1.corp.contoso.com;ms-source-network=publiccloud;ms-source-verified-user=verified

Looking at the Call-ID, CSeq, From, and To headers, we can determine that the 200 OK response follows the SUBSCRIBE request and is from the same transaction.

Headers in SUBSCRIBE request

Correlated headers in 200 OK response

From: <sip:alice@contoso.com>;tag=80806ddbdb;epid=16ce6a1da0

From: "Alice"<sip:alice@contoso.com>;tag=80806ddbdb;epid=16ce6a1da0

To: <sip:bob@cpandl.com.com>

To: <sip:bob@cpandl.com >;tag=0-13c4-4f25d232-f8b6886-2ef6fe8c

Call-ID: 153dfa4bcf6d4c1d8d4226bcb55be63f

Call-ID: 153dfa4bcf6d4c1d8d4226bcb55be63f

CSeq: 1 SUBSCRIBE

CSeq: 1 SUBSCRIBE

Note

The To and From headers in both the request and response are identical. The To header in the response has a tag parameter appended. The first observation shows that the From header of a SIP message does not necessarily indicate a sender (or source) of the message. Similarly, the To header should not be casually associated with the receiver (or target) of a message. The second observation shows that only the message-sending client can set the tag or other endpoint-specific identifier.

When our event handler sends a 200 OK response to an intercepted SUBSCRIBE request, ensure that these headers follow the same patterns shown here. The following C# example shows one implementation of sending a 200 OK response to the sender of the SUBSCRIBE request on behalf of the intended receiver of the request.

        void Send200OkResponse(RequestReceivedEventArgs e)
        {
            Console.WriteLine("\r\nBegin to send 200 OK response...");

            try
            {
                // Create a Response instance from the corresponding request.
                // It is not necessary to set up any response header values because 
                // the server will set them properly. If needed, application-specific headers,
                // such as diagnostic-related headers, may be added by an application.
                Response response = e.Request.CreateResponse(200);

                PrintMessage(response);

                e.ServerTransaction.SendResponse(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine("ServerTransaction.SendResponse(r) erred: \r\n{0}", ex.Message);
                if (ex.InnerException != null)
                    Console.WriteLine("InnerException:\r\n{0}", ex.InnerException.Message);
            }
        }

 

In the Lync Server 2010 SIP Application API, a response is created by calling the CreateResponse method on an incoming request object to which the response is intended. The resultant response contains the same To, From, Call-Id, and Cseq headers as incoming the Request object. An application should refrain from setting them explicitly to ensure that the response is to follow the intercepted request. In general, the application should let the server set any other headers. The response is sent by calling the SendResponse method on the server transaction initiated by the request.

Send Unhandled Request Back to Lync Server

When a Lync Server 2010 application decides not to handle an intercepted message, it should reroute the request back to the server. The same can be said of the case when the server application cannot complete the event handling due to exceptions thrown at the run time.

To reroute a request to the server in a managed Lync server application, the SendRequest method is invoked on a ClientTransaction object. The Lync Server 2010 SIP Application API supports the creation of a client transaction by only calling CreateBranch on ServerTransaction. This means that the Microsoft.Rtc.Sip namespace cannot be used to begin a client-side request. Each ClientTransaction object can be used to send one request, although multiple ClientTransaction objects can be created from a ServerTransaction object. The latter scenario is known as messaging forking, where one request is sent to multiple endpoints at the same time. Forking is not discussed in this article and is disabled in the application project that is discussed in Extending Unified Communications Services of UCMA Bots to PIC Clients: Creating Application Project (Part 4 of 5).

Additional Resources

For more information, see the following resources:

About the Author

Kurt De Ding is a Senior Programming Writer in the Microsoft Office Content Publishing (UA) group. Ajay Soni is a Senior Program Manager with Engineering, Community & Online (ECO) of Microsoft Services.