MSRP Library Implementation

Jinai A
UNI: ja2432
ja2432@columbia.edu

Abstract

This project implements a library for a SIP-based message protocol: Message Session Relay Protocol (MSRP). In the library, a set of standard and concise APIs are provided for programmer and two testing programs are enclosed to show the basic procedure of the library.

Introduction

MSRP is a message protocol for transmitting a series of related instant messages in the context of a session. Message sessions are treated like any other media stream when set up via a rendezvous or session creation protocol such as the Session Initiation Protocol (SIP). It is different from a conversational exchange of messages with a definite beginning and end and sent independently. There are two kinds of message schemes: "page-mode" and "session-mode". "Page-mode" means tracking only individual messages, whereas "session-mode" means messaging is a part of a "session" with a definite start and end.

"Session-mode" has a number of benefits over page-mode messaging, such as explicit rendezvous, tighter integration with other media-types, direct client-to-client operation, and brokered privacy and security.

MSRP is a kind of "session-mode" message protocol. Its session can be negotiated with an offer and answer using the Session Description Protocol (SDP). The exchange is carried by some signaling protocol, such as SIP. This allows a communication user agent to offer a messaging session as one of the possible media-types in a session. SIP can use an offer/answer model to transport the MSRP URLs for the media in SDP. Our library here implements MSRP behavior only for peer-to-peer sessions, that is, sessions crossing only a single hop. Future work may cover MSRP relay implementation.

For a message protocol, the most important functions are message constructing and message parsing. Therefore, this library provides concise APIs for MSRP message constructing and parsing. In addition, it allow user to flexibly establish MSRP session and configure message contexts based on their preference.

After implementing the basic procedure of the library, two testing programs (msrp_sender and msrp_receiver) are provided to virtually build two MSRP sessions between two hosts and let them exchange MSRP messages.

Background

MSRP is a text-based, connection-oriented protocol for exchanging arbitrary (binary) MIME content, especially instant message. This background section describes how MSRP work with SIP and what MSRP message like.

MSRP sessions are typically arranged using SIP the same way a session of audio and video media is set up. One SIP user agent (Alice) sends the other (Bob) a SIP invitation containing an offered session-description that includes a session of MSRP. The receiving SIP user agent can accept the invitation and include an answer session-description that acknowledges the choice of media. Alice's session description contains an MSRP URI that describes where she is willing to receive MSRP requests from Bob, and vice versa.

Here is an example:

Alice sends to Bob:

   INVITE sip:bob@biloxi.example.com SIP/2.0
   To: <sip:bob@biloxi.example.com>
   From: <sip:alice@atlanta.example.com>;tag=786
   Call-ID: 3413an89KU
   Content-Type: application/sdp

   c=IN IP4 atlanta.example.com
   m=message 7654 TCP/MSRP *
   a=accept-types:text/plain
   a=path:msrp://atlanta.example.com:7654/jshA7weztas;tcp
 
Bob sends to Alice:

   SIP/2.0 200 OK
   To: <sip:bob@biloxi.example.com>l;tag=087js
   From: <sip:alice@atlanta.example.com>;tag=786
   Call-ID: 3413an89KU
   Content-Type: application/sdp

   c=IN IP4 biloxi.example.com
   m=message 12763 TCP/MSRP *
   a=accept-types:text/plain
   a=path:msrp://biloxi.example.com:12763/kjhd37s2s20w2a;tcp

Alice sends to Bob:

   ACK sip:bob@biloxi SIP/2.0
   To: <sip:bob@biloxi.example.com>;tag=087js
   From: <sip:alice@atlanta.example.com>;tag=786
   Call-ID: 3413an89KU

This is a basic session setup process. When Alice receives Bob's answer, she checks to see if she has an existing connection to Bob. If not, she opens a new connection to Bob using the URI he provided in the SDP. Alice then delivers a SEND request to Bob with her initial message, and Bob replies indicating that Alice's request was received successfully.

Here is a basic MSRP message example:

   MSRP a786hjs2 SEND
   To-Path: msrp://biloxi.example.com:12763/kjhd37s2s20w2a;tcp
   From-Path: msrp://atlanta.example.com:7654/jshA7weztas;tcp
   Message-ID: 87652491
   Byte-Range: 1-25/25
   Content-Type: text/plain

   Hello bob, congratulation to your graduation!
   -------a786hjs2$

Here is the success reply message:

   MSRP a786hjs2 200 OK
   To-Path: msrp://atlanta.example.com:7654/jshA7weztas;tcp
   From-Path: msrp://biloxi.example.com:12763/kjhd37s2s20w2a;tcp
   -------a786hjs2$

Here, we introduce the MSRP message structure in details: The first line of Alice request contains transaction identifier that is also used for request framing. In our program, we cache the transaction identifier into a multiset sturcture. When a status report messages is received, only if the report message matches elements in the multiset, we can accept it. The second line is called To-Path line which includes the path of URIs to the destination. This URI is exchanged within SIP messages. The third line is From-Path line which contains Alice own URI. As we mentioned in introduce section, this scenario is just one "hop", so there is only one URI in each path header field. The next line is message ID line, which Alice can use to correlate status reports with the original message. Next, she put byte-range field, which describes the length of the actual message enclosed and is especially useful in message chunking. The next line is Content-Type line, our program allows user to use a string to designate the field. After that, she puts the actual content. Finally, she closes the request with an end-line of seven hyphens, the transaction identifier, and a "$" to indicate that this request contains the end of a complete message.

If Alice wants to deliver a very large message, she can split the message into chunks and deliver each chunk in a separate SEND request. The message ID corresponds to the whole message, so the receiver can also use it to reassemble the message and tell which chunks belong with which message. The Byte-Range header field identifies the portion of the message carried in this chunk and the total size of the message. Our library support message chunking, and chunk size is 2048 bytes, as the RFC recommends.

Alice can also specify what type of reporting she would like in response to her request. If Alice requests positive acknowledgments, Bob sends a 200 OK message to Alice confirming the delivery of her complete message. This is especially useful if Alice sent a series of SEND requests containing chunks of a single message.

Here are some introductions of MSRP URIs. In our test programs, we use this kind of MSRP URIS:

MSRP://127.0.0.1:6000/jlkjfd234;tcp

The first field is IP address (127.0.0.1), the second field is MSRP receiving port number (port# 6000), the third field is MSRP session identifier (a random string: jlkjfd234), which should be unique among MSRP sessions, and the last field indicates that this MSRP exchange uses TCP connection. The whole URI can identify a MSRP session in one host.

Architecture

Two essential classes

In order to explain explicitly, we firstly introduce two main classes in our library: MSRP session class and MSRP message class:

class msrp_session {
	
public:
       msrp_session(string their_url, string my_url, int flag);
       /* Create another thread to listen one port all the time*/
       void create_listening();
       /* These fout are "get" functions, used to get private members in msrp_session class */
       string get_sessionid(int );
       string get_ip_address(int );
       unsigned short int get_port_num(int );
       int get_sockfd();	
private:
       int fd;
       string their_sessionid;
       string their_IP_address;
       unsigned short int their_port;
       string my_sessionid;
       string my_IP_address;
       unsigned short int my_port;
}; /* class msrp_session */

In MSRP session class constructor, it parses my_msrp_url (a string contains a local host MSRP URI) and their_msrp_url (another string contains a peer host MSRP URI) to fill out its private variables, and based on these private variables to create a TCP connection. What is a little tricky here is that constructor uses the third argument flag to distinguish MSRP session initiator and responder, due to TCP needs to designate client and server when it is created the first time. In addition, in the constructor, it starts a thread to listen on its MSRP receiving port. Whenever it receives MSRP SEND message, MSRP ACK message or some other messages, it automatically parses and displays it on standard output.

Another critical class is MSRP message class. Its structure is:

class msrp_message {

public:
       msrp_message(msrp_session * = NULL, int = MSRP_SEND, char * = "text/plain");
       int send_msrp_message(const char *buf, int buf_len);
       int msrp_send_chunk(int start, int end);
       int change_message_id(char *);
       int send_report_request_message(char *);

private:
       msrp_session *msrp_session_t; /* The msrp session this message belongs to */
       char *message_id;             /* MSRP method (SEND/REPORT) */
       int method;          	     /* Message-ID */
       char *content;         	     /* Payload content type, define by programmer */
       char *data;          	     /* Total payload data (pointing to all chunks) */
       int bytes;           	     /* Total payload lenght */
};/* class msrp_message */

Since only within one specific session, programmer can send MSRP message, in MSRP message constructor, it provides an msrp_session class pointer to point the MSRP session to which it belongs. Programmer can use the second argument in the constructor to designate the message method: it can be either SEND or REPORT message. The third argument is used to assign the message type, since with the development of Networks, we cannot limit the type in a specific range, we leave a character type pointer to let programmer assign this field. Another important method this class provides is send_msrp_message(), which is responsible for constructing the MSRP message and use the existed TCP connection to send it. Send_msrp_message() actually invokes method msrp_send_chunk() to implement its function, since it needs to deal with message chunking problem when necessary. The last method providing to programmer is that it can let programmer define message identifier, otherwise library will create a random string as the message identifier. All its private members are useful when constructing the MSRP message context.

Basic procedures

As a programmer, the following graph illustrates the basic process when he uses the library. This is also the basic procedure of the testing programs enclosed.

What other functions that programmer can use

Except the basic sending and receiving function, we conclude here what other functions that programmer can use.

1)  Programmer can define how to deal with the parsed message. When parsing the received message, the library declares a function OnReceive(string &OnRecv_buf), but does not define it. It leaves its definition to programmer. Programmer can define it such as printing it out, or some other operations;

2)  When MSRP session is built, programmers in two peer hosts must designate if his host acts as TCP initiator or responder, otherwise, errors like "Connection refused" would be given;

3)  When constructing MSRP message, programmer can flexibly assign content-type by using a character string pointer. DO not forget let message belong to an existed MSRP session, since it will use session's private varibles to construct message and session's socket to send the message;

4)  When programmer uses the library, what he needs to worry about is how to construct the message, but do not have to worry about how to parse the received message or report message. Since when the session is built, an underlying thread starts to listen on one MSRP receiving port to receive and parse MSRP message.

More library internal operation will be discussed in more details in section program internal operation section

Program Documentation

System requirements

This library can be used in standard Linux. The library has been tested in Ubuntu 7.10 which is running on a VMware virtual machine (6.02 Edition).

Installation instructions

1)  Compile library

In terminal, type "make". A static library named libmsrp.a will be built;

2)  Copy library to system directory

In terminal, type "make install". The library and msrp_session.h will be copied to system directories;

3)  Create testing programs

In terminal, type "make test". Two testing programs will be built.

Here is a screen dump for the whole process:

4)  Test basic procedure

Start two terminals in one host. In one terminals, type "./msrp_receiver_test", and in the other terminal, type "./msrp_sender_test". Try to input something that you want send in the sender terminal, and then input something in the receiver terminal. The whole process will be displayed on the screen. Do not forget to use a plus symbol "+" to terminate your input.

5)  Test REPORT request

In terminal, type "make test_report". Then two testing programs will be built. Start two terminals in one host. In one terminals, type "./msrp_receiver_test_report", and in the other terminal, type "./msrp_sender_test_report". Try to input something that you want to send in the sender terminal. The whole process will be displayed on the screen. Do not forget to use a plus symbol "+" to terminate your input.

Operation

Since this program is implementing a protocol into an open library. It totally depends on programmer's preference to use the library. Basic library procedures are described in section basic procedures section and testing programs in testing section.

Program internal operation

MSRP message sending function

Firstly, this function judges if the sending message length is larger than MAX_CHUNK_SIZE or not. The default value of MAX_CHUNK_SIZE is 2048 bytes. If the value is smaller than MAX_CHUNK_SIZE, it would invoke method msrp_send_chunk() to send the message. If the value is larger than MAX_CHUNK_SIZE, it will enter a loop to invoke msrp_send_chunk() till all the data have been sent.

In method msrp_send_chunk(), the most frequent invoked function is msrp_add_frompath_line() like function, they add corresponding lines to message context. Actually, it firstly invokes snprintf() to write the content into a string. and then write the formatted message line into a sending string buffer. After constructing the message, msrp_send_chunk() sends buffer to peer host via the existed socket in MSRP session which this message belongs to.

MSRP message parsing function

Method receive_message() is invoked in another thread. Its responsibility is to receive messages and parse them. It can parse both MSRP message and MSRP report message. The basic idea of parsing is to use strstr() system call to find message context line, and copy different line to different string (for example, the Message-ID string). If the received message is an MSRP SEND message, the most important work is to find the actual message content. As we mentioned above, after parsing, receive_message() invokes function OnReceive() to let user choose how to deal with the parsed message content. If the received message is a report ACK message, receive_message() would find the corresponding sent message that this ACK matches, and let programmer know one of his sent messages has been acknowledged.

Random string creation

When building message context, we need to create random strings to fill out transaction ID and message ID field. It is very important to make sure the string created to be unique, so the library's random string creation function random_string() connects the created string with the specific time when it is invoked to guarantee the string is unique. Its codes are as follows:

char *random_string(char *buf, size_t size, unsigned int seed)
{
      long val[4];
      int i;
      time_t seconds;      //Used current time to generate random string
        
      time(&seconds);
      srand((unsigned int) seconds + seed);

      for (i = 0; i < 4; i++) {          
	   val[i] = random();
      }
 
      snprintf(buf, size, "%08lx%08lx%08lx%08lx", val[0], val[1], val[2], val[3]);    //size = 9

      return buf;
}

Caching transaction ID and Message ID

The library provides a global multiset to store message's transaction ID. When a message is sent, its transaction ID is stored in the set, and when parsing the corresponding ACK message, only if its transaction ID matches correctly one element in the set, it can be continued to deal with and that matched item in the transaction ID stack is deleted.

In the similar way, the library also provides a global multiset to store message ID. It is used to march received MSRP REPORT message. When a REPORT message is received, host checks the message ID set. If the corresponding message ID exists, a success report is transmitted back, if not, the library would let programmer know one report request cannot find corresponding message ID.

Connection model

Based on the MSRP RFC, message transmission typically uses TCP connection. In our library, every MSRP session has a TCP connection and this connection is built when the session is constructed. After the TCP connection is created, MSRP messages belonging to this session are exchanged via it.

In addition, if the listening port received a MSRP message, it firstly tries to match the message MSRP URI (in message From-Path field). If successes, it continues the procedure. If not, an error code is transmitted back.

Things that do not work or other restrictions

1)  In the original RFC, it's recommended to provide "Success-report" and "Failure-report" header fields in message context. With programmer setting this field, it can decide if programmer prefers success-report or failure-report. In my implementation, I use a separate thread to listen on the receiving port. If implementing this function, it needs complex communication between main thread and listening thread to let listening thread run a timer. Since limited time, I did not implement this function.

2)  In my implementation, I build TCP connection for each MSRP session. In the scenario that a lot of session needed to created, this method would be a resource wasting. A better method may be creating only one TCP connection between two hosts, and let every MSRP message share this TCP connection. When a message coming, it uses its session identifier to match corresponding MSRP session and do following procedures. However, the disadvantage for this method is that it is difficult to provide programmers with concise APIs and let the overall designing more complex.

Acknowledgements for code and ideas borrowed

I need to appreciate another open source MSRP implementation, I borrow the basic idea when constructing and parsing MSRP messages.

Measurements and test

Basic testing

After running "make test", two testing programs are complied to show the basic procedure of using library. The basic functions of these testing programs are illustrated by the following figure:

The basic idea is creating two MSRP sessions between two hosts, that is, two TCP connections are built between two hosts. At beginning, sender sends a MSRP message, and the receiver's listening thread parses the message and sends back an ACK message. And then, receiver sends a MSRP message to sender, and sender automatically parsea it and sends back the corresponding ACK message. In DEBUG mode, all the exchange messages and the parsed message contents are displayed in terminal.

Its codes are as follows:

Firstly, MSRP message sender:

#include "msrp_session.h"

int main(int argc, char *argv[])
{ 
    /* We assume these MSRP URIs have been changed by SIP, and each peer has known them */
    string their_msrp_url_1("msrp://127.0.0.1:6000/dlkjfndsgf;tcp");
    string my_msrp_url_1("msrp://127.0.0.1:5000/aflkjdfet;tcp");
    string my_msrp_url_2("msrp://127.0.0.1:4300/fdlkgjsf;tcp");
    string their_msrp_url_2("msrp://127.0.0.1:6043/fdalksfkdf;tcp");

    string ss;

    /* Constructing the first session*/
    msrp_session session1(their_msrp_url_1, my_msrp_url_1, INITIATOR);
    /* Constructing the second session*/
    msrp_session session2(their_msrp_url_2, my_msrp_url_2, RESPONDER);

    cout << "Please input the message you want to send" << endl;
    getline( cin, ss, '+');
    cout << "The input is " << ss << endl;

    /* Construct a message class under the first session, it is a text/plain type */
    msrp_message message1(&session1, MSRP_SEND, "text/plain");
    
    /* Sending a message using the first session */
    
    if(message1.send_msrp_message(ss.c_str(), ss.size()) < 0)
    {
    	cout << "Error in send msrp message" << endl;
    	exit(1);
    }  

    cout << "Please input the message you want to send" << endl;
    getline( cin, ss, '+');
 
    return 0;
    
} /* main() */


/*
 *  OnReceive()
 *  This function is defined by programmer to decided how to deal with the parsed actual message content 
 *  If successes, 0 will be returned
 */
int OnReceive(string &OnRecv_buf)
{
    cout << "\nIn function OnReceive:" << endl;

    if(strcmp("a Success Report", OnRecv_buf.data()) == 0) {
       cout << "We have received a Success Report" << endl << endl;
    }/* if */
    else if (strcmp("a REPORT request", OnRecv_buf.data()) == 0) {
             cout << "We have received a REPORT request" << endl << endl;
         }/* if */
         else {
               cout << "The received message's content is\n" << OnRecv_buf << endl << endl;
         }/* else */

    cout << "Please input the message you want to send" << endl;

    return 0;
}/* OnReceive() */

Here, we mention function OnReceive()again. It provides flexibly interface to let programmer deal with the parsed message content.

The Screen dumps are like:

For Sender:

Continuing....

For Receiver:

Continuing....

Message chunking testing

In this part, we test the message chunking scenario. We modified the MAX_CHUNK_SIZE macro definition in msrp_session.h from 2048 to 10 bytes (only meaningful in testing). Let sender input 18 bytes string, two MSRP messages would be sent orderly. Receiver would parse these two MSRP messages, combined the parsed message contents together and showed on the screen. Here are the screen dumps.

For Sender:

Continuing....

For Receiver:

Report request testing

In this part, we test REPORT request type instead of SEND request type in the last two tests above. Firstly, let programmer designate a message ID and send a message with this ID. And then send a REPORT message to query peer host if the last message with that message ID has been successfully received or not. If received successfully, a 200 OK ACK message will be returned.

The codes are enclosed in the sumbitted file folder (msrp_sender_test_report.cpp & msrp_receiver_test_report.cpp), the following are screen dumps of this test.

For Sender:

Continuing....

For Receiver:

Continuing....

References

1
B. Campbell, Ed. et al. "The Message Session Relay Protocol (MSRP)", RFC 4975, 2007.
2
Schulzrinne,H., et al. "SIP: Session Initiation Protocol", RFC 3261, 2002.
3
Stanley B.Lippman, et al. C++ Primer. Addison-Wesley Press, Indianapolis, IN, 2002.

Last updated: 2008-05-21 by Jinai A