Writing a Message Filter Script

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

Writing a Message Filter Script

A message filter is an MSPL scripting application that traps SIP messages based on one or more criteria, and then either processes the messages or dispatches them to a managed code application for processing.

Tips for Writing an Effective Message Filter Script

  • Always ensure that you are handling a request when you call a function like Fork by enclosing it in an if (!sipResponse) { ... } section. For response-specific functions like ProxyResponse, an if (!sipRequest) { ... } section is likewise appropriate. Remember, every time a function attempts to process a message type it cannot handle, a critical MSPL error is raised against your application, and recorded in the server event log. If 10 of these errors occur, your application is disabled by Office Communications Server. Divide your filter into discrete sections for handling each class of message—response and request—and implement their processing separately.

  • Log is the only effective mechanism you have to debug your code logic. While the ApplicationManifest.Compile method will catch syntactical errors, code logic errors must be debugged using the SPLLogger.exe (which ships with Office Communications Server). Call Log anytime a message with a specific SIP method is identified, or when any meaningful action is taken when processing a message.

  • When forking a message, BeginFork, Fork, and EndFork must be called in the implied order. Calling one out of order generates an error. Fork can be called as many times for as many messages as appropriate as long as BeginFork has not been closed with a subsequent call to EndFork. When EndFork is called, all of the messages previously specified with calls to Fork after the initial BeginFork are forked. If you do not call EndFork, the forked messages are not sent, and an error is generated.

  • If your application is declared as script-only using the scriptOnly Element, compilation will fail if you attempt to call the Dispatch function.

A Sample Message Filter Script

The following annotated MSPL script filters incoming SIP responses and attempts to select the best endpoint for each message based on the endpoint ID (EPID), the aggregate availability and activity values, and the RegistrarEndpoint value. Endpoints with no registered presence or an aggregate availability less than 100 will not be considered.

<?xml version="1.0">
<lc:applicationManifest
 lc:appUri="http://www.my_domain_here.com/DefaultRoutingScript"
 xmlns:lc="http://schemas.microsoft.com/lcs/2006/05">
<lc:requestFilter methodNames="INVITE,MESSAGE,INFO,REFER,ACK,BYE,OTHER"
                        strictRoute="false"
                        registrarGenerated="true"
                        domainSupported="true"/ >
<lc:responseFilter reasonCodes="NONE" />
<lc:proxyByDefault action="true" />
<lc:scriptOnly />
<lc:splScript><![CDATA[
    //
    // This script handles default routing of requests to Office 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 (that is, how long since last set presence)
    //
    // Endpoints with no presence or an availability less than 100, or
    // no routing information (for example, set presence without
    // register) are not considered.
    //

    Log( “Debugr”, 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.
    //
    bestEPID = "";
    bestAgeOfPresence = 0x7FFFFFFF;
    bestAvailability = 0;
    bestActivity = 0;
    bestContactInfo = "";
    Log( “Debugr”, 1, "EPID - ", requestEPID );
    Log( “Debugr”, 1, "toUserAtHost - ", toUserAtHost );
    foreach (dbEndpoint in QueryEndpoints( toUserAtHost, true )) {
        Log( “Debugr”, 1, "    endpoint.EPID - ", dbEndpoint.EPID );
        Log( “Debugr”, 1, "    endpoint.HasPresence - ", dbEndpoint.HasPresence );
        Log( “Debugr”, 1, "    endpoint.Availability - ", dbEndpoint.Availability );
        Log( “Debugr”, 1, "    endpoint.Activity - ", dbEndpoint.Activity );
        Log( “Debugr”, 1, "    endpoint.AgeOfPresence - ", dbEndpoint.AgeOfPresence );
        Log( “Debugr”, 1, "    endpoint.ContactInfo - ", dbEndpoint.ContactInfo );

        //
        // First, determine whether this endpoint supports the method in the request.
        //
        if (!SupportsMethod( sipRequest.Method, dbEndpoint.StandardMethods, dbEndpoint.ExtraMethods )) {
            //
            // Skip this endpoint because it cannot handle the method on this request.
            //
            Log( “Debugr”, 1, "        * skipped because of method" );
            continue;
            }

        if (requestEPID != "") {
            if (requestEPID == dbEndpoint.EPID) {
                //
                // The request is already targeted at a specific EPID that can handle the method,
                // so use this endpoint.
                //
                Log( “Debugr”, 1, "        * matched EPID" );
                bestContactInfo = dbEndpoint.ContactInfo;
                break;
            }
            else {
                //
                // The request is targeted at a specific EPID, but does not match this endpoint.
                // Skip this endpoint.
                //
                Log( “Debugr”, 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( “Debugr”, 1, "        * skipped because of presence, activity or contactinfo" );
            continue;
        }

        if (dbEndpoint.Availability < bestAvailability) {
            //
            // Skip this endpoint if it cannot win on availability.
            //
            Log( “Debugr”, 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( “Debugr”, 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( “Debugr”, 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( “Debugr”, 1, "        *** new best contact" );
        }


    //
    // See if an endpoint to proxy to was found.
    //
    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( “Debugr”, 1, toUserAtHost, " is homed on ", homeServer );
                newRequestUri = Concatenate( sipRequest.RequestUri, ";maddr=", homeServer );
                Log( “Debugr”, 1, "Request is being routed to ", newRequestUri );
                AddHeader( "MS-LBVIA", FQDN );
                ProxyRequest( newRequestUri );
                return;
            }
        }
        */
        Log( “Debugr”, 1, "Responding 480 - temporarily unavailable as no suitable endpoint found" );
        Respond( 480, "Temporarily Unavailable" );
    }
    else {
        if (requestEPID == "") {
            Log( “Debugr”, 1, "Adding missing EPID '", bestEPID, "' to To header" );
            SetParameterValue( "To", "epid", bestEPID );
            }

        Log( “Debugr”, 1, "Proxying request to - ", bestContactInfo );
        ProxyRequest( bestContactInfo );
        }

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

Note

The use of the following patterns: request.RequestUri = ... ProxyRequest(sipRequestUri) If you change RequestUri, the request is marked as routed without providing a request target. Applications that do not have routing information for a request should not modify the request-uri. We recommend that non-routing applications never change the request-uri.