Developers.Code.CHandshake

From Shareaza Wiki
Revision as of 20:07, 20 June 2009 by Kevogod (talk | contribs) (1 revision)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

CHandshake

The class CHandshake inherits from CConnection. This means it picks up all of the member variables and methods of CConnection, and adds more here. When you're looking at a CHandshake object, you can't easily tell which parts are from CConnection and which parts CHandshake added. They are all mixed together.

So, before you read this, make sure you are familiar with Developers.Code.CConnection. CHandshake already holds a socket, has the ability to connect it to a remote computer, and can read data through it in both directions. It can't do Zlib compression, though.

CHandshake adds member variables and methods to do the following jobs:

  1. Figure out what network the remote computer is on by the first 7 bytes it sends to us
  2. Process Gnutella GIV and Gnutella2 PUSH packets

Constructor and Destructor

There are three versions of the constructor for CHandshake. The first takes nothing and just makes a new object. Shareaza uses the second when a remote computer is connecting to us. It takes a socket from Windows and the IP address and port number of the remote computer, and uses CConnection's AcceptFrom to setup the new object. The third takes a pointer to another CConnection object, pCopy. It drops down to CConnection and calls AttachTo there to copy across the CConnection part of the object. Then, it copies the rest.

These constructors have the responsibility of initializing the CHandshake member variables. One, m_bPushing is used by Shareaza to remember if we've sent the remote computer a push packet, and are waiting for it to process it. This starts out as FALSE. This line of code also appears several places:

<source lang="c"> m_mInput.pLimit = m_mOutput.pLimit = &Settings.Bandwidth.Request; </source>

The structures m_mInput and m_mOutput are TCP bandwidth meters. Their pLimit members point to a DWORD that sets the speed limit they follow. This line of code points both at the address of Settings.Bandwidth.Request, the speed limit in Shareaza settings the user set for the entire program. Notice how this line of code copies across a pointer, and not the DWORD value. This means that later, if the user goes into Shareaza Settings and changes the value, both TCP bandwidth meters will get the new information.

The destructor for CHandshake doesn't contain any code. Since CHandshake inherits from CConnection, both destructors get called every time a CHandshake object gets deleted. CConnection cleans up everything we need it to.

Push

The Push method takes an IP address and port number. It connects to the remote computer at that address, tells Windows to fire a m_pWakeup event when anything happens with the socket, and sets m_bPushing to true.

OnRun

OnRun determines if the handshake has been going on for too long. Handshakes should never take longer than the number of milliseconds stored in Settings.Connection.TimeoutHandshake. The CConnection member variable m_tConnected records the tick count when the connection was made. GetTickCount() - m_tConnected is the number of milliseconds since then.

OnConnected

The OnConnected method composes a header that looks like this:

<source lang="c"> GIV index:guid/ </source>

The index used is CHandshake::m_nIndex. The GUID used is MyProfile.GUID. Both numbers identify our computer in the Gnutella network. With the header composed, OnConnected sends it to the other computer.

OnDropped

OnDropped only does something if m_bPushing is TRUE. And then, all it does is record the error.

OnRead

When we connect to a remote computer, it will start sending bytes. The OnRead method looks at the first few bytes, and tries to figure out what the other computer is running, and what it wants.

First, the method looks at the bytes as data. If the byte at 0 is e3 and the two bytes at 5 are 0110, the remote computer has sent us an eDonkey2000 Hello packet. We know it wants to communicate with eDonkey2000, and give EDClients.OnAccept this CNeighbour object to take things forward.

If the bytes are the ASCII characters \023BitTorrent protocol, the remote computer is running BitTorrent. Control passes to BTClients.OnAccept.

With these two networks detected, the method looks at the bytes as lines of text. It calls m_pInput->ReadLine to copy the first line into strLine. ReadLine copies the ASCII characters until it reaches the first \n. If there isn't a \n yet, ReadLine returns FALSE and OnRead returns TRUE. When OnRead returns TRUE, that means the caller should call it again so it can have another chance at reading the entire header. There might be some more data there in a little while.

Code in this part of the method guards against two possible cases. First, if the remote computer has sent a lot of bytes but not a single \n, something is wrong. The first header is usually very short. Second, if the computer sends an empty line, OnRead returns TRUE to have the calling function try again.

With these possibilities dealt with, OnRead can look at the very start of what the remote computer has told us. It might be:

GET or HEAD - The remote computer is talking to us as though we were a Web server. It wants to download a file from us. OnRead calls Uploads.OnAccept to upload it to the remote computer.

GNUTELLA - The remote computer wants to exchange Gnutella packets on the Gnutella network. This CNeighbour object is given to Neighbours.OnAccept.

PUSH or GIV - We are involved in a push operation with the remote computer.

CHAT - The user at the remote computer wants to text chat with our user.

OnAcceptPush

The first thing a remote computer says to us might be a header line like this:

<source lang="c"> PUSH guid:d51dff817f895598ff0065537c09d503\r\n </source>

We are firewalled. The computer with this GUID wants to open a connection to us, but can't. We should connect to them.

OnAcceptPush calls OnPush to see if a child window recognizes the GUID. The method returns TRUE if one did, FALSE if we weren't expecting it.

This part of the Gnutella2 specification talks about the Gnutella2 PUSH packet: [1]

OnAcceptGive

The first thing a remote computer says to us might be a header line like this:

<source lang="c"> GIV 124:d51dff817f895598ff0065537c09d503/my%20song.mp3\r\n </source>

Here are the parts:

  1. The text "GIV ", which ends with a space and takes up 4 bytes.
  2. The client id number, in this example it's 124.
  3. Exactly 32 letters, all 0-9 or a-f, that describe the GUID in hexidecimal notation.
  4. A slash, and after that, a file name. The text is URL-encoded, with %20 instead of spaces.

OnAcceptGive does the same thing as OnAcceptPush - it sees if a child window recognizes the GUID, and returns TRUE or FALSE.

OnPush

OnPush takes a GUID from a remote computer that has sent us a Gnutella2-style PUSH handshake. It needs to see if we know about this computer before. It does this in a very interesting way. It loops through all of the child windows in Shareaza, asking each if it has a record of the GUID. When a child window says yes, it knows about it, OnPush returns TRUE. If no window recognizes it, OnPush returns FALSE.

There is only one user interface to Shareaza, and there are most certainly many threads running this low-level networking code a the same time. This means that to talk to the windows safely, OnPush must make sure it has exclusive access. Code that looks like this does it:

<source lang="c"> CSingleLock pWindowLock( &theApp.m_pSection ); if ( pWindowLock.Lock( 250 ) ) { // Access granted, now we can talk to the windows

// Let other threads use theApp.m_pSection pWindowLock.Unlock(); } </source>

Developers.Topic.ThreadSynchronization