As I mentioned in past blog entries, MSRP is designed to carry arbitrary content. Not only does that mean it can carry any type of data, or at least any kind that can be represented in MIME
, it means it can carry arbitrarily large data. MSRP also lets you use the same TCP connection for multiple sessions.
The down side to this is that TCP always delivers data in the order sent. If you start sending a very large piece of content (say, video), but then need to send something else that’s very important, the recipient’s endpoint won’t be able to see the very-important-message until it completely receives the video. We call this head-of-line blocking. MSRP’s chunking feature lets you get around this problem by interleaving content over a single connection.
For the purposes of this discussion, let’s define the term “message” to mean a whole piece of content, in the form of a complete MIME object. That could be a short “text/plain” object, or it could be a huge “video/mp4″ object. A “chunk” is a piece of a message. That piece could be the whole message, which would be likely for the small “text/plain” message, or it could be just a fragment of the message. To break a message into chunks, you first encode the message. You take one “message” and cut it into pieces, instead of creating a bunch of smaller “messages.”
MSRP uses the “byte-range” header field for this purpose. The byte-range field tells you what portion of the message is in a particular chunk. It also tells you the overall size of the message. For example, in the case of a short message that is fully contained in one chunk, you might see the following:
This means the chunk contains bytes 1 through 100 out of a total length of 100. On the other hand, for a chunk from the middle of a bigger message the Byte-Range header field might look like:
The sending endpoint puts each chunk into a separate MSRP SEND request. The receiving endpoint reassembles the chunks into a whole message by inspecting the Byte-Range headers. Both parties can use the total length value to provide progress information to the users. All the chunks from a given message share the same value in the Message-ID header field.
Here’s an example of a SEND request containing an entire message:
MSRP d93kswow SEND
Hi, I’m Alice!
And here’s an example of a message broken into two chunks:
MSRP d93kswow SEND
MSRP op2nc9a SEND
But sometimes, the sender may not know the size of the entire message in advance. For example, imagine that the sender is capturing video from a webcam. It would be inefficient to wait until the entire video is captured to start sending. If you don’t know the full message length, you can put a “*” in the total size field. For example:
Chunking is very useful, but it adds overhead. You really want to avoid chunking a message unless you have a good reason. Chunking was envisioned for situations where an endpoint needs to send a high priority message when a large message is already in the process of being sent. For example, the peer device might send you a message that requires a timely response in the form of an MSRP REPORT request–but you’ve already got a large transfer in progress. You can’t wait for the large transfer to complete first. The problem is, you can’t know in advance that this will happen, so how do you decide whether to break the large message into chunks? MSRP allows you to interrupt an in-process SEND request.
The last line of a SEND request is known as the end-line. The end-line is made up of seven hyphens, followed by a transaction identifier and a continuation flag. The transaction identifier is a random sequence initially written into the start line (look closely at the second field in the start lines of the examples above–they repeat in the end-line. The continuation flag tells you whether there are more chunks in the message. If it contains a “+”, then the recipient knows to expect more chunks. If it contains a “$”, then it’s the last chunk in the message. It can also contain a “#” to indicate the sender is abandoning the message.
You can make a SEND request interruptible by putting a “*” in the end-range field in the Byte-Range, like so:
In this example, the sender could start sending the entire 65535 octet message in a single SEND request. But if it needs to interrupt the message part way through, it simply writes out the end-line, and puts a “+” in the continuation field. It then sends whatever high-priority message that forced the interruption, then resumes the original large message in a new chunk. The receiver cannot tell the actual end-range from the Byte-Range header. Instead, it has to calculate the end-range by counting the actual number of octets before the end-line, and adding that to the start-range value.
A common misconception about MSRP is that the specification says that any message larger than 2048 octets must be broken into chunks. It really says that any message larger than that must be interruptible. It’s far more efficient to send a large message in one interruptible chunk than in a bunch of small chunks. That way you don’t incur the chunking overhead unless you really need it.
There’s another aspect of MSRP chunking that can make the implementer’s life interesting. If there’s an MSRP relay in the path of a session, that relay can break a chunk into smaller chunks. It can also reassemble several smaller chunks into a bigger chunk. This is transparent to the sender, except for one thing.
MSRP allows the recipient, or an intervening relay, to optionally send delivery reports in the form of REPORT requests. A REPORT request also contains a Byte-Range header field. In this case, the Byte-Range header field tells you the range of the original message to which the report applies. Since an intervening relay can change the chunking for a given message, the sender cannot assume that REPORT requests will line up with the chunks it actually sent. For example, you could send 3 chunks for the byte ranges of 1-33, 34-66, and 67-100, but get back REPORT requests for the ranges of 1-50, and 51-100.
I think that’s enough for now. Next time, I will cover more details on how those REPORT requests work. But until then, here’s a forewarning: The REPORT mechanism is more complicated than you probably expect.