Saturday, September 11, 2010

WebSocket Draft 76 Algorithm Example

The latest versions of Google Chrome and Safari have switched to draft 76 of the WebSocket protocol. Since this version is so drastically different than draft 75, this means it isn't exactly backwards compatible. In other words, if your server doesn't support d76, then the newest browsers won't work with the cool WebSocket stuff you wrote.

This means server/framework developers will need to upgrade their WebSocket support (if they haven't already). The trouble is, the spec is unnecessarily difficult to read. And I'm not the only one who thinks so:

The specification document is just not readable unless you want to go completely insane. [1]

it's both over engineered and absolutely badly documented via those "specs" [2]

If you want a quick and understandable overview of the changes, I'd recommend this page.

But if you're looking for a good quality example, with step-by-step instructions (or maybe just a unit test as you're implementing the code), you'll find them few and far in-between. So I thought I would provide one.

The example comes from the spec, but I'll break down the steps one-by-one:

So how do we calculate the response body? (The fQJ,fN... part) Very carefully.

The first thing to note is that the request body is exactly 8 bytes. We are displaying it here as if each byte were interpreted as ASCII text. Similarly, the response body is exactly 16 bytes, again displayed as if each byte were ASCII text.

An overview of the steps we need to perform are:

Extract each digit from Sec-WebSocket-Key1 and concatenate them

Count the number of spaces in Sec-WebSocket-Key1

Divide #1 by #2

Convert #3 to a 32 bit big-endian integer (network byte order)

Repeat steps 1-4 for Sec-WebSocket-Key2

Concatenate #4, #5, and the request body

Perform MD5 digest of #6

Sec-WebSocket-Key1 was given as18x 6]8vM;54 *(5: { U1]8 z [ 8

So this gives us the number 1868545188 for step 1. And there are 12 spaces (step 2).

1868545188 / 12 = 155712099 (step 3)

To covert this value to big-endian (network byte order) you might use something like this, or a similar language dependent function. The end result, if printed in hexadecimal, is <0947fa63>.

If we print these raw bytes in hexadecimal we get:Part 1: 0947fa63Part 2: 0a5510d3Part 3: 546d5b4b20543275

Part 3 came from "Tm[K T2u" in the response body. This is the 8 bytes you read from the socket. If you took those 8 bytes, and printed them as a series of 8 ascii characters you would get "Tm[K T2u". If you printed those 8 bytes as hexadecimal you would get "546d5b4b20543275".

So if we concatenate these parts we get <0947fa630a5510d3546d5b4b20543275>. Notice this is 16 bytes total.

We're almost done. The last step is to calculate the MD5 hash of <0947fa630a5510d3546d5b4b20543275>. Remember, you are passing this data to the MD5 routine as raw bytes. Don't convert it to a string, or anything like that.

You should get <66514a2c664e2f344634217e4b7e4d48> as the result. This is the result printed in hexadecimal. If we instead interpreted the 16 bytes as 16 ascii characters, we would get:fQJ,fN/4F4!~K~MH

You are to send these raw 16 bytes immediately after the HTTP response headers.

Nice article but it´s not working to me. In the step 4 you say to convert the value to big-endian. When i converted it to big-endian i got "7204148657912807424". In the step 6 i should concatenate the value of the key1 as big-endian (as i wrote) or as hexadecimal (0947fa63)? The third key (request body) i should concatenate as string?

Hey Deusty! Thanks for adding websocket support to cocoahttpserver! One request.. would you be kind to create a Websocket client that can connect and communicate with a Websocket server? All the other cocoa websocket libraries do not support the new draft spec. Thanks!

Thanks for the info. It help allot.For those who still have trubbel with this i can recomend wireshark for trubleshoting the comunication between client and server. And for examplet use echo.websocket.org as echo server to investigate server response.