|
Hi Bob. I'm using your great library. I'm implementing application with sockets for the first time. I have one question. Is it possible to catch events on the begining of data receive and on the end of recieved data? It could be used for example for data transfer status (i.e. icon flashes green when data is being transfered).
Thanks
|
|
|
|
|
There isn't really such a thing as 'the end of received data' with TCP; it's a stream protocol, so you never know whether you got the end or if there's more about to come.
If you are using the message capabilities of the library, OnPartialReadMessage will give you an indication of the proportion of the message that has been received so far. If you are not, you can use OnReadBytes to tell you that some data has been received, perhaps using a timer to turn the icon back away from green. Both of these are fired in the callback to the internal .Net asynchronous socket API, and will be fired shortly after the first data is received, so you can use these as 'beginning of data receive'.
If you're using messages or text mode, the completion of a message (OnReadMessage or OnRead respectively) would do as 'the end of received data'.
|
|
|
|
|
Thanks for the explanation.
At this moment I'm using OnReadMessage to transfer strings. Next thing I'm going to need to do is transfer files, up to 100MB large. Can I continue to use OnReadMessage or should I choose any other method?
br
|
|
|
|
|
The size part of a message uses 4 bytes, so it should allow for messages up to 2GB. I've used it for file transfer, although not that large. The only issue is that there is no sort of 'resume on error' so if you get a dropped connection near the end you would have to resend the entire message (unless you write some form of recovery code).
In this case you should use OnPartialMessage to update a status display. Here is the handler from my game client, which does a similar thing:
void clientReadPartialMessage(ClientInfo ci, uint code, byte[] bytes, int start, int len, int sofar, int total){
if(sofar >= total) sockstatus.Text = "Connected.";
else sockstatus.Text = "Read "+sofar+" of "+total+" (" + (int)(100 * (sofar * 1.0f / total)) + "%)";
UpdateStatusOverlay(sofar, total);
}
|
|
|
|
|
Hi Bob,
Firstly, nice work on this wrapper. It simplifies what I have been trying to acheive for the past couple of weeks. Namely the encryption part of it.
However, I am having issues with the code for encryption, as it seems that some of this article is out of date compared to the version I am currently using (1.5.2009.0329).
My Client currently has this:
Socket socket = Sockets.CreateTCPSocket(serverAddress, serverPort);
client = new ClientInfo(socket, false);
client.Delimiter = "<EOF>";
client.EncryptionType = EncryptionType.ServerRSAClientKey;
client.OnReadBytes += new ConnectionReadBytes(ReadMessage);
client.BeginReceive();
and...
client.Send(message + "<EOF>");
My Server currently has this:
server = new Server(6721, new ClientEvent(ClientConnect));
server.DefaultEncryptionType = EncryptionType.ServerRSAClientKey;
and...
bool ClientConnect(Server serv, ClientInfo newClient)
{
newClient.Delimiter = "<EOF>";
LogMessage("Client connected: " + newClient.ID);
newClient.OnRead += new ConnectionRead(ReadData);
return true;
}
void ReadData(ClientInfo ci, String message)
{
LogMessage("Received: " + message);
}
The problem is that it seems to send the data, but does not receive at the Server end. I assume it is something to do with me using OnRead. I did read the article about OnReady, but the delegate does not seem to have a message parameter like OnRead does.
Note that the client successfully connects, and I receive the "Client connected" message in my test application.
What am I missing here?
Kind Regards,
James
modified on Friday, September 25, 2009 5:02 AM
|
|
|
|
|
I've never tried using the encryption with text mode sockets, so it's possible that that just doesn't work right. Have you tried turning the encryption off, to be sure that that's where the problem is?
OnReady is what you should use instead of OnConnect if you want to send messages immediately, it indicates that the socket is ready to accept messages to send; it's not related to reading data.
|
|
|
|
|
Ive got it successfully sending and receiving test with no problems. By what you are saying, I assume the encryption is designed for sending files, not text?
How would this implementation look? Maybe I can work out a way to use text.
|
|
|
|
|
Figured it out.
In the ClientReady handler, I used OnReadBytes instead of OnReadMessage, and converted it to a UTF8 string from there.
Works perfectly, thank you
|
|
|
|
|
Glad you worked it out. I use the encryption with messages (using SendMessage and OnReceiveMessage), and I simply haven't tested it for delimited text. I should probably make that work :p
Here's an example of the messaged communication (my test classes):
using System;
using RedCorona.Net;
class ServerTest {
static void Main(string[] args){
Server s = new Server(7000);
s.DefaultEncryptionType = EncryptionType.ServerRSAClientKey;
s.ClientReady += new ClientEvent(connect);
while(Console.ReadLine() != "exit"){
}
s.Close();
}
static bool connect(Server serv, ClientInfo new_client){
new_client.MessageType = MessageType.CodeAndLength;
new_client.SendMessage(0x01020304, new byte[]{0, 1, 2, 3, 4}, 0);
new_client.SendMessage(0x11111111, new byte[]{0,0,4,23}, ParameterType.Int);
new_client.OnReadMessage += new ConnectionReadMessage(ReadMessage);
return true;
}
static void ReadMessage(ClientInfo ci, uint code, byte[] buf, int len){
Console.WriteLine("Message, code "+code.ToString("X8")+", content:");
byte[] ba = new byte[len];
Array.Copy(buf, ba, len);
Console.WriteLine(" "+ByteBuilder.FormatParameter(new Parameter(ba, ParameterType.Byte)));
ci.SendMessage(code, buf, (byte)0, len);
}
}
using System;
using RedCorona.Net;
class ClientTest {
static void Main(string[] args){
string host = args.Length > 0 ? args[0] : "localhost";
int port = args.Length > 1 ? Int32.Parse(args[1]) : 7000;
Console.WriteLine("Running client test against "+host+":"+port);
ClientInfo ci = new ClientInfo(Sockets.CreateTCPSocket(host, port), false);
ci.EncryptionType = EncryptionType.ServerRSAClientKey;
ci.MessageType = MessageType.CodeAndLength;
ci.OnReadMessage += new ConnectionReadMessage(ReadMessage);
ci.BeginReceive();
string s;
while((s = Console.ReadLine()) != "exit"){
ci.SendMessage(0xABCDEF01, System.Text.Encoding.UTF8.GetBytes(s), 0);
}
ci.Close();
}
static void ReadMessage(ClientInfo ci, uint code, byte[] buf, int len){
Console.WriteLine("Message, code "+code.ToString("X8")+", content:");
byte[] ba = new byte[len];
Array.Copy(buf, ba, len);
Console.WriteLine(" "+ByteBuilder.FormatParameter(new Parameter(ba, ParameterType.Byte)));
}
}
OnReadBytes isn't 'packaged up' at all, so if you're using that you can get the usual TCP issues with multiple or partial messages appearing in one call.
|
|
|
|
|
Hi, I'm having problems using the code, It may be something stupid, but it´s my firts encounter with this topic. The problem is I can't get it to work (and I am not sure how to), OK, maybe I am more newbie than I suppose, but I really need this...
Here are my problems: these lines from MessageClient class report errors
client.MessageMode = MessageMode.Length; //MessageMode doesn't exist in RedCorona.net...
client.OnReadMessage += new ConnectionRead(ReadData); //no overload for 'ReadData' matches delegate...
new_client.Delimiter = '\n'; //can't cast char to string
in socket.cs the line Dns.GetHostByName(hostname).AddressList[0]; acuses of obsolet
duplicate AssemblyVersion...
and other similar problems. Can someone help me with this?... pleaaaaaaase
|
|
|
|
|
Looks like the MessageClient example is just wrong
client.MessageType = MessageType.CodeAndLength;
client.OnReadMessage += new ConnectionReadMessage(ReadData);
Delimiter got upgraded to allow multicharacter ones; you don't actually need that line at all though because the default is "\n".
The obsolesence is just a warning (under .Net 2) but I'll check out the way you're supposed to do that now.
modified on Tuesday, August 18, 2009 12:54 PM
|
|
|
|
|
Hi all experts,
I'm developing a Client/Server application in which I'm facing stream flushing problem. I'm sending an image file by writing its bytes in network stream, and on the receiving end I'm receiving the exact file but the problem is that when I again send another file, on the receiving end I receive the previously sent file. I'm also flushing the network stream afer first receive.
Is there any help in this problem?
thanks in advance!!!
|
|
|
|
|
Hello again,
I am developing a simple (nothing is ever simple) instant messaging application using the sockets.cs library. The basic server works fine with multiple human clients, but now I am trying to ratchet up the volume (so to speak) by having created a tester program that simulates multiple clients. I have run into several problems that I believe may be related to the asynchronous nature of this package.
First of all, I have had to make sure that I put synchronization locks (C# 'lock') around my main class data structure because of async events that caused issues. One example was that I keep a simple list of users, and even traversing that list would crash if another thread tried to change the list, e.g., due to a disconnect. I know that this is basic thread programming, but in this case the threads are hidden by the nature of async sockets. Still, I have found and fixed several problems like that.
Now I seem to be hitting similar problems that may involve sockets.cs. For example, when a client (ClientInfo) gets closed, the code issues a 'clients.Remove(ci);' in function ClientClosed. Isn't it possible that under load, this code has the same problem my application code had, namely, that the various async callback threads could collide trying to access this data structure?
For what it's worth, I am usually running about 20 clients at the same time, trying to send data to each other. Because of the simplistic design of the tester program (although each pseudo-client runs in its own thread), they will all send roughly the same amount of data, then all close at once. This is probably the worst case scenario for a server (I once caused an AT&T 5ESS switch module to crash while doing ISDN testing and hanging up on lots of calls nearly simultaneously, but that is a story for another time).
So I guess my questions are
1. Can you see that the scenario I am talking about might cause data corruption problems due to lack of explicit synchronization from the async threads?
2. Do you know of anyone else who has tried to use the sockets.cs library in this way?
3. I understand that the receives are handled asynchronously, but in TCP (and Winsock) *sends* can block as well. The book "TCP/IP Sockets in C#" mentions the use of the "EndWrite" IASyncResult as well as the "EndRead" (ReadCallback in sockets.cs). Did you consider implementing async writes?
Sorry for so many questions, but I really like the package, and would like to use it if it will scale upwards. Any help or advice you can give would be welcomed.
One final enhancement I would suggest would be to include a string argument for the OnClose event, and to pass back any SocketException error message that may occur. I modified the code to catch some of these errors (remember that many times they are "normal" errors, e.g., because the other side was closing) so they could be logged. The sockets.cs code could catch these and try to pass them up to the user OnClose routine.
Thanks again,
Mitch
|
|
|
|
|
Thanks very much for your information! I have not used the library under load myself and having this information is excellent for working out what might not be working well.
Yes, I can see that the asynchronous nature of the event handlers could cause issues unless you use locks. However, I think ArrayList.Remove is a thread-atomic operation so the server's code should be safe within itself.
You can attach client-specific data structure to ClientInfo.Data, instead of holding a parallel data structure. I think you would still need to lock something to iterate through it though. (If you're broadcasting data, use the Broadcast or BroadcastMessage methods.) You shouldn't need to do that very often; perhaps I should provide a sync-root property that you can use to thread-safe those operations (and lock(syncRoot) around server ops that change the client list).
I have never had a problem with a send call blocking for significant time, so I've never felt the need to do the async send.
Also, is there any chance you could send me your test program? It sounds like a good way to work with these issues.
|
|
|
|
|
I will have to see if I can send you any code. I am working on a project for a company, and obviously they own the rights to it. Still, I will check with my boss and explain the problem, and to ask if I can send something to you.
I can tell you that I created a simple instant message application that uses your encryption feature to send messages with a code. The messages are just text, although they are encrypted on the wire.
My most recent crash just occurred, and was in the sockets.cs function
public ClientInfo this[int id]
which is in the Server object.
The error I received was InvalidOperationException was unhandled; Collection was modified; enumeration operation may not execute.
The context was that of my 30 users (your ClientInfo) I had active at the time, one client connection had been closed, so I iterate over my list of clients and call SendMessage to notify all of the other IM users that one went away.
The actual error line pointed to
foreach (ClientInfo ci in Clients)
I have made changes to sockets.cs. I can include it below just in case you want to see what I have done. Some of the changes are cosmetic (sorry!), but you will see it catch some specific exceptions and generate a simple Application exception that my app can catch. I think 100% of these are really just SocketExceptions when a client is closing and someone is trying to send, etc.
Here is the modified code:
// Client-server helpers for TCP/IP
// ClientInfo: wrapper for a socket which throws
// Receive events, and allows for messaged communication (using
// a specified character as end-of-message)
// Server: simple TCP server that throws Connect events
// ByteBuilder: utility class to manage byte arrays built up
// in multiple transactions
// (C) Richard Smith 2005-8
// bobjanova@gmail.com
// You can use this for free and give it to people as much as you like
// as long as you leave a credit to me .
// Code to connect to a SOCKS proxy modified from
// http://www.thecodeproject.com/csharp/ZaSocks5Proxy.asp
// Define this symbol to include console output in various places
#define DEBUG
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Collections;
using System.Net.Sockets;
using System.Security.Cryptography;
using Netsalon.Cryptography;
[assembly: System.Reflection.AssemblyVersion("1.4.2008.0330")]
namespace Netsalon.Sockets
{
public delegate void ConnectionRead(ClientInfo ci, String text);
public delegate void ConnectionClosed(ClientInfo ci);
public delegate void ConnectionReadBytes(ClientInfo ci, byte[] bytes, int len);
public delegate void ConnectionReadMessage(ClientInfo ci, uint code, byte[] bytes, int len);
public delegate void ConnectionReadPartialMessage(ClientInfo ci, uint code, byte[] bytes, int start, int read, int sofar, int totallength);
public delegate void ConnectionNotify(ClientInfo ci);
public enum ClientDirection { In, Out, Left, Right, Both };
public enum MessageType { Unmessaged, EndMarker, Length, CodeAndLength };
// ServerDES: The server sends an encryption key on connect
// ServerRSAClientDES: The server sends an RSA public key, the client sends back a key
public enum EncryptionType { None, ServerKey, ServerRSAClientKey };
public class EncryptionUtils
{
static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
public static byte[] GetRandomBytes(int length, bool addByte)
{
if (addByte && (length > 255))
throw new ArgumentException("Length must be 1 byte <256");
byte[] random = new byte[length + (addByte ? 1 : 0)];
rng.GetBytes(random);
if (addByte)
random[0] = (byte)length;
return random;
}
}
// OnReadBytes: catch the raw bytes as they arrive
// OnRead: for text ended with a marker character
// OnReadMessage: for binary info on a messaged client
//-----------------------------------------------------------------------
//
// c l a s s C l i e n t I n f o
//
//-----------------------------------------------------------------------
public class ClientInfo
{
internal Server server = null;
private Socket sock;
private String buffer;
public event ConnectionRead OnRead;
public event ConnectionClosed OnClose;
public event ConnectionReadBytes OnReadBytes;
public event ConnectionReadMessage OnReadMessage;
public event ConnectionReadPartialMessage OnPartialMessage;
public event ConnectionNotify OnReady;
public MessageType MessageType;
private ClientDirection dir;
int id;
bool alreadyclosed = false;
public static int NextID = 100;
//private ClientThread t;
public object Data = null;
// Encryption info
EncryptionType encType;
int encRead = 0, encStage, encExpected;
internal bool encComplete;
internal byte[] encKey;
internal RSAParameters encParams;
public EncryptionType EncryptionType
{
get { return encType; }
set
{
if (encStage != 0) throw new ArgumentException("Key exchange has already begun");
encType = value;
encComplete = encType == EncryptionType.None;
encExpected = -1;
}
}
public bool EncryptionReady { get { return encComplete; } }
internal ICryptoTransform encryptor, decryptor;
public ICryptoTransform Encryptor { get { return encryptor; } }
public ICryptoTransform Decryptor { get { return decryptor; } }
private string delim;
public const int BUFSIZE = 1024;
byte[] buf = new byte[BUFSIZE];
ByteBuilder bytes = new ByteBuilder(10);
byte[] msgheader = new byte[8];
byte headerread = 0;
bool wantingChecksum = true;
public string Delimiter
{
get { return delim; }
set { delim = value; }
}
public ClientDirection Direction { get { return dir; } }
public Socket Socket { get { return sock; } }
public Server Server { get { return server; } }
public int ID { get { return id; } }
public bool Closed
{
get { return !sock.Connected; }
}
public ClientInfo(Socket cl, bool StartNow) : this(cl, null, null, ClientDirection.Both, StartNow, EncryptionType.None) { }
//public ClientInfo(Socket cl, ConnectionRead read, ConnectionReadBytes readevt, ClientDirection d) : this(cl, read, readevt, d, true, EncryptionType.None) {}
public ClientInfo(Socket cl, ConnectionRead read, ConnectionReadBytes readevt, ClientDirection d, bool StartNow) : this(cl, read, readevt, d, StartNow, EncryptionType.None) { }
public ClientInfo(Socket cl, ConnectionRead read, ConnectionReadBytes readevt, ClientDirection d, bool StartNow, EncryptionType encryptionType)
{
sock = cl; buffer = ""; OnReadBytes = readevt; encType = encryptionType;
encStage = 0; encComplete = encType == EncryptionType.None;
OnRead = read;
MessageType = MessageType.EndMarker;
dir = d; delim = "\n";
id = NextID; // Assign each client an application-unique ID
unchecked { NextID++; }
//t = new ClientThread(this);
if (StartNow)
BeginReceive();
}
public void BeginReceive()
{
#if DEBUG
Console.WriteLine("BeginReceive: enter");
#endif
// t.t.Start();
sock.BeginReceive(buf, 0, BUFSIZE, 0, new AsyncCallback(ReadCallback), this);
}
public String Send(String text)
{
byte[] ba = Encoding.UTF8.GetBytes(text);
String s = "";
for (int i = 0; i < ba.Length; i++)
s += ba[i] + " ";
Send(ba);
return s;
}
public void SendMessage(uint code, byte[] bytes) { SendMessage(code, bytes, 0, bytes.Length); }
public void SendMessage(uint code, byte[] bytes, byte paramType) { SendMessage(code, bytes, paramType, bytes.Length); }
public void SendMessage(uint code, byte[] bytes, byte paramType, int len)
{
if (paramType > 0)
{
ByteBuilder b = new ByteBuilder(3);
b.AddParameter(bytes, paramType);
bytes = b.Read(0, b.Length);
len = bytes.Length;
}
lock (sock)
{
byte checksum = 0; byte[] ba;
switch (MessageType)
{
case MessageType.CodeAndLength:
Send(ba = UintToBytes(code));
for (int i = 0; i < 4; i++) checksum += ba[i];
Send(ba = IntToBytes(len));
for (int i = 0; i < 4; i++) checksum += ba[i];
if (encType != EncryptionType.None) Send(new byte[] { checksum });
break;
case MessageType.Length:
Send(ba = IntToBytes(len));
for (int i = 0; i < 4; i++) checksum += ba[i];
if (encType != EncryptionType.None) Send(new byte[] { checksum });
break;
}
Send(bytes, len);
if (encType != EncryptionType.None)
{
checksum = 0;
for (int i = 0; i < len; i++) checksum += bytes[i];
Send(new byte[] { checksum });
}
}
}
public void Send(byte[] bytes) { Send(bytes, bytes.Length); }
public void Send(byte[] bytes, int len)
{
if (!encComplete) throw new IOException("Key exchange is not yet completed");
if (encType != EncryptionType.None)
{
byte[] outbytes = new byte[len];
Encryptor.TransformBlock(bytes, 0, len, outbytes, 0);
bytes = outbytes;
//Console.Write("Encryptor IV: "); LogBytes(encryptor.Key, encryptor.IV.length);
}
#if DEBUG
Console.Write(ID + " Sent: "); LogBytes(bytes, len);
#endif
try
{
sock.Send(bytes, len, SocketFlags.None);
}
catch (SocketException e)
{
string errMsg = "ERROR: got Socket Exception errorCode " + e.ErrorCode.ToString() + " trying to send on socket";
Console.WriteLine(errMsg);
Console.WriteLine(e.StackTrace);
throw new ApplicationException(errMsg);
}
}
public bool MessageWaiting()
{
FillBuffer(sock);
return buffer.IndexOf(delim) >= 0;
}
public String Read()
{
//FillBuffer(sock);
int p = buffer.IndexOf(delim);
if (p >= 0)
{
String res = buffer.Substring(0, p);
buffer = buffer.Substring(p + delim.Length);
return res;
}
else return "";
}
private void FillBuffer(Socket sock)
{
byte[] buf = new byte[256];
int read;
while (sock.Available != 0)
{
read = sock.Receive(buf);
if (OnReadBytes != null) OnReadBytes(this, buf, read);
buffer += Encoding.UTF8.GetString(buf, 0, read);
}
}
void ReadCallback(IAsyncResult ar)
{
try
{
int read = sock.EndReceive(ar);
#if DEBUG
Console.WriteLine("Socket " + ID + " read " + read + " bytes");
#endif
if (read > 0)
{
DoRead(buf, read);
BeginReceive();
}
else
{
#if DEBUG
Console.WriteLine(ID + " zero byte read closure");
#endif
Close();
}
}
catch (SocketException e)
{
#if DEBUG
Console.WriteLine(ID + " socket exception closure: " + e);
#endif
Close();
}
catch (ObjectDisposedException)
{
#if DEBUG
Console.WriteLine(ID + " disposed exception closure");
#endif
Close();
}
}
internal void DoRead(byte[] buf, int read)
{
if (read > 0)
{
if (OnRead != null)
{ // Simple text mode
buffer += Encoding.UTF8.GetString(buf, 0, read);
while (buffer.IndexOf(delim) >= 0)
OnRead(this, Read());
}
}
ReadInternal(buf, read, false);
}
public static void LogBytes(byte[] buf, int len)
{
byte[] ba = new byte[len];
Array.Copy(buf, ba, len);
Console.WriteLine(ByteBuilder.FormatParameter(new Parameter(ba, ParameterType.Byte)));
}
void ReadInternal(byte[] buf, int read, bool alreadyEncrypted)
{
if ((!alreadyEncrypted) && (encType != EncryptionType.None))
{
if (encComplete)
{
#if DEBUG
Console.Write(ID + " Received: "); LogBytes(buf, read);
#endif
buf = decryptor.TransformFinalBlock(buf, 0, read);
#if DEBUG
Console.Write(ID + " Decrypted: "); LogBytes(buf, read);
#endif
}
else
{
// Client side key exchange
int ofs = 0;
if (encExpected < 0)
{
encStage++;
ofs++; read--; encExpected = buf[0]; // length of key to come
encKey = new byte[encExpected];
encRead = 0;
}
if (read >= encExpected)
{
Array.Copy(buf, ofs, encKey, encRead, encExpected);
int togo = read - encExpected;
encExpected = -1;
#if DEBUG
Console.WriteLine(ID + " Read encryption key: " + ByteBuilder.FormatParameter(new Parameter(encKey, ParameterType.Byte)));
#endif
if (server == null) ClientEncryptionTransferComplete();
else ServerEncryptionTransferComplete();
if (togo > 0)
{
byte[] newbuf = new byte[togo];
Array.Copy(buf, read + ofs - togo, newbuf, 0, togo);
ReadInternal(newbuf, togo, false);
}
}
else
{
Array.Copy(buf, ofs, encKey, encRead, read);
encExpected -= read; encRead += read;
}
return;
}
}
if ((!alreadyEncrypted) && (OnReadBytes != null)) OnReadBytes(this, buf, read);
if ((OnReadMessage != null) && (MessageType != MessageType.Unmessaged))
{
// Messaged mode
int copied;
uint code = 0;
switch (MessageType)
{
case MessageType.CodeAndLength:
case MessageType.Length:
int length;
if (MessageType == MessageType.Length)
{
copied = FillHeader(ref buf, 4, read);
if (headerread < 4) break;
length = GetInt(msgheader, 0, 4);
}
else
{
copied = FillHeader(ref buf, 8, read);
if (headerread < 8) break;
code = (uint)GetInt(msgheader, 0, 4);
length = GetInt(msgheader, 4, 4);
}
if (read == copied)
break;
// If encryption is on, the next byte is a checksum of the header
int ofs = 0;
if (wantingChecksum && (encType != EncryptionType.None))
{
byte checksum = buf[0];
ofs++;
wantingChecksum = false;
byte headersum = 0;
for (int i = 0; i < 8; i++)
headersum += msgheader[i];
if (checksum != headersum)
{
Close();
throw new IOException("Client ID " + ID + " header checksum failed! (was " + checksum + ", calculated " + headersum + ")");
}
}
bytes.Add(buf, ofs, read - ofs - copied);
if (encType != EncryptionType.None) length++; // checksum byte
// Now we know we are reading into the body of the message
#if DEBUG
Console.WriteLine(ID + " Added " + (read - ofs - copied) + " bytes, have " + bytes.Length + " of " + length);
#endif
if (OnPartialMessage != null) OnPartialMessage(this, code, buf, ofs, read - ofs - copied, bytes.Length, length);
if (bytes.Length >= length)
{
// A message was received!
headerread = 0;
wantingChecksum = true;
byte[] msg = bytes.Read(0, length);
if (encType != EncryptionType.None)
{
byte checksum = msg[length - 1], msgsum = 0;
for (int i = 0; i < length - 1; i++)
msgsum += msg[i];
if (checksum != msgsum)
{
Close();
throw new IOException("Content checksum failed! (was " + checksum + ", calculated " + msgsum + ")");
}
OnReadMessage(this, code, msg, length - 1);
}
else
OnReadMessage(this, code, msg, length);
// Don't forget to put the rest through the mill
int togo = bytes.Length - length;
if (togo > 0)
{
byte[] whatsleft = bytes.Read(length, togo);
bytes.Clear();
ReadInternal(whatsleft, whatsleft.Length, true);
}
else bytes.Clear();
}
//if(OnStatus != null) OnStatus(this, bytes.Length, length);
break;
}
}
}
int FillHeader(ref byte[] buf, int to, int read)
{
int copied = 0;
if (headerread < to)
{
// First copy the header into the header variable.
for (int i = 0; (i < read) && (headerread < to); i++, headerread++, copied++)
{
msgheader[headerread] = buf[i];
}
}
if (copied > 0)
{
// Take the header bytes off the 'message' section
byte[] newbuf = new byte[read - copied];
for (int i = 0; i < newbuf.Length; i++)
newbuf[i] = buf[i + copied];
buf = newbuf;
}
return copied;
}
internal ICryptoTransform MakeEncryptor() { return MakeCrypto(true); }
internal ICryptoTransform MakeDecryptor() { return MakeCrypto(false); }
internal ICryptoTransform MakeCrypto(bool encrypt)
{
if (encrypt) return new SimpleEncryptor(encKey);
else return new SimpleDecryptor(encKey);
}
void ServerEncryptionTransferComplete()
{
switch (encType)
{
case EncryptionType.None:
throw new ArgumentException("Should not have key exchange for unencrypted connection!");
case EncryptionType.ServerKey:
throw new ArgumentException("Should not have server-side key exchange for server keyed connection!");
case EncryptionType.ServerRSAClientKey:
// Symmetric key is in RSA-encoded encKey
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(encParams);
encKey = rsa.Decrypt(encKey, false);
#if DEBUG
Console.WriteLine("Symmetric key is: "); LogBytes(encKey, encKey.Length);
#endif
MakeEncoders();
server.KeyExchangeComplete(this);
break;
}
}
void ClientEncryptionTransferComplete()
{
// A part of the key exchange process has been completed, and the key is
// in encKey
switch (encType)
{
case EncryptionType.None:
throw new ArgumentException("Should not have key exchange for unencrypted connection!");
case EncryptionType.ServerKey:
// key for transfer is now in encKey, so all is good
MakeEncoders();
break;
case EncryptionType.ServerRSAClientKey:
// Stage 1: modulus; Stage 2: exponent
// When the exponent arrives, create a random DES key
// and send it
switch (encStage)
{
case 1: encParams.Modulus = encKey; break;
case 2:
encParams.Exponent = encKey;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(encParams);
encKey = EncryptionUtils.GetRandomBytes(24, false);
byte[] send = GetLengthEncodedVector(rsa.Encrypt(encKey, false));
sock.Send(send);
MakeEncoders();
break;
}
break;
}
}
internal void MakeEncoders()
{
encryptor = MakeEncryptor();
decryptor = MakeDecryptor();
#if ORIG
if (OnReady != null)
OnReady(this);
encComplete = true;
#else
// MJM: the doc says that when OnReady is called, the app can start sending,
// but if encComplete is not set to true, it generates an exception. This looks
// like a bug to me, so I am trying this. It doesn't look like it will hurt anything.
encComplete = true;
if (OnReady != null)
OnReady(this);
#endif
}
public static byte[] GetLengthEncodedVector(byte[] from)
{
int l = from.Length;
if (l > 255) throw new ArgumentException("Cannot length encode more than 255");
byte[] to = new byte[l + 1];
to[0] = (byte)l;
Array.Copy(from, 0, to, 1, l);
return to;
}
public static int GetInt(byte[] ba, int from, int len)
{
int r = 0;
for (int i = 0; i < len; i++)
r += ba[from + i] << ((len - i - 1) * 8);
return r;
}
public static int[] GetIntArray(byte[] ba) { return GetIntArray(ba, 0, ba.Length); }
public static int[] GetIntArray(byte[] ba, int from, int len)
{
int[] res = new int[len / 4];
for (int i = 0; i < res.Length; i++)
{
res[i] = GetInt(ba, from + (i * 4), 4);
}
return res;
}
public static uint[] GetUintArray(byte[] ba)
{
uint[] res = new uint[ba.Length / 4];
for (int i = 0; i < res.Length; i++)
{
res[i] = (uint)GetInt(ba, i * 4, 4);
}
return res;
}
public static byte[] IntToBytes(int val) { return UintToBytes((uint)val); }
public static byte[] UintToBytes(uint val)
{
byte[] res = new byte[4];
for (int i = 3; i >= 0; i--)
{
res[i] = (byte)val; val >>= 8;
}
return res;
}
public static byte[] IntArrayToBytes(int[] val)
{
byte[] res = new byte[val.Length * 4];
for (int i = 0; i < val.Length; i++)
{
byte[] vb = IntToBytes(val[i]);
res[(i * 4)] = vb[0];
res[(i * 4) + 1] = vb[1];
res[(i * 4) + 2] = vb[2];
res[(i * 4) + 3] = vb[3];
}
return res;
}
public static byte[] UintArrayToBytes(uint[] val)
{
byte[] res = new byte[val.Length * 4];
for (uint i = 0; i < val.Length; i++)
{
byte[] vb = IntToBytes((int)val[i]);
res[(i * 4)] = vb[0];
res[(i * 4) + 1] = vb[1];
res[(i * 4) + 2] = vb[2];
res[(i * 4) + 3] = vb[3];
}
return res;
}
public static byte[] StringArrayToBytes(string[] val, Encoding e)
{
byte[][] baa = new byte[val.Length][];
int l = 0;
for (int i = 0; i < val.Length; i++) { baa[i] = e.GetBytes(val[i]); l += 4 + baa[i].Length; }
byte[] r = new byte[l + 4];
IntToBytes(val.Length).CopyTo(r, 0);
int ofs = 4;
for (int i = 0; i < baa.Length; i++)
{
IntToBytes(baa[i].Length).CopyTo(r, ofs); ofs += 4;
baa[i].CopyTo(r, ofs); ofs += baa[i].Length;
}
return r;
}
public static string[] GetStringArray(byte[] ba, Encoding e)
{
int l = GetInt(ba, 0, 4), ofs = 4;
string[] r = new string[l];
for (int i = 0; i < l; i++)
{
int thislen = GetInt(ba, ofs, 4); ofs += 4;
r[i] = e.GetString(ba, ofs, thislen); ofs += thislen;
}
return r;
}
public void Close()
{
if (!alreadyclosed)
{
if (server != null)
server.ClientClosed(this);
if (OnClose != null)
OnClose(this);
alreadyclosed = true;
#if DEBUG
Console.WriteLine("(ClientInfo) **closed client** at " + DateTime.Now.Ticks);
#endif
}
sock.Close();
}
} // end class ClientInfo
//-----------------------------------------------------------------------
//
// c l a s s S o c k e t s
//
//-----------------------------------------------------------------------
public class Sockets
{
// Socks proxy inspired by http://www.thecodeproject.com/csharp/ZaSocks5Proxy.asp
public static SocksProxy SocksProxy;
public static bool UseSocks = false;
public static Socket CreateTCPSocket(String address, int port) { return CreateTCPSocket(address, port, UseSocks, SocksProxy); }
public static Socket CreateTCPSocket(String address, int port, bool useSocks, SocksProxy proxy)
{
Socket sock;
if (useSocks) sock = ConnectToSocksProxy(proxy.host, proxy.port, address, port, proxy.username, proxy.password);
else
{
IPAddress host = Dns.GetHostByName(address).AddressList[0];
sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
sock.Connect(new IPEndPoint(host, port));
}
catch (SocketException e)
{
throw new ApplicationException("Socket connect failed, errno " + e.Message);
}
}
return sock;
}
private static string[] errorMsgs = {
"Operation completed successfully.",
"General SOCKS server failure.",
"Connection not allowed by ruleset.",
"Network unreachable.",
"Host unreachable.",
"Connection refused.",
"TTL expired.",
"Command not supported.",
"Address type not supported.",
"Unknown error."
};
public static Socket ConnectToSocksProxy(IPAddress proxyIP, int proxyPort, String destAddress, int destPort, string userName, string password)
{
byte[] request = new byte[257];
byte[] response = new byte[257];
ushort nIndex;
IPAddress destIP = null;
try { destIP = IPAddress.Parse(destAddress); }
catch { }
IPEndPoint proxyEndPoint = new IPEndPoint(proxyIP, proxyPort);
// open a TCP connection to SOCKS server...
Socket s;
s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s.Connect(proxyEndPoint);
/* } catch(SocketException){
throw new SocketException(0, "Could not connect to proxy server.");
}*/
nIndex = 0;
request[nIndex++] = 0x05; // Version 5.
request[nIndex++] = 0x02; // 2 Authentication methods are in packet...
request[nIndex++] = 0x00; // NO AUTHENTICATION REQUIRED
request[nIndex++] = 0x02; // USERNAME/PASSWORD
// Send the authentication negotiation request...
s.Send(request, nIndex, SocketFlags.None);
// Receive 2 byte response...
int nGot = s.Receive(response, 2, SocketFlags.None);
if (nGot != 2)
throw new ConnectionException("Bad response received from proxy server.");
if (response[1] == 0xFF)
{ // No authentication method was accepted close the socket.
s.Close();
throw new ConnectionException("None of the authentication method was accepted by proxy server.");
}
byte[] rawBytes;
if (/*response[1]==0x02*/true)
{//Username/Password Authentication protocol
nIndex = 0;
request[nIndex++] = 0x05; // Version 5.
// add user name
request[nIndex++] = (byte)userName.Length;
rawBytes = Encoding.Default.GetBytes(userName);
rawBytes.CopyTo(request, nIndex);
nIndex += (ushort)rawBytes.Length;
// add password
request[nIndex++] = (byte)password.Length;
rawBytes = Encoding.Default.GetBytes(password);
rawBytes.CopyTo(request, nIndex);
nIndex += (ushort)rawBytes.Length;
// Send the Username/Password request
s.Send(request, nIndex, SocketFlags.None);
// Receive 2 byte response...
nGot = s.Receive(response, 2, SocketFlags.None);
if (nGot != 2)
throw new ConnectionException("Bad response received from proxy server.");
if (response[1] != 0x00)
throw new ConnectionException("Bad Usernaem/Password.");
}
// This version only supports connect command.
// UDP and Bind are not supported.
// Send connect request now...
nIndex = 0;
request[nIndex++] = 0x05; // version 5.
request[nIndex++] = 0x01; // command = connect.
request[nIndex++] = 0x00; // Reserve = must be 0x00
if (destIP != null)
{// Destination adress in an IP.
switch (destIP.AddressFamily)
{
case AddressFamily.InterNetwork:
// Address is IPV4 format
request[nIndex++] = 0x01;
rawBytes = destIP.GetAddressBytes();
rawBytes.CopyTo(request, nIndex);
nIndex += (ushort)rawBytes.Length;
break;
case AddressFamily.InterNetworkV6:
// Address is IPV6 format
request[nIndex++] = 0x04;
rawBytes = destIP.GetAddressBytes();
rawBytes.CopyTo(request, nIndex);
nIndex += (ushort)rawBytes.Length;
break;
}
}
else
{// Dest. address is domain name.
request[nIndex++] = 0x03; // Address is full-qualified domain name.
request[nIndex++] = Convert.ToByte(destAddress.Length); // length of address.
rawBytes = Encoding.Default.GetBytes(destAddress);
rawBytes.CopyTo(request, nIndex);
nIndex += (ushort)rawBytes.Length;
}
// using big-edian byte order
byte[] portBytes = BitConverter.GetBytes((ushort)destPort);
for (int i = portBytes.Length - 1; i >= 0; i--)
request[nIndex++] = portBytes[i];
// send connect request.
s.Send(request, nIndex, SocketFlags.None);
s.Receive(response); // Get variable length response...
if (response[1] != 0x00)
throw new ConnectionException(errorMsgs[response[1]]);
// Success Connected...
return s;
}
} // end class Sockets
public struct SocksProxy
{
public IPAddress host;
public ushort port;
public string username, password;
public SocksProxy(String hostname, ushort port, String username, String password)
{
this.port = port;
host = Dns.GetHostByName(hostname).AddressList[0];
this.username = username; this.password = password;
}
}
public class ConnectionException : Exception
{
public ConnectionException(string message) : base(message) { }
}
// Server code cribbed from Framework Help
public delegate bool ClientEvent(Server serv, ClientInfo new_client); // whether to accept the client
//-----------------------------------------------------------------------
//
// c l a s s S e r v e r
//
//-----------------------------------------------------------------------
public class Server
{
class ClientState
{
// To hold the state information about a client between transactions
internal Socket Socket = null;
internal const int BufferSize = 1024;
internal byte[] buffer = new byte[BufferSize];
internal StringBuilder sofar = new StringBuilder();
internal ClientState(Socket sock)
{
Socket = sock;
}
}
ArrayList clients = new ArrayList();
Socket ss;
public event ClientEvent Connect, ClientReady;
public IEnumerable Clients
{
get { return clients; }
}
public Socket ServerSocket
{
get { return ss; }
}
public ClientInfo this[int id]
{
get
{
foreach (ClientInfo ci in Clients)
if (ci.ID == id) return ci;
return null;
}
}
private EncryptionType encType;
public EncryptionType DefaultEncryptionType
{
get { return encType; }
set { encType = value; }
}
public int Port
{
get { return ((IPEndPoint)ss.LocalEndPoint).Port; }
}
public Server(int port) : this(port, null) { }
public Server(int port, ClientEvent connDel)
{
Connect = connDel;
#if DEBUG
Console.WriteLine("(Server) constructor called, port = " + port.ToString());
#endif
ss = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ss.Bind(new IPEndPoint(IPAddress.Any, port));
ss.Listen(100);
// Start the accept process. When a connection is accepted, the callback
// must do this again to accept another connection
#if DEBUG
Console.WriteLine("(Server) calling Accept()");
#endif
ss.BeginAccept(new AsyncCallback(AcceptCallback), ss);
}
internal void ClientClosed(ClientInfo ci)
{
#if DEBUG
Console.WriteLine("(Server) closed client " + ci.ID.ToString() + " at " + DateTime.Now.Ticks);
#endif
clients.Remove(ci);
}
public void Broadcast(byte[] bytes)
{
foreach (ClientInfo ci in clients) ci.Send(bytes);
}
public void BroadcastMessage(uint code, byte[] bytes) { BroadcastMessage(code, bytes, 0); }
public void BroadcastMessage(uint code, byte[] bytes, byte paramType)
{
foreach (ClientInfo ci in clients) ci.SendMessage(code, bytes, paramType);
}
// ASYNC CALLBACK CODE
void AcceptCallback(IAsyncResult ar)
{
try
{
Socket server = (Socket)ar.AsyncState;
Socket cs = server.EndAccept(ar);
#if DEBUG
Console.WriteLine("(Server-AcceptCallback) enter");
#endif
// Start the thing listening again
server.BeginAccept(new AsyncCallback(AcceptCallback), server);
ClientInfo c = new ClientInfo(cs, null, null, ClientDirection.Both, false);
c.server = this;
// Allow the new client to be rejected by the application
if (Connect != null)
{
if (!Connect(this, c))
{
// Rejected
cs.Close();
return;
}
}
// Initiate key exchange
c.EncryptionType = encType;
switch (encType)
{
case EncryptionType.None:
KeyExchangeComplete(c);
break;
case EncryptionType.ServerKey:
c.encKey = GetSymmetricKey();
byte[] key = ClientInfo.GetLengthEncodedVector(c.encKey);
cs.Send(key);
c.MakeEncoders();
KeyExchangeComplete(c);
break;
case EncryptionType.ServerRSAClientKey:
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters p = rsa.ExportParameters(true);
cs.Send(ClientInfo.GetLengthEncodedVector(p.Modulus));
cs.Send(ClientInfo.GetLengthEncodedVector(p.Exponent));
c.encParams = p;
break;
default:
throw new ArgumentException("Unknown or unsupported encryption type " + encType);
} // end switch
clients.Add(c);
c.BeginReceive();
}
catch (ObjectDisposedException) { }
catch (SocketException)
{
throw new ApplicationException("Socket error in AcceptCallBack");
}
catch (Exception e)
{
Console.WriteLine(e);
}
} // end AcceptCallBack
protected virtual byte[] GetSymmetricKey()
{
return EncryptionUtils.GetRandomBytes(24, false);
}
internal void KeyExchangeComplete(ClientInfo ci)
{
// Key exchange is complete on this client. Client ready
// handlers may still force a close of the connection
if (ClientReady != null)
if (!ClientReady(this, ci)) ci.Close();
}
~Server()
{
Close();
}
public void Close()
{
ArrayList cl2 = new ArrayList();
foreach (ClientInfo c in clients) cl2.Add(c);
foreach (ClientInfo c in cl2) c.Close();
ss.Close();
}
} // end class Server
//-----------------------------------------------------------------------
//
// c l a s s B y t e B u i l d e r
//
//-----------------------------------------------------------------------
public class ByteBuilder : MarshalByRefObject
{
byte[][] data;
int packsize, used;
public int Length
{
get
{
int len = 0;
for (int i = 0; i < used; i++) len += data[i].Length;
return len;
}
}
public byte this[int i]
{
get { return Read(i, 1)[0]; }
}
public ByteBuilder() : this(10) { }
public ByteBuilder(int packsize)
{
this.packsize = packsize; used = 0;
data = new byte[packsize][];
}
public ByteBuilder(byte[] data)
{
packsize = 1;
used = 1;
this.data = new byte[][] { data };
}
public ByteBuilder(byte[] data, int len) : this(data, 0, len) { }
public ByteBuilder(byte[] data, int from, int len)
: this(1)
{
Add(data, from, len);
}
public void Add(byte[] moredata) { Add(moredata, 0, moredata.Length); }
public void Add(byte[] moredata, int from, int len)
{
//Console.WriteLine("Getting "+from+" to "+(from+len-1)+" of "+moredata.Length);
if (used < packsize)
{
data[used] = new byte[len];
for (int j = from; j < from + len; j++)
data[used][j - from] = moredata[j];
used++;
}
else
{
// Compress the existing items into the first array
byte[] newdata = new byte[Length + len];
int np = 0;
for (int i = 0; i < used; i++)
for (int j = 0; j < data[i].Length; j++)
newdata[np++] = data[i][j];
for (int j = from; j < from + len; j++)
newdata[np++] = moredata[j];
data[0] = newdata;
for (int i = 1; i < used; i++) data[i] = null;
used = 1;
}
}
public byte[] Read(int from, int len)
{
if (len == 0) return new byte[0];
byte[] res = new byte[len];
int done = 0, start = 0;
for (int i = 0; i < used; i++)
{
if ((start + data[i].Length) <= from)
{
start += data[i].Length; continue;
}
// Now we're in the data block
for (int j = 0; j < data[i].Length; j++)
{
if ((j + start) < from) continue;
res[done++] = data[i][j];
if (done == len) return res;
}
}
throw new ArgumentException("Datapoints " + from + " and " + (from + len) + " must be less than " + Length);
}
public void Clear()
{
used = 0;
for (int i = 0; i < used; i++) data[i] = null;
}
public Parameter GetParameter(ref int index)
{
Parameter res = new Parameter();
res.Type = Read(index++, 1)[0];
byte[] lenba = Read(index, 4);
index += 4;
int len = ClientInfo.GetInt(lenba, 0, 4);
res.content = Read(index, len);
index += len;
return res;
}
public void AddParameter(Parameter param) { AddParameter(param.content, param.Type); }
public void AddParameter(byte[] content, byte Type)
{
Add(new byte[] { Type });
Add(ClientInfo.IntToBytes(content.Length));
Add(content);
}
public static String FormatParameter(Parameter p)
{
switch (p.Type)
{
case ParameterType.Int:
int[] ia = ClientInfo.GetIntArray(p.content);
StringBuilder sb = new StringBuilder();
foreach (int i in ia) sb.Append(i + " ");
return sb.ToString();
case ParameterType.Uint:
ia = ClientInfo.GetIntArray(p.content);
sb = new StringBuilder();
foreach (int i in ia) sb.Append(i.ToString("X8") + " ");
return sb.ToString();
case ParameterType.String:
return Encoding.UTF8.GetString(p.content);
case ParameterType.StringArray:
string[] sa = ClientInfo.GetStringArray(p.content, Encoding.UTF8);
sb = new StringBuilder();
foreach (string s in sa) sb.Append(s + "; ");
return sb.ToString();
case ParameterType.Byte:
sb = new StringBuilder();
foreach (int b in p.content) sb.Append(b.ToString("X2") + " ");
return sb.ToString();
default: return "??";
}
}
}
[Serializable]
public struct Parameter
{
public byte Type;
public byte[] content;
public Parameter(byte[] content, byte type)
{
this.content = content; Type = type;
}
}
public struct ParameterType
{
public const byte Unparameterised = 0;
public const byte Int = 1;
public const byte Uint = 2;
public const byte String = 3;
public const byte Byte = 4;
public const byte StringArray = 5;
}
}
namespace Netsalon.Cryptography
{
// Cryptographic classes
public abstract class BaseCrypto : ICryptoTransform
{
public int InputBlockSize { get { return 1; } }
public int OutputBlockSize { get { return 1; } }
public bool CanTransformMultipleBlocks { get { return true; } }
public bool CanReuseTransform { get { return true; } }
protected byte[] key;
protected byte currentKey;
protected int done = 0, keyinx = 0;
protected BaseCrypto(byte[] key)
{
if (key.Length == 0) throw new ArgumentException("Must provide a key");
this.key = key;
currentKey = 0;
for (int i = 0; i < key.Length; i++) currentKey += key[i];
}
protected abstract byte DoByte(byte b);
public int TransformBlock(byte[] from, int frominx, int len, byte[] to, int toinx)
{
for (int i = 0; i < len; i++)
{
byte oldkey = currentKey;
to[toinx + i] = DoByte(from[frominx + i]);
#if DEBUG
// Console.WriteLine(" encrypting "+from[frominx + i]+" to "+to[toinx + i]+", key is "+oldkey);
#endif
BumpKey();
}
return len;
}
public byte[] TransformFinalBlock(byte[] from, int frominx, int len)
{
byte[] to = new byte[len];
TransformBlock(from, frominx, len, to, 0);
return to;
}
protected void BumpKey()
{
keyinx = (keyinx + 1) % key.Length;
currentKey = Multiply257(key[keyinx], currentKey);
}
protected static byte Multiply257(byte a, byte b)
{
return (byte)((((a + 1) * (b + 1)) % 257) - 1);
}
protected static byte[] complements = { 0, 128, 85, 192, 102, 42, 146, 224, 199, 179, 186, 149, 177, 201, 119, 240, 120, 99, 229, 89, 48, 221, 189, 74, 71, 88, 237, 100, 194, 59, 198, 248, 147, 188, 234, 49, 131, 114, 144, 44, 162, 152, 5, 110, 39, 94, 174, 165, 20, 35, 125, 172, 96, 118, 242, 178, 247, 225, 60, 29, 58, 227, 101, 252, 86, 73, 233, 222, 148, 245, 180, 24, 168, 65, 23, 185, 246, 200, 243, 150, 164, 209, 95, 204, 126, 2, 64, 183, 25, 19, 208, 175, 151, 215, 45, 82, 52, 138, 134, 17, 27, 62, 4, 214, 163, 176, 244, 187, 223, 249, 43, 217, 115, 123, 37, 112, 133, 158, 53, 14, 16, 157, 139, 113, 219, 50, 84, 254, 1, 171, 205, 36, 142, 116, 98, 239, 241, 202, 97, 122, 143, 218, 132, 140, 38, 212, 6, 32, 68, 11, 79, 92, 41, 251, 193, 228, 238, 121, 117, 203, 173, 210, 40, 104, 80, 47, 236, 230, 72, 191, 253, 129, 51, 160, 46, 91, 105, 12, 55, 9, 70, 232, 190, 87, 231, 75, 10, 107, 33, 22, 182, 169, 3, 154, 28, 197, 226, 195, 30, 8, 77, 13, 137, 159, 83, 130, 220, 235, 90, 81, 161, 216, 145, 250, 103, 93, 211, 111, 141, 124, 206, 21, 67, 108, 7, 57, 196, 61, 155, 18, 167, 184, 181, 66, 34, 207, 166, 26, 156, 135, 15, 136, 54, 78, 106, 69, 76, 56, 31, 109, 213, 153, 63, 170, 127, 255 };
protected static byte Complement257(byte b) { return complements[b]; }
public void Dispose() { } // for IDisposable
}
public class SimpleEncryptor : BaseCrypto
{
public SimpleEncryptor(byte[] key) : base(key) { }
protected override byte DoByte(byte b)
{
byte b2 = Multiply257((byte)(b + currentKey), currentKey);
currentKey = Multiply257((byte)(b + b2), currentKey);
return b2;
}
}
public class SimpleDecryptor : BaseCrypto
{
public SimpleDecryptor(byte[] key) : base(key) { }
protected override byte DoByte(byte b)
{
byte b2 = (byte)(Multiply257(b, Complement257(currentKey)) - currentKey);
currentKey = Multiply257((byte)(b + b2), currentKey);
return b2;
}
}
}
As soon as I can chat with my boss, I will ask about sending more code.
|
|
|
|
|
One more thing. I don't know if ArrayList.Remove is protected as you said, but since the code snippet I mentioned iterates over the list, you would still be liable to get the exception I mentioned, since the list is being changed out from under one thread by another.
Mitch
|
|
|
|
|
found here: http://www.informit.com/guides/content.aspx?g=dotnet&seqNum=143[^]
Collections and Thread Safety
Last updated Jan 1, 2004.
The default behavior of all the collection classes is not generally thread safe. Although any number of threads can read a collection simultaneously, a single thread modifying the collection can cause undefined behavior for any other threads that are accessing it. The Hashtable class guarantees thread safety for a single writer and multiple readers, but if multiple writers are required, the same rules apply as for all the other collection types.
The .NET Framework SDK documentation identifies three primary ways to make collections thread safe:
*
Create a thread-safe wrapper using the Synchronized method, and access the collection exclusively through that wrapper.
*
If the class does not have a Synchronized method, derive from the class and implement a Synchronized method using the SyncRoot property.
*
Use a locking mechanism, such as the lock statement in C# (SyncLock in Visual Basic), on the SyncRoot property when accessing the collection.
lock (SyncLock in Visual Basic) is the easiest and most effective way to guarantee thread safety. Lock is most useful when enumerating a collection. Enumerating through a collection is inherently not thread-safe, and will throw an exception if the collection is modified at any point during the enumeration. To prevent any other thread from modifying the collection, you lock the collection first, like this:
|
|
|
|
|
Looks like I need to provide a lock object and lock around things that modify Clients then.
|
|
|
|
|
The rule is for *any* data that could be touched by another thread, which includes things the end user can do during any of the callbacks.
Since you know the code better than anyone else, you would know what, if anything, else besides the Client array might need to be protected.
If you do make any changes, I would be happy to try them with my server and tester programs.
Mitch
|
|
|
|
|
Well I can't lock the callbacks, because that forces the entire flow of execution to be serial – so if you had a long request it would block every other user. Most callback actions don't need to be synchronised (certainly not at the global level, anyway). If the user wants to do something that does need to be (such as sending a message to some but not all clients) then I should provide an object that you can put in the lock statement.
I think in your case (sending a message to all other clients when someone disconnects), you can use Server.BroadcastMessage. But that will probably fail in exactly the same way as your code at the minute .
I will try to put some threading safety nets in over the weekend.
|
|
|
|
|
Ok, this article is not unedited any more so I can't make instant changes. I have made a few changes to the library in line with your suggestions:
- The server exposes a SyncRoot property which you can use in lock expressions if you need to
- Server operations that affect or enumerate the client list are synchronised with a lock(SyncRoot)
- The server stores clients in a hashtable (should be transparent to you)
- The client provides a Close method with a string argument to specify a reason
- The reason and exception (if applicable) for client closure are stored in the CloseReason and CloseException properties
You can find this version at http://www.redcorona.com/Sockets.cs. Hopefully it will solve most of your problems, although you will still have to lock(server.SyncRoot) where you enumerate the client list. If it works better I will ask CP to update the version here.
|
|
|
|
|
Those changes sound perfect. I will try and modify my code to use the new version, and I will let you know how it behaves. I can tell you that just last night I ran my automated test with 75 clients simultaneously sending data, and each ran over 1000 iterations of their data "script". I had previously modified sockets.cs to lock the clients list, and it seemed to help. I would prefer to use your version, as you know the code better than I do!
Mitch
|
|
|
|
|
I just took your new version and started using it. Right away, I found
another problem, one which I had previously tried to fix in Sockets.cs. The
problem is as follows. I have an application which creates a configurable
number of clients to a server. (Remember it is a simple IM application).
When one of the clients disconnects for any reason, the server sends a message
to all of the others telling them that a user went away.
Well, because my tester is automated, essentially what happens is that all
of the clients exit at the same time. I know that this is not like what
would happen in the real world, but it is the "worst-case" test scenario,
and the code should be robust to it.
WHat happens when I run this is that I consistently get a socket exception
when I am trying to send, as all of the connections (remember) are basically
coming down at once. I had previously seen this, and simply enclosed most
of the raw socket operations (like 'send') with a try block and caught the
SocketExceptions and logged them. I did this because I assumed (and still
think I am correct) that this is a "normal" error, namely, that the low-level
socket library is telling you that the connection went away out from under
you before you had the time to consume the close event. If this were a
non-object-oriented language (like Unix C), it would have returned a
-1 and set errno appropriately.
The only issue I have (if you agree with what I am proposing) is whether
you should (in your catch block) go ahead and set the CloseReason to be
the error string from the SocketException ("An existing connection was forcibly closed by the remote host", errno 10054). I think you probably should.
I may try this today, but would like to get your feedback as well.
I should note that this also happened with just two manual clients, so it is
definitely a time-bomb that could happen any time.
Mitch
|
|
|
|
|
Yes, I think you're right.
public void Send(byte[] bytes, int len){
if(!encComplete) throw new IOException("Key exchange is not yet completed");
if(encType != EncryptionType.None){
byte[] outbytes = new byte[len];
Encryptor.TransformBlock(bytes, 0, len, outbytes, 0);
bytes = outbytes;
}
#if DEBUG
Console.Write(ID + " Sent: "); LogBytes(bytes, len);
#endif
try {
sock.Send(bytes, len, SocketFlags.None);
} catch(Exception e){
closeException = e;
closeReason = "Exception in send: "+e.Message;
Close();
}
}
I think I left it because you might want to catch the exception yourself. But it causes problems with broadcasts and it would be better handled by having the user catch OnClose and looking at the CloseException, I think.
Edit: Source on redcorona.com updated with this change.
modified on Sunday, March 29, 2009 8:40 PM
|
|
|
|
|
Bob,
I am now trying to set up another simple client and server app
which will use server key encryption and messageType CodeAndLength.
I set up the client to wait for OnReady to fire before attempting
to send the first data, but when I did, I got the "Key exchange
is not complete" exception.
Looking at the code, I see that in the method MakeEncoders,
it seems like that since you are setting encComplete to true
only AFTER the call to OnReady, this is guaranteed to happen.
The doc says "OnReady is used with encrypted sockets to notify
your application that key exchange is complete and you may
send data".
Please enlighten me if I am missing something (and apologies
if so).
Mitch
|
|
|
|
|