Using the Microsoft Office Live Communications Server API

 

Wayne Freeze
Studio B

November 2003

Applies to:
    Microsoft® Office Live Communications Server 2003

Summary: Learn about the Live Communications Server API, including how to build filters using the Microsoft Session Initiation Protocol (SIP) Processing Language and how to create Microsoft Visual C# applications using the Microsoft.Rtc.Sip namespace. Further, acquire information and tips on how to build Live Communications Server applications. (26 printed pages)

Contents

Introduction
Basic Concepts
Benefits of Live Communications Server
Introducing Microsoft SIP Processing Language
Building Managed Server Applications
Programming Issues
Conclusion

Introduction

Microsoft Office Live Communications Server 2003, Standard Edition, facilitates communications between two or more real-time communications applications. While Live Communications Server 2003 meets the needs of many organizations, some organizations require the ability to modify how the server responds to requests. The Live Communications Server application programming interface (API) provides the necessary tools to do this.

Centralized servers are becoming very important when it comes to establishing real-time communications networks. Microsoft has developed a real-time communications server called Microsoft® Office Live Communications Server 2003, Standard Edition, which is based on the Internet standard for name lookup (RFC 2543). RFC 2543, also known as the Session Initiation Protocol (SIP), defines how applications can use the server to retrieve a set of attributes associated with an e-mail address or a telephone number.

In order to provide the flexibility developers need to implement complex real-time communications applications, Microsoft has developed the Live Communications Server API, which gives developers a method to override the default processing by Live Communications Server.

This article explores the fundamentals of how to create applications using the Live Communications Server API to control how requests are processed on the server. Included in this article are an overview of how Live Communications Server operates, the types of functions that the server can perform, an introduction to the Microsoft SIP Processing Language (MSPL), a discussion of the .NET class libraries for the C programmer, and practical instructions about how to create and use both MSPL filters and C programs using the Live Communications Server API.

Basic Concepts

As businesses adopt peer-to-peer real-time applications, the limitations of peer-to-peer communications have become apparent. As long as each peer remains at its current location, everything works fine. Other peers can determine whether they are online by attempting to communicate with them directly. But as soon as one client moves to a different location, peep-to-peer applications begin to lose their capabilities.

The other clients only know that one of their contacts is offline. They do not know the new location of the client that moved until the client explicitly communicates that information from the new location.

The only real solution to this problem is to introduce a centralized server that performs two main functions: storing the current location of a client, and storing a common collection of information for the client that can be retrieved from any devices that the client uses.

Real-Time Applications

In the simplest case, a real-time session is established when one computer contacts the other computer and issues an INVITE request. The second computer responds with OK, indicating that it is ready to proceed with the communications session.

However, in order to issue the INVITE request, the first computer needs to know the address of the second computer. Note that in this situation, an e-mail address is not sufficient since the INVITE request must be sent to a physical address. Instead, the first computer must know the DNS name or IP address of the second computer in order to send the INVITE request.

If the individuals who wish to communicate in real time use multiple devices (such as an office computer and a home computer), it becomes more difficult to locate each other. The best solution to this problem is to provide a server that is common to both users to hold their location information. Such a server works in two phases. Each individual logs on to the server to accept real-time communications. In addition to indicating that they are online, users also have their IP addresses automatically saved on the server. Then, when users wish to communicate with that person, all the server needs to do is to look up that person's information to find out whether the person is online and what the IP address is.

SIP is the protocol used to communicate with the server, and the server is called a SIP server, or in this case, Live Communications Server.

SIP Server

A SIP server performs three main functions:

  1. It acts as a proxy server, which processes requests and optionally modifies them before sending them on.
  2. The SIP server can act as a registrar server, which keeps track of users from which the registrar server has received REGISTER requests.
  3. A SIP server returns the current address of a user agent when it receives an INVITE request.

The SIP server maintains a registrar database, which keeps track of all registered users. Each registered user is identified by a Uniform Resource Identifier (URI), which in this situation is typically an e-mail address or telephone number. The SIP server also tracks the address where the user is currently online. This information is used to respond to INVITE requests with the most current address of the user.

In addition to the user's URI and current address, the SIP server can store other information in a centralized location. For example, users who use real-time communications from multiple devices can share a single list containing their common contacts with all of the devices.

Client-to-Client Communications

In a typical real-time session without a SIP server, the clients communicate with each other directly. Each client has a user agent, which is the interface the client computer presents to the Internet. All communications occur between the agents. Figure 1 shows the communications between two user agents attempting to communicate with each other directly.

Figure 1. Two clients talking to each other through their user agents.

User Agent 1 initiates the communications session by sending a SIP INVITE request to User Agent 2. User Agent 2 replies with a response code of 100, which means that it has received the request and is processing it. This processing involves informing the user of the request and waiting until the user chooses to accept or deny the session. When the user accepts the invitation, User Agent 2 then sends a response code of 200 to User Agent 1, indicating it's ready to start a real-time session. User Agent 1 sends a SIP ACK, and then the real-time communications session begins.

Later, User Agent 1 sends a SIP BYE request to User Agent 2, to which User Agent 2 replies with a response code of 200, thus terminating the real-time session. (Note that either party can send the SIP BYE request.)

SIP Server Communications

Figure 2 shows the role a SIP server plays in establishing a real-time communications session. Instead of User Agent 1 sending the INVITE request directly to User Agent 2, the INVITE request is sent to the SIP server. The SIP server in this case acts as a proxy and uses the information stored on the server to locate User Agent 2, and then passes on the INVITE request.

Figure 2. A SIP server acts as a proxy to establish the real-time communications session.

This process repeats itself as the server accepts and passes along messages from both user agents until the real-time communications session is established. The real-time communications stream (for audio and video) occurs directly between the two user agents because the SIP server is only needed to communicate the signaling traffic. For instant messaging traffic, the traffic goes through the SIP server for archiving text communication.

Routing and Forking

There are two basic types of SIP server applications: routing and forking.

  • A routing application takes an INVITE request and determines where the request should be sent.
  • A forking application takes an INVITE request and forwards it to multiple locations.

In both cases, the responses from the multiple locations are returned to the server, which in turn sends them on to the originating client. Because of the way SIP applications work, they are often referred to as filters.

Benefits of Live Communications Server

Besides the obvious benefit of tracking the location of various clients, Live Communications Server provides the tools to build applications that perform functions such as the ones below.

Intelligent Agents

Live Communications Server can be used to create a rules-based engine that processes incoming requests and determines how the calls should be routed. For instance, one rule might automatically route all calls that arrive outside normal business hours to the voice mail system, except for those that come from a predefined list of contacts.

Another rule might define a virtual contact and automatically reroute any calls placed to the virtual contact to the first non-busy contact found in a pool of contacts. This technique is ideal for support organizations that wish to route customers to the first available support person.

Content Logging

With the need to keep accurate records of most modern business communications, it makes sense to maintain a log of all real-time communications. Live Communications Server is ideally positioned to implement this type of application.

The server can record information about each new session when it is initialized. Once the session is initialized, an application has the option to remain in the middle of all communications. This means that it is quite easy to log each message sent during each real-time session.

Filtering

In the same way that each message can be logged during a real-time session, each message can be filtered for unacceptable content. Each message can be examined for a list of unacceptable words, and if an unacceptable word is found, it can be removed from the message.

Virus Scanning

Another possible way to use Live Communications Server is to allow the server to intercept all requests for file transfers. Rather than the file being transmitted directly to the client, it is transmitted to an application upon the server. Then a virus-scanning program is used to examine the data for potential viruses. If the file is virus free, then the file can be forwarded to the intended recipient.

Building Live Communications Server Applications

Live Communications Server offers two ways to build server applications. Applications can be built using a special scripting language called Microsoft SIP Processing Language (MSPL) or written in any Microsoft .NET-compatible programming language, including Microsoft Visual C#® .NET and Microsoft Visual Basic® .NET.

Introducing Microsoft SIP Processing Language

MSPL is a scripting language designed to simplify filtering and dispatching SIP messages. While MSPL resembles C, many features of C are not present in MSPL. Specifically, MSPL doesn't support arithmetic, type declarations, or loop statements other than foreach. However, MSPL includes a number of built-in constants, variables, and functions that can simplify building message filters.

Statements

Only a few C statements are supported in MSPL. These include the assignment statement (=), the switch statement, the if statement, and a foreach statement that works only with the built-in MSPL objects. In addition, it is possible to define functions within the script. These functions may have zero or more parameters and return a single value.

Variables

MSPL supports five data types: string, integer, float, bool, and collection. While MSPL doesn't support explicit variable declarations, a variable is automatically created when a value is assigned to it with either an assignment statement or a foreach statement. The type of the variable is determined by the value assigned to it. All variables are global to the entire routine or application body. However, variables declared in the application body or routine cannot be referenced by another routine.

If you have variables of different types, MSPL attempts to convert from one type to another. When converting bool values to numeric values, 0 is false, while 1 is true. When converting numeric values to bool, nonzero values are true, while 0 is false.

Built-in Objects, Variables, and Functions

To support scripting programs, MSPL includes a number of objects, variables, and functions that provide information about the messages received by the server and the environment that the server is running in. The objects and functions also provide high-level messaging tools that reduce the total amount of code required to build a server application.

Capturing Debugging Information

Because the MSPL program runs within Live Communications Server, there isn't a convenient way to determine how the program is working. The only practical option is to periodically call the Log function and include any information deemed useful.

Here is the format of the Log function. The name parameter determines where to write the log information. Specifying "Event" as the value for the Name parameter records the information in the event log. Specifying "Error" writes the information in the error log. Specifying "Debug" writes the information into a special debug log file.

void Log(
  string Name,      // Event, Error, Debug
  bool Detail,      // false => selected message headers will be recorded
  string String1,   // first line of the custom message
  string String2,   // second line of the custom message
   ..., 
  string StringN    // nth line of the custom message
);

If Detail is false, the application URI, the date and time, and the Method, Request-Uri, From, and To message header values are recorded in the log file. If the Detail parameter is true, then it writes the entire message except for the Content value to the log file.

String1 through Stringn are user-defined messages that are recorded in the log file. Each value is written on a separate line.

Running the SPLogger.exe utility program retrieves the information from the debug log file.

Application Manifest

Every MSPL program must be enclosed in an application manifest. The application manifest is an XML document that communicates information about the SIP application to Live Communications Server. It also allows you to determine which SIP messages your application processes.

Each application manifest has the following structure. The namespace alias (rtc in the following example) precedes each of the element names.

<?xml version="1.0" ?>
<rtc:applicationManifest
   appUri="http://www.example.com/myApplicationLocation"
   xmlns:rtc="http://schemas.microsoft.com/rtc/2003/05">
<rtc:requestFilter methodNames="INVITE,MESSAGE" 
                        strictRoute="false" 
                        registrarGenerated="true"
                        domainSupported="true"/ >
<rtc:responseFilter reasonCodes="NONE" />
<rtc:proxyByDefault action="true" />
<rtc:scriptOnly />
<rtc:splScript><![CDATA[

// ...
// my MSPL message filter script here
// ...

   ]]></rtc:splScript>
</rtc:applicationManifest>

The root element in this application manifest is applicationManifest. This element contains two main attributes: appUri and xmIns. While the appUri attribute must uniquely identify the application to the server, it has meaning beyond that. In other words, it need not point to a real location. The xmlns attribute is qualified by the namespace alias and always refers to the URI specified in the example.

The requestFilter element defines which SIP request messages this program processes. In addition to the standard set of message types, NONE can be specified to indicate that no request messages be processed, while ALL means that all messages should be passed to the program.

If the registrarGenerated attribute is true, the application sees requests generated by the endpoint. If the strictRoute attribute is true, the application sees requests with a strict route that cannot be changed. If the domainSupported attribute is true, the only requests that the application sees are those from a domain supported by the server.

The responseFilter element defines the reason codes that this program handles. Reason codes are 1XX, 2XX, 3XX, 4XX, 5XX and 6XX, plus NONE and ALL.

When the action attribute of the proxyByDefault element is true, any message that was not handled by the program the server automatically handles.handles A message is considered handled if a call was made to any of the following functions while in the program: Challenge, Dispatch, EndFork, ProxyRequest, ProxyResponse, or Respond.

The scriptOnly element indicates that the entire program is contained within the application manifest and no managed code is called. This also means that the Dispatch function cannot be called from within the script.

The splScript element contains a CDATA element that contains the MSPL statements.

A Simple Routing Application

The following application manifest uses MSPL to examine requests for the user Wayne@Example.com and replace it with WFreeze@Example.com. The manifest specifies http://Example.com/rtc/Wayne in the appUri element to uniquely identify this application.

<?xml version="1.0"?>
<rtc:applicationManifest
    appUri="http://Example.com/rtc/Wayne"
    xmlns:rtc="http://schemas.microsoft.com/rtc/2003/05">
    <rtc:requestFilter methodName="INVITE"
                       strictRoute="false"
                       registrarGenerated="true"
                       domainSupported="true" />
    <rtc:responseFilter reasonCodes="NONE" />
    <rtc:proxyByDefault action="true" />
    <rtc:scriptOnly />
    <rtc:splScript><![CDATA[

    Log("Debug", true, "Checking request");
    if (sipRequest.RequestUri == "Wayne@Example.com")
        {
        Log("Debug", false, "Found Wayne, changing to WFreeze");
        ProxyRequest(("sip:WFreeze@Example.com");
        }
        
    ]]></rtc:splScript>

</rtc:applicationManifest>

The requestFilter element traps only INVITE messages, while the responseFilter element instructs the server to ignore all responses for this application. The proxyByDefault element instructs the server to process any messages not specifically handled by the application. The scriptOnly element informs the server that the entire application is contained within the application manifest.

The MSPL code is very simple. It logs the fact that it is checking the request. Then it checks the Request.RequestUri property to see whether the request should be sent to wayne@example.com. If the request is directed at wayne@example.com, then it changes the RequestUri property to send the request to wfreeze@example.com.

Exploring Routing.am

The routing.am application is the default message handler for Live Communications Server. It examines each request to determine the best endpoint with which to establish the connection. Extensive use is made of the Log function to record the various status conditions while running the program.

The routine begins by getting the user and host information from the request. Next, it looks at the endpoint id (EPID) value to see whether an endpoint value has already been determined.

<?xml version="1.0"?>
<r:applicationManifest
 r:appUri="http://www.microsoft.com/RTC/DefaultRouting"
 xmlns:r="http://schemas.microsoft.com/rtc/2003/05">
  <r:requestFilter methodNames="INVITE,MESSAGE,INFO,REFER,ACK,BYE,OTHER"
                   strictRoute="false"
                   registrarGenerated="false"
                   domainSupported="true"/>
  <r:responseFilter reasonCodes="NONE"/>
  <r:proxyByDefault action="true"/>
  <r:scriptOnly/>
  <r:splScript><![CDATA[
//
// This script handles default routing of requests to Live Communications Server. 
// It looks up all the registered endpoints for the To: user@host and tries
// to pick the best endpoint to route to based on:
//  EPID
//  Availability
//  Activity
//  AgeOfPresence (i.e., how long since last set presence)
//
// Endpoints with no presence or an availability less than 100, or
// no routing information (e.g., set presence without register), are
// not considered.
//

Log( "Debug", 1, "we have a request - ", sipRequest.Method );

//
// Build the user@host from the To: header
//
toUri = GetUri( sipRequest.To );
toUserAtHost = Concatenate( GetUserName( toUri ), "@", GetHostName( toUri ) );

//
// Determine whether this request is already asking for a specific EPID via a
// parameter in the To: header.
//
requestEPID = GetParameterValue( sipRequest.To, "EPID" );

//
// Now, loop over all the endpoints for the To: user@host
//
// sawAtLeastOneEndpoint = false;
bestEPID = "";
bestAgeOfPresence = 0x7FFFFFFF;
bestAvailability = 0;
bestActivity = 0;
bestContactInfo = "";
Log( "Debug", 1, "EPID - ", requestEPID );
Log( "Debug", 1, "toUserAtHost - ", toUserAtHost );
foreach (dbEndpoint in QueryEndpoints( toUserAtHost, true )) {
   // sawAtLeastOneEndpoint = true;
   Log( "Debug", 1, "    endpoint.EPID - ", dbEndpoint.EPID );
   Log( "Debug", 1, "    endpoint.HasPresence - ", dbEndpoint.HasPresence );
   Log( "Debug", 1, "    endpoint.Availability - ", dbEndpoint.Availability );
   Log( "Debug", 1, "    endpoint.Activity - ", dbEndpoint.Activity );
   Log( "Debug", 1, "    endpoint.AgeOfPresence - ", dbEndpoint.AgeOfPresence );
   Log( "Debug", 1, "    endpoint.ContactInfo - ", dbEndpoint.ContactInfo );

   //
   // First, see whether this endpoint supports the method in the request
   //
   if (!SupportsMethod( sipRequest.Method, dbEndpoint.StandardMethods,
      dbEndpoint.ExtraMethods )) {
      //
      // Skip this endpoint, as it can't handle the method on this request
      //
      Log( "Debug", 1, "        * skipped because of method" );
      continue;
      }

   if (requestEPID != "") {
      if (requestEPID == dbEndpoint.EPID) {
         //
         // Request already targeted at a specific EPID that can handle the method,
         // so use this endpoint
         //
         Log( "Debug", 1, "        * matched EPID" );
         bestContactInfo = dbEndpoint.ContactInfo;
         break;
         }
      else {
         //
         // Request targeted at a specific EPID, but does not match this endpoint
         // Skip this endpoint.
         //
         Log( "Debug", 1, "        * skipped because of EPID" );
         continue;
      }
   }

   if (!dbEndpoint.HasPresence ||
      dbEndpoint.Availability < 100 ||
      dbEndpoint.ContactInfo == ""
      ) {
      //
      // Skip this endpoint if it does not have presence information
      // or its availability is "offline" or if it does not have
      // route information in the ContactInfo field
      //
      Log("Debug",1, "        * skipped because of presence, activity or contactinfo");
      continue;
   }

   if (dbEndpoint.Availability < bestAvailability) {
      //
      // Skip this endpoint if it can't win on availability
      //
      Log( "Debug", 1, "        * skipped because of availability" );
      continue;
      }

   if (dbEndpoint.Availability == bestAvailability) {
      if (dbEndpoint.Activity < bestActivity) {
         //
         // Skip this endpoint if its activity is
         // less than the one we are tied with on availability
         //
         Log( "Debug", 1, "        * skipped because of activity" );
         continue;
         }

      if (dbEndpoint.Activity == bestActivity) {
         if (dbEndpoint.AgeOfPresence > bestAgeOfPresence) {
            //
            // Skip this endpoint if we tied on availability
            // and activity but this endpoint has "older"
            // presence information
            //
            Log( "Debug", 1, "        * skipped because of age of presence" );
            continue;
            }
      }

      //
      // Here if we tied on availability but activity is better or
      // we tied on activity too and presence is "newer".
      //
   }
   //
   // Here if we are more "available" than our current best endpoint
   //

   bestEPID = dbEndpoint.EPID;
   bestAvailability = dbEndpoint.Availability;
   bestActivity = dbEndpoint.Activity;
   bestAgeOfPresence = dbEndpoint.AgeOfPresence;
   bestContactInfo = dbEndpoint.ContactInfo;
   Log( "Debug", 1, "        *** new best contact" );
   }


//
// See whether we found an endpoint to proxy to.
//
if (bestContactInfo == "") {
/* Uncomment this block of code and the two assignments to sawAtLeastOneEndpoint
   above if you want to run a UAC application before this one and have this code
   route your messages to the correct home server
   if (!sawAtLeastOneEndpoint) {
      homeServer = QueryHomeServer( toUserAtHost );
      if (!EqualString( homeServer, FQDN, true )) {
         Log( "Debug", 1, toUserAtHost, " is homed on ", homeServer );
         newRequestUri = Concatenate( sipRequest.RequestUri, ";maddr=", homeServer );
         Log( "Debug", 1, "Request is being routed to ", newRequestUri );
         AddHeader( "MS-LBVIA", FQDN );
         ProxyRequest( newRequestUri );
         return;
         }
     }
 */
Log("Debug",1,"Responding 480 - temporarily unavailable as no suitable endpoint found"
   );
Respond( 480, "Temporarily Unavailable" );
   }
else {
    if (requestEPID == "") {
       Log( "Debug", 1, "Adding missing EPID '", bestEPID, "' to To header" );
       SetParameterValue( "To", "epid", bestEPID );
       }

    Log( "Debug", 1, "Proxying request to - ", bestContactInfo );
    ProxyRequest( bestContactInfo );
    }

return;
]]></r:splScript>
</r:applicationManifest>

Next, the code loops through each endpoint associated with the user and host information extracted from the request. An endpoint that doesn't meet the criteria specified is skipped.

The first criterion checked in the endpoint is whether the endpoint supports the method specified in the request. If it doesn't, then this endpoint is skipped.

Next, if the request specified this specific endpoint, then it uses the contact information from this endpoint; otherwise, this endpoint is skipped.

Finally, the endpoint's Availability, Activity, and AgeOfPresence properties are compared with the values for the current best endpoint. If the new endpoint's information is older than the current best endpoint, then the new endpoint is skipped. Otherwise, the information from the new endpoint is saved as the current best endpoint.

Once all of the endpoints have been checked, the information from the best endpoint is saved in the request header and the request is then routed to the new endpoint.

Building Managed Server Applications

Although MSPL is a good language for building most filters, MSPL cannot call external routines or perform any calculations. And you cannot use Web services or a database. In these cases you need to use a comprehensive language such as Visual C .NET or Visual Basic .NET. In order to write Live Communications Server applications in a .NET-compatible programming language, a complete set of managed classes is provided in the Microsoft.Rtc.Sip namespace. These classes are discussed in the following sections.

ClientTransaction

A ClientTransaction class contains a SIP client transaction object located on a SIP proxy. This class is derived from the Transaction class and inherits all of its properties, methods, and events.

Here is a list of key properties and methods. Refer to the properties, methods, and events of the Transaction class from which this class inherited.

  • ResponseReceived. Event that occurs when a response message sent by this object has been received.
  • SendRequest. Method that sends a SIP request to a transaction server.
  • ServerTransaction. Property containing a reference to the root ServerTransaction object that contains this client transaction.

Request

The Request class contains information about a SIP request sent from a client.

Here is a list of key properties and methods:

  • AllHeaders. Read-only property that returns a HeaderCollection object containing all of the message headers.
  • AuthenticationInfo. Read-only property that returns an AuthenticationInfo structure containing the authentication information for this message.
  • Content. Property returning the parsed content from the message.
  • CreateResponse. Method that returns a new Response object for this Request object.
  • GetHeaders. Method that returns an IEnumerator interface for a type-filtered collection of headers.
  • Method. Property that returns the SIP method for this message.
  • RawContent. Property that returns the unparsed message content as a Byte array.
  • Received. Read-only property that is true if the message was received by the server. False implies that the message was created by the application.
  • RecordRoute. Property that is true when the server should record each SIP proxy that this request passes through.
  • RequestUri. Property containing the SIP URI for this request.
  • StandardMethod. Read-only method that returns the standard SIP method for this request represented as a StandardMethodType enumeration.
  • TimeReceived. Read-only property containing the date and time the message was received by the server.

Response

The Response class contains information about a SIP response that is sent to a client.

Here is a list of key properties and methods:

  • AllHeaders. Read-only property that returns a HeaderCollection object containing all of the message headers. (Inherited from Message.)
  • AuthenticationInfo. Read-only property that returns an AuthenticationInfo structure containing the authentication information for this message. (Inherited from Message.)
  • Content. Property returning the parsed content from the message. (Inherited from Message.)
  • GetHeaders. Method that returns an IEnumerator interface for a type-filtered collection of headers.
  • RawContent. Property that returns the unparsed message content as a Byte array. (Inherited from Message.)
  • ReasonPhrase. Property containing a text description of the status code.
  • Received. Read-only property that is true if the message was received by the server. False implies that the message was created by the application. (Inherited from Message.)
  • StatusClass. Read-only property containing the class of the status code.
  • StatusCode. Read-only property containing the status code for the response.
  • TimeReceived. Read-only property containing the date and time the message was received by the server. (Inherited from Message.)

ServerAgent

The ServerAgent class represents a Live Communications Server program. It provides a way for the program to interact with Live Communications Server. Typically, each program has only one ServerAgent. However, there may be situations where it becomes practical to implement multiple ServerAgent objects, one to process incoming messages and one to process outgoing messages.

Here is a list of key properties, methods, and events:

  • ConnectionDropped. Event that is fired when the connection to the server has been dropped.
  • Dispose. Method that terminates the interaction with Live Communications Server.
  • ProcessEvent. Method that removes an event from the server queue and processes it.
  • ServerProcess. Property that returns a System.Diagnostics.Process object that provides access to the Live Communications Server process.
  • WaitForServerAvailable. Method that checks to see whether the Live Communications Server process has been started and initialized. Applications should call this method before creating the first ServerAgent object.
  • WaitHandle. Property that indicates that there is input from the server to be processed.

ServerTransaction

A ServerTransaction class contains a SIP server transaction object located on a SIP proxy. This class is derived from the Microsoft.Rtc.Sip.Transaction class and inherits all of its properties, methods, and events.

Here is a list of key properties and methods. Refer to the Transaction class properties, methods, and events that this class inherited.

  • Branches. Property that contains all branches associated with this ServerTransaction object.
  • ChallengeRequest. Method that sends an authentication challenge response to the client transaction whose request is attempting to initiate this server transaction.
  • CreateBranch. Method that adds a new ClientTransaction to the Branches property.
  • EnableForking. Property that is true when request forking is allowed.
  • SendResponse. Method that sends a SIP response message to the client transaction.

Transaction

The Transaction class provides an abstract base class for both ClientTransaction and ServerTransaction.

Here is a list of key properties, methods, and events:

  • Cancel. Method that cancels this transaction and fires the Canceled event.
  • Canceled. Event that is fired when the transaction is canceled.
  • Dispose. Method that destroys this transaction and releases all resources associated with it.
  • OriginationTime. Property containing the date and time the transaction originated.
  • Request. Property containing the Request object that initiated the transaction.
  • Responses. Property containing a ResponseCollection object with all of the responses associated with this transaction.
  • SyncRoot. Property that returns an object used to synchronize access to a group of objects related to a transaction.
  • Terminated. Event that is fired when the transaction is terminated.
  • TimedOut. Event that is fired when the transaction times out.

Building a Managed Filter Application

A managed filter application consists of an application manifest that traps the messages to be processed and a class that consists of the methods that process the messages. The following application manifest traps all INVITE messages. After verifying that the message has a valid request, it then uses the Dispatch function to call the external routine, ManagedInvite.

<?xml version="1.0" ?>
<rtc:applicationManifest
   appUri="http://Example.com.com/rtc/ManagedInvite"
   xmlns:rtc="http://schemas.microsoft.com/rtc/2003/05">
   <rtc:requestFilter methodNames="INVITE" 
                      strictRoute="false" 
                      registrarGenerated="true"
                      domainSupported="true"/ >
   <rtc:responseFilter reasonCodes="NONE" />
   <rtc:proxyByDefault action="true" />
   <rtc:splScript><![CDATA[
if (sipRequest) 
   {
   Dispatch("ManagedInvite");
   }
   ]]></rtc:splScript>
</myNS:applicationManifest>

The InviteHandler class contains a method called ManagedInvite. This is the method called by the Dispatch function in the application manifest. This method must include two parameters, an object that contains information about the sender, and a RequestReceivedEventArgs object containing information about the message that is to be processed. The following is the code for the InviteHandler class:

class InviteHandler
{
   public void ManagedInvite(object sender, RequestReceivedEventArgs args) 
   {
   Request request = args.Request;

   ServerTransaction myServerTrans = requestEventArgs.ServerTransaction;

// Processing logic here
// ...
// Create a branch on the server transaction to forward the request

   ClientTransaction myClientTrans = myServerTrans.CreateBranch();

// Send the request 

   myClientTrans.SendRequest(request);
   }
}

After pulling the Request object from args, you can perform any custom processing. Once the processing has been completed, a new client transaction must be created in order to pass the request along to the other user agent. Once the new client transaction is ready, a call to SendRequest passes the message to the proper destination.

Installing a Managed Live Communications Server Application

Unlike regular MSPL scripts that are installed with Live Communications Server, a special program is needed to compile the program. The following code fragment creates an ApplicationManifest object using a disk file called ManagedInvite.am, which contains the application manifest described above. Then, the Compile method is called to compile the application manifest. Any compilation errors are displayed on the console.

ApplicationManifest myApp = ApplicationManifest.CreateFromFile("ManagedInvite.am");

try 
   {
   myApp.Compile();
   }
catch (CompilerErrorException ex)
   {
   Console.WriteLine("Compiler errors:");
   foreach (string msg in ex.ErrorMessages)
      {
      Console.WriteLine(msg);
      }
   return;
   }

In the next code fragment, the newly compiled filter is installed. Assuming that the application compiled properly, WaitForServerAvailable is called to ensure that Live Communications Server is ready for processing. If the server is not available, the method waits for 10 seconds and tries again. After 20 unsuccessful attempts to connect to the server, a ServerNotFoundException is generated.

try 
   {
   ServerAgent.WaitForServerAvailable(20);
   ServerAgent myServerAgent = new ServerAgent(myHandler, myApp);

   WaitHandle waitHandle = myServerAgent.WaitHandle;

   WaitCallback rtcEventCallback = new WaitCallBack(myServerAgent.ProcessEvent);

   for(;;) 
      {
      waitHandle.WaitOne();
      ThreadPool.QueueUserWorkItem(rtcEventCallback);
      }

   } 
catch (NullReferenceException ex) 
   {
   Console.WriteLine("The application manifest is not compiled.");
   return;
   } 

catch (ServerNotFoundException ex) 
   {
   Console.WriteLine("The server is not available.");
   return;
   }

After the server is available, a ServerAgent is created using the newly compiled application manifest and myHandler that is an instance of the InviteHandler class created earlier. Because the filter runs in its own process, some sort of thread management should be used. ServerAgent provides some tools for thread management that work with the standard System.Threading library. The WaitHandle method contains an internal handle that signals when there is work to be processed, while the ProcessEvent method removes an event from the server queue and processes it.

Installing a Live Communications Server Application with WMI

It is possible to use the Microsoft Windows® Management Instrumentation (WMI) to add a SIP application to the server. The following code fragments assume that there is a value connection to the object repository. Note that you should check the return values after each call to ensure that no errors occurred while processing the call.

The following block of code creates an instance of the WMI MSFT_SIPApplicationSetting object that holds information about this:

    // Create a new instance of the MSFT_SIPApplicationSetting class
    CComBSTR sbstrClassName(L"MSFT_SIPApplicationSetting");
    CComPtr<IWbemClassObject> spIWbemClassObject;
    HRESULT hr = spIServices->GetObject(sbstrClassName, 
        WBEM_FLAG_RETURN_WBEM_COMPLETE, NULL, &spIWbemClassObject;, NULL);
    CComPtr<IWbemClassObject> spApplicationSettingObject;
    hr = spIWbemClassObject->SpawnInstance(0, &spApplicationSettingObject;);

The InstanceID property must contain a GUID value that uniquely identifies the application. The following code fragment creates a GUID using the CoCreateGuid function and then converts it to a string using the StringFromGUID2 function. Finally this value is saved in the InstanceId property.

    // Set the attributes for the instance that was created
    // Create a unique instance ID
    OLECHAR oleszTemp[39] = {0};
    GUID guid;
    hr = CoCreateGuid(&guid;);

    // Set the 'InstanceID' field to the unique GUID value that was created
    StringFromGUID2(guid, oleszTemp, sizeof(oleszTemp) / sizeof(OLECHAR));
    CComVariant svarGuid(oleszTemp);
    hr = spApplicationSettingObject->Put(L"InstanceID", 0, svarGuid);

You can set the appropriate values for each of the properties. The Name property contains the user-friendly name for the application. The URI property contains the unique URI string that identifies the SIP application. Note that URI property must be the same as in the application manifest. When the Enabled property is True, the application automatically starts when the SIP server starts. When the Critical property is True, the SIP server automatically shuts down if the application fails. The ScriptPath property contains the local path to the MSPL script when the script is not embedded in the application manifest.

    // Set the 'Name' field
    CComVariant svarName(L"NameValue");
    hr = spApplicationSettingObject->Put(L"Name", 0, svarName);
    
    // Set the 'URI' field
    CComVariant svarURI(L"URIValue");
    hr = spApplicationSettingObject->Put(L"URI", 0, svarURI);

    // Set the 'Enabled' field
    CComVariant svarEnabled(true);
    hr = spApplicationSettingObject->Put(L"Enabled", 0, svarEnabled);

    // Set the 'Critical' field
    CComVariant svarCritical(false);
    hr = spApplicationSettingObject->Put(L"Critical", 0, svarCritical);

    // Set the 'ScriptPath' field
    // (only needs to be set for scriptis NULL for non-script applications)
    CComVariant svarScriptPath(L"ScriptPathValue");
    hr = spApplicationSettingObject->Put(L"ScriptPath", 0, svarScriptPath);

After all of the values have been set, the PutInstance method is called to save the application's information.

    // Commit the new instance to WMI
    hr = spIServices->PutInstance(spApplicationSettingObject,
        WBEM_FLAG_CREATE_OR_UPDATE, NULL, NULL);

Programming Issues

A few issues that should be kept in mind when developing Live Communications Server applications are outlined below.

Managed Code vs. MSPL Script

MSPL is a scripting language designed to support filtering and routing of SIP messages. It is not designed to be a comprehensive programming language. This means that many features commonly found in programming languages, such as the ability to compute arithmetic values or use external routines, are not present in MSPL.

Also, MSPL is an interpreted language. As such, each statement in an MSPL program must be parsed and translated prior to execution. This means that script-based programs typically require a lot more resources to execute each time. However, MSPL is ideal for situations where complex processing is not required. In fact, Live Communications Server uses an MSPL script to handle default routing.

The power of a .NET-compatible programming language such as Visual C .NET comes into play when it is necessary to do more complex processing such as looking up information in a database. However, with the extra flexibility comes a price. Managed programs are generally more complex than MSPL scripts due to the lack of the high-level tools built into MSPL.

Parallel and Serial Forking

Forking is the process whereby a single INVITE request is translated into multiple requests. In parallel forking, the multiple requests are generated at the same time. In serial forking, one request is initially sent out, and then depending on the response to that request or if the request timed out, additional requests may be sent out one at a time.

Execution Order

When running multiple applications on the same server, it is important to ensure that the applications are running in the proper order. Suppose an administrator installs two applications, one for logging and one that implements an advanced discretionary access control list (DACL) that prevents unauthorized communications between certain parts of the company. If the logging application runs after the DACL application, any communication attempts that are denied by the DACL application are not logged. If the order is reversed and the logging application runs first, the text of the unauthorized message is logged.

Application Thread Pools

When building multi-threaded applications, it is useful to push long-running tasks such as database queries from the main thread that processes requests into a worker thread. One of the best ways to manage this process is to create a work item class that contains all of the information necessary to perform the task, along with the information, and then send it to a worker thread for processing.

Consider the following routine, which is called when the server receives an INVITE request. As part of processing the INVITE request, a work item object is created including the database query to be performed, along with an object reference to the request object. The serverTransaction.SyncRoot is used to synchronize access to the transaction associated with the request.

OnInviteReceived() { 
   ...
   // Form a database query necessary to process this request. 
   workItem.Query = ...; 
   workItem.Request = request; 
   // This is the "object space" we're going to 
   // have to lock in the completion routine.
   workItem.Lock = serverTransaction.SyncRoot;    
   workItem.CompletionRoutine = OnQueryCompleted; 
   // Fire off an asynchronous database query. 
   FireAsynchronousQuery (workItem);
   ...
}

After the activities associated with the work item have been finished, the OnQueryCompleted routine is called to complete the processing. Because this routine is executed in the worker thread, which is outside the context of the dispatch handler, there is a need to acquire the lock the server agent used to synchronize access to all objects related to a given transaction. Once the lock has been acquired, the request object can be modified and the request can be returned to the dispatch handler for final processing.

OnQueryCompleted (workItem) { 
        // Acquire the lock. 
        lock (workItem.Lock) { 
                request.RequestUri = workItem.targetUri; 
                // Create a branch transaction. 
                // Forward the request. 
                branch.SendRequest (request); 
        } 
}

Conclusion

Live Communications Server includes powerful tools for building programs that provide custom processing for real-time communications applications. MSPL provides a high-level approach to creating routing and forking filters. Its Visual C-like syntax makes it easy for developers to learn, and its rich collection of functions and objects reduces the amount of code required to create most filters.

It's important to note that while MSPL is a good solution for building most types of filters, by design it is limited in what it can do. It cannot call Web services nor can it access a database or XML file. And it cannot perform arithmetic expressions. To address this limitation, Live Communications Server includes a complete set of managed classes, which allows a developer to use any .NET-compatible programming language to create a filter. MSPL and the managed classes provide effective solutions to customizing Live Communications Server to meet the specific needs of an organization.

For more information, see the following: