Introduction
I am a Silverlight lover, and was looking around for a messenger, any messenger (Yahoo!, GTalk, AIM, ICQ, or MySpace) built with Silverlight, but unfortunately, there was not a single one (or at least I couldn't find any). So I decided to build one. I started playing around the YMSG packet, and soon found that it is possible to utilize Silverlight's Socket/Polling duplex service to develop a messenger application.
Bing Choosy
First, I had to discover how to push data to the client. I thought of going for Socket, but then the hosting limitation kicks in (I neither want to spend money on testing things out nor was sure about I could build a Yahoo! Messenger clone). So I stuck to the Duplex Polling WCF Service, which can be hosted in any normal ASP.NET hosting without having any extra permissions to run an exe or any fancy things.
OK, let's first decide what we will call it! I like beaches and surfing (though I haven't experienced surfing yet) and because it is a Yahoo! Messenger clone, let's call it ySurf. Cool, we got a name for our messenger and I think we should build the UI with a sea blue and sandy color (interesting, huh?). Based on these, we have the final application screenshot, and you can see it live at here.
YMSG: A Yahoo! Messenger Protocol
So far so good. We decided which technology to use, so now we will see how to actually build things. Let's dive into the Yahoo! Messenger part.
How to build the Yahoo! Messenger?
You need a custom tool from Yahoo!, named YDotNet, worth $500, and a messenger service subscription for $100/month. Scared? Don't worry, we don't need to buy anything. Luckily, Yahoo! will respond with data if we requested correctly in the correct format to Yahoo! servers. And that's all we will need.
What is the correct format to connect with Yahoo! servers?
Like everything else, Yahoo! too has a well defined protocol, YMSG. If we send a packet in YMSG format, Yahoo! will respond to it in the correct format (again, in the YMSG packet format).
So what is YMSG and what is its generalized packet format?
YMSG, as I said earlier, is a Yahoo! protocol used by none other than Yahoo! Messenger. The YMSG packet structure is as follows:
- 4B - YMSG (every packet starts with a static text, YMSG).
- 4B - version (we use version 16)
- 2B - Packet length
- 2B - Service code (it tells the client/server how to treat the content/payload of the packet)
- 4B - Status code (success/failure/typing etc...)
- 4B - Session ID (required for every communication, but for the first couple of requests, it can be zero, because we are authenticating the user credentials)
- 0-65535B - Data/Payload (the actual data or payload of the packet)
A sample would be a typing notify event, which when sent, looks like below:
So where are the actual Yahoo! servers?
Public Const YServer As String = "cs116.msg.ac4.yahoo.com"
Public Const YPort As String = "8001"
Public Const YProtocol As String = "16"
Now we know where to connect and what to send, but how do we connect and send?
Enough teaching, let's fire-up Visual Studio. We are so lucky that we do not need to build things from scratch. Someone has already built a Yahoo! Messenger, which is available at http://www.codeproject.com/KB/vb/YCC_Trainer.aspx, so I'm going to use it and extend it to use YMSG v16.
First, let me introduce the important classes to you.
modSocket
- As the name suggests, it is a wrapper around the internal socket communication, so if you don't know how to work with sockets (like me!), no worries, this class will allow you to send/receive YMSG packets. We are not going to edit this class (because I don't know how to work with it).
modYFunctions
- Using modSocket
, we can send/receive packets (bytes), but we don't want to work with bytes, so here comes another class which gives you a structured way to work with a YMSG packet. This class uses uPacket
(a structure with YMSG packet segments, so that we can do things like packet.Payload or packet.Service). This class basically converts incoming raw packet bytes to uPacket
s, and vice versa.
modByteFunctions
- This class holds all the basic functions to operate on byte arrays. This is another class which we should not update, and there is no need to. Just leave it as it is.
modUser
(don't know why such a wired name) - It is the entry point for establishing Yahoo! interaction (its object can be treated as a user). You can see there are many events, like, ProcessPacket
, LoginFailed
, NewMail
, MessageReceive
, NofiyReceive
, UserOffline
etc. If we want to add more functionality like add a new buddy or do conference calling, all that logic (packet parsing and creation) must go in this class.
- There are other classes, like
uBuddyData
, uMessage
, uServerData
etc. Just go through them; they are very simple and self-explanatory.
Now you have a basic knowledge of the classes and their usage, let's dive into the code and build a send message packet. We need to assume a little information. Suppose we have already authenticated ourselves and have a session ID (remember, YMSG requires a session ID for any packet which needs to be sent to the server). First, let's see what it looks like in WireShark. (I had to blur out the Yahoo! ID through which I'm capturing packets. To learn how to use Wireshark, just go to the end of the YMSG part.)
Every YMSG packet's payload consists of the same structure: <code><delimiter><content><delimiter>. I have edited the Wireshark content to remove the not-so-required content fields. We only need the above three things: sender, receiver, and the message to be sent. Let me decipher it for you:
- Version (16).
- Packet Length (46).
- Service: Message (6) - As I said earlier, Yahoo! has a set of defined services available (to see some of the available YMSG services and their hex code, look into the
yService
enum in the Enum.vb file).
- Status code: Default (0). Indicates user status (to see some of the available status codes, look at the
yStatus
enum in the Enum.vb file).
- Session ID: Hex. Provided by Yahoo! when we try to authenticate the username and password.
- Content: 1 - Current logged in user, 5 - User to whom we want to send message, and 14 - actual content of the message.
Now that we know the packet format for the send message, let's code it
First, we need to add Message (6) as a Service
enum. So, add Message
to the enum yService in Enum.vb and to pbService
(packet to byte) and bpService
(byte to packet) in the modYFunctions.vb file.
Now create a function which takes a single argument of type uMessage
(where uMessage
contains From
, To
, and Message
as public properties). Let's call it ySendMessage
. It will look like the following:
Public Sub ySendMessage(ByVal message As uMessage)
Dim packet As New uPacket
Dim byteData() As Byte = ascii.GetBytes("")
packet.yVersion = yVersion.Sixteen
packet.yService = yService.Message
packet.yStatus = yStatus.Available
packet.bSessionID = _CurrentUserData.aSessionID
packet.addPayLoad("1", message.strFrom)
packet.addPayLoad("5", message.strTo)
packet.addPayLoad("14", message.strMessage)
byteData = PacketToByte(packet)
_socket.Send(byteData)
End Sub
Pretty simple. That's it, we can use the above function to send a message. Similarly, we can inspect any packet in Wireshark and construct the related methods to build the exact same YMSG packet.
Now, some of you will definitely ask me, 'How about receiving response (packets) from Yahoo! servers and parsing them?'
I wanted to skip this part, but if you ask, let me elaborate it too. I'm not going into too much detail this time.
Wireshark view:
We need to parse this message, but where do we start (talking about the code)?
Public Sub modUser_ProcessPacket(ByVal packet As uPacket, _
ByVal receiveData() As Byte)
Handles Me.ProcessPacket
In this event handler, we need to concentrate on the packet (uPacket
). We can decide what to do with the content by considering the value of packet.yService
. And it is a message this time, so we just pass the packet information to the message processing function, which looks like below:
Public Sub Message(ByVal packet As uPacket)
Dim message As New uMessage
message.strFrom = acket.getPayLoadValue("4")
message.strMessage = packet.getPayLoadValue("14")
RaiseEvent MessageRecieve(message, Me.Username)
End Sub
How do we utilize Wireshark?
- You can download it for free from http://www.wireshark.org/download.html.
- Start Wireshark (after downloading, of course) and go to Capture->Interface. You will see your available network from which you can get packets. In my machine, it would look like:
You can see there is a network adapter which has the packet count. Hit the Start button against the working adapter. After hitting Start, you immediately see lots of packet, so it's time to apply a filter to it. Type "ymsg" into the filter box and hit Apply. That's it, now we will only see Yahoo! packets.
Silverlight Joins the Party
Give me some sunshine, give me some rain; give me another chance; so I wanna grow up once again. Fantastic song (from Bollywood movie, 3 Idiots). Always start with something pleasant.
We know how to communicate with Yahoo! Messenger servers, so now we build the UI for our Yahoo! Messenger Clone. No more time waste now, let me start with the Polling Duplex WCF Service.
Polling Duplex WCF service: do you know that Silverlight comes with the inbuilt functionality to build a WCF Service which can push data into the client? Yes, you heard it right, data push capability, so that your client does not need to ping the server every now and then for new messages. There are already many discussions and blog posts on this topic. I will include some of my favorite posts:
From the above articles, you will learn that we can push data to a client very simply. You want me to explain it again? The mechanism is very simple: You expose some methods as an OperationContract
, which the client can access using a proxy. When the client wants to communicate with the server (normal scenario), it just calls the WebService's method asynchronously, and when the server needs to push data to the client, it just calls the required method using the client's callback channel, with data needed to be passed as parameter(s).
Easy enough, but you might be wondering, what the hell is the client callback channel and how do we get it? To answer that, we need to look into the code, open the project in Visual Studio, and open the IYahooDuplexService.vb file located in the Web project in the WebService folder. There are two interfaces: one for the client to server (IDuplexClient
) and another for the server to client (IDuplexYahooService
) callback. Our actual WCF service is nothing more than a normal WCF service which implements IDuplexYahooService
. OK, now comes the tricky part. As I told you before, to push data, we need the client callback channel, and that we get from the client itself. So when the client first tries to connect with the server, we get its callback channel and store it for further use (see the Connect
method). Like in the Connect
method, the client sends the username, password, and visibility status, and the server stores the callback channel with the username (I repeat things a lot to remember them easily, a little problem with the memory, forgot to take my memory pills). Now, suppose the user login fails, and the server needs to tell this to the client, it does this by calling channel.LoginFailed
(where channel
is the client's callback channel object). On the other hand, the client needs to subscribe to this LoginFailed
event (which you can see in the constructor of MainPage.xaml). In the end, I think you people are smart enough to build a simple UI against the said theory.
The UI is not a difficult one, and with the help of Expression Studio, it is very easy to build something very cool without any design skills, like I did (I'm not praising myself!!!!). If you look at existing browser based messengers, you can think of a simple UI: buddy list in the left hand side and chat window opening in the right hand side. That's the basis of our messenger's UI too. About the login panel, it is just a login control, centrally aligned inside a Border
, with some opacity. Behind that border, we have a TreeView
and TabControl
, separated by a grid splitter.
Known Bugs
Sometimes we receive the same message twice. I could not figure-out why, if you do, then let me know. Also, if you try to login with the wrong credentials, then it just shows you the loading sign forever (I might have solved that problem....).
Closing Note
I built this project just for fun, and any kind of damage caused by using the given code or the method explained here are not my concern. Use the code as you like to. Also, I have published it at here. You can try it, and if there are bugs in the code, please send me a mail. And lastly, this is my first tech article ever, so please provide me with some feedback on what things you don't like in the article, the pace of the article, language, code, explanation, and diagrams. Let me know whatever you feel about the article.