context: /*
   * The Spread Toolkit.
   *     
   * The contents of this file are subject to the Spread Open-Source
   * License, Version 1.0 (the ``License''); you may not use
   * this file except in compliance with the License.  You may obtain a
   * copy of the License at:
   *
   * http://www.spread.org/license/
   *
   * or in the file ``license.txt'' found in this distribution.
   *
   * Software distributed under the License is distributed on an AS IS basis, 
   * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
   * for the specific language governing rights and limitations under the 
   * License.
   *
   * The Creators of Spread are:
   *  Yair Amir, Michal Miskin-Amir, Jonathan Stanton.
   *
   *  Copyright (C) 1993-2001 Spread Concepts LLC <spread@spreadconcepts.com>
   *
   *  All Rights Reserved.
   *
   * Major Contributor(s):
   * ---------------
   *    Dan Schoenblum   dansch@cnds.jhu.edu - Java Interface Developer.
   *    John Schultz     jschultz@cnds.jhu.edu - contribution to process group membership.
   *    Theo Schlossnagle theos@cnds.jhu.edu - Perl library and Skiplists.
   *
   */
  
  using System;
  using System.Net;
  using System.Net.Sockets;
  using System.Collections;
  using System.Threading;
  
  namespace Timok.SpreadLib 
  {
  	public class SpreadConnection {
  		internal const int DEFAULT_SPREAD_PORT = 4803;
  		internal const int MAX_PRIVATE_NAME = 10;
  		internal const int MAX_MESSAGE_LENGTH = 140000;
  		internal const int MAX_GROUP_NAME = 32;
  		private const byte SP_MAJOR_VERSION = 3;
  		private const byte SP_MINOR_VERSION = 16;
  		private const byte SP_PATCH_VERSION = 0;
  		private const int ACCEPT_SESSION = 1;
  		internal const int MAX_AUTH_NAME = 30;
  		internal const int MAX_AUTH_METHODS = 3;
  		private const string DEFAULT_AUTH_NAME = "NULL";
  		private const int ENDIAN_TYPE = unchecked((int)0x80000080);
  		
  		private bool connected = false;
  		private string address;
  		private int port;
  		private bool priority;
  		private bool groupMembership;
  		private TcpClient socket;
  		private NetworkStream stream;
  		private string authName = DEFAULT_AUTH_NAME;
  		private SpreadGroup group;
  		private byte[] buffer;
  		private int receivedLength = 0;
  		private int receiveState = INIT_RECEIVE;
  		private int numGroups;
  		bool daemonEndianMismatch;
  		private const int INIT_RECEIVE = 0;
  		private const int HEADER_RECEIVE = 1;
  		private const int OLD_TYPE_RECEIVE = 2;
  		private const int GROUPS_RECEIVE = 3;
  		private const int DATA_RECEIVE = 4;
  		private object rsynchro = new object();
  		private SpreadMessage receivedMessage;
  		private bool messageWaiting = false;
  
  		public delegate void MessageHandler(SpreadMessage msg);
  		public event MessageHandler OnRegularMessage;
  		public event MessageHandler OnMembershipMessage;
  		
  		private void AsyncReceive(IAsyncResult ar) {
  			bool readAsync = false;
  			do {
  				if (receiveState == INIT_RECEIVE) {
  					buffer = new byte[48];
  					receivedLength = 0;
  					receiveState = HEADER_RECEIVE;
  					receivedMessage = new SpreadMessage();
  				} else {
  					if (receivedLength != buffer.Length) {
  						int size = stream.EndRead(ar);
  						if (size == 0)
  							throw new SpreadException("Connection closed while reading");
  						receivedLength += size;
  					}
  					if (receivedLength == buffer.Length) 
  						switch (receiveState) {
  							case HEADER_RECEIVE:
  								byte[] header = buffer;
  
  								int headerIndex = 0;
  								// Get service type.
  								receivedMessage.ServiceType = toInt(header, headerIndex);
  								headerIndex += 4;
  
  								// Get the sender.
  								receivedMessage.Sender = toGroup(header, headerIndex);
  								headerIndex += MAX_GROUP_NAME;
  
  								// Get the number of groups.
  								numGroups = toInt(header, headerIndex);
  								headerIndex += 4;
  
  								// Get the hint/type.
  								int hint = toInt(header, headerIndex);
  								headerIndex += 4;
  
  								// Get the data length.
  								int dataLen = toInt(header, headerIndex);
  								headerIndex += 4;
  
  								// Does the header need to be flipped?
  								// (Checking for a daemon server endian-mismatch)
  								if(sameEndian(receivedMessage.ServiceType) == false) {
  									// Flip them.
  									receivedMessage.ServiceType = flip(receivedMessage.ServiceType);
  									numGroups = flip(numGroups);
  									dataLen = flip(dataLen);
  		
  									// The daemon endian-mismatch.
  									daemonEndianMismatch = true;
  								}
  								else {
  									// The daemon endian-mismatch.
  									daemonEndianMismatch = false;
  								}
  
  								receivedMessage.Data = new byte[dataLen];
  		
  								// Is this a regular message?
  								if( receivedMessage.IsRegular || receivedMessage.IsReject ) {
  									// Does the hint need to be flipped?
  									// (Checking for a sender endian-mismatch)
  									if(sameEndian(hint) == false) {
  										hint = flip(hint);
  										receivedMessage.EndianMismatch = true;
  									}
  									else {
  										receivedMessage.EndianMismatch = false;
  									}
  			
  									// Get the type from the hint.
  									hint = clearEndian(hint);
  									hint >>= 8;
  									hint &= 0x0000FFFF;
  									receivedMessage.Type = (short)hint;
  								}
  								else {
  									// This is not a regular message.
  									receivedMessage.Type = -1;
  									receivedMessage.EndianMismatch = false;
  								}
  								if( receivedMessage.IsReject ) {
  									buffer = new byte[4];
  									receivedLength = 0;
  									receiveState = OLD_TYPE_RECEIVE;
  								} else {
  									buffer = new byte[numGroups * MAX_GROUP_NAME];
  									receivedLength = 0;
  									receiveState = GROUPS_RECEIVE;
  								}
  								break;
  							case OLD_TYPE_RECEIVE:
  								int oldType = toInt(buffer, 0);
  								if ( sameEndian(receivedMessage.ServiceType) == false )
  									oldType = flip(oldType);
  								receivedMessage.ServiceType = (SpreadMessage.REJECT_MESS | oldType);
  								buffer = new byte[numGroups * MAX_GROUP_NAME];
  								receivedLength = 0;
  								receiveState = GROUPS_RECEIVE;
  								break;
  							case GROUPS_RECEIVE:
  								// Clear the endian type.
  								receivedMessage.ServiceType = clearEndian(receivedMessage.ServiceType);
  
  								// Get the groups from the buffer.
  								for(int bufferIndex = 0 ; bufferIndex < buffer.Length ; bufferIndex += MAX_GROUP_NAME) {
  									// Translate the name into a group and add it to the vector.
  									receivedMessage.AddGroup(toGroup(buffer, bufferIndex));
  								}
  								buffer = receivedMessage.Data;
  								receivedLength = 0;
  								receiveState = DATA_RECEIVE;
  								break;
  							case DATA_RECEIVE:
  								// Is it a membership message?
  								if(receivedMessage.IsMembership) {
  									// Create a membership info object.
  									receivedMessage.membershipInfo = new MembershipInfo(this, receivedMessage, daemonEndianMismatch);
  			
  									// Is it a regular membership message?
  									if(receivedMessage.membershipInfo.IsRegularMembership) {
  										// Find which of these groups is the local connection.
  										receivedMessage.Type = (short)receivedMessage.groups.IndexOf(PrivateGroup);
  									}
  								}
  								else {
  									// There's no membership info.
  									receivedMessage.membershipInfo = null;
  								}
  								if (OnRegularMessage !=null  && receivedMessage.IsRegular) {
  									OnRegularMessage(receivedMessage);
  								}
  								else if (OnMembershipMessage != null && receivedMessage.IsMembership) {
  									OnMembershipMessage(receivedMessage);
  								}
  								else {
  									lock (rsynchro) {
  										messageWaiting = true;
  										Monitor.Pulse(rsynchro);
  										Monitor.Wait(rsynchro);
  									}
  								}
  								buffer = new byte[48];
  								receivedLength = 0;
  								receiveState = HEADER_RECEIVE;
  								receivedMessage = new SpreadMessage();
  								break;
  						}
  				}
  				try {
  					// read sync if data available
  					while(stream.DataAvailable && receivedLength < buffer.Length) {
  						int size = stream.Read(buffer,receivedLength,buffer.Length-receivedLength);
  						if (size == 0)
  							throw new SpreadException("Connection closed while reading");
  						receivedLength += size;
  					} 
  					if (receivedLength != buffer.Length) {
  						// no more data - continue async read
  						stream.BeginRead(buffer,receivedLength,buffer.Length-receivedLength,new AsyncCallback(AsyncReceive),null);
  						readAsync = true;
  					}
  				} 
  				catch (Exception e) {
  					throw new SpreadException("BeginRead(): " + e);
  				}
  			} while (!readAsync);
  		}
  
  		private void StartReceiving() {
  		}
  		// Clears the int's endian type.
  		private static int clearEndian(int i) {
  			return (i & ~ENDIAN_TYPE);
  		}
  		// Endian-flips the int.
  		internal static int flip(int i) {
  			return (int)(((i >> 24) & 0x000000ff) | (int)((i >>  8) & 0x0000ff00) | (int)((i <<  8) & 0x00ff0000) | (int)((i << 24) & 0xff000000));
  		}
  		// Checks if the int is the same-endian type as the local machine.
  		private static bool sameEndian(int i) {
  			return ((i & ENDIAN_TYPE) == 0);
  		}
  		// Gets a string from an array of bytes.
  		internal SpreadGroup toGroup(byte[] buffer, int bufferIndex) {
  			try {
  				for(int end = bufferIndex ; end < buffer.Length ; end++) {
  					if(buffer[end] == 0) {
  						// Get the group name.
  						String name = System.Text.Encoding.ASCII.GetString(buffer, bufferIndex, end - bufferIndex);
  					
  						// Return the group.
  						return new SpreadGroup(this, name);
  					}
  				}
  			}
  			catch(Exception e) {
  				// Already checked for this exception in connect.
  				Console.WriteLine(e);
  			}
  	
  			return null;
  		}
  		// Gets an int from an array of bytes.
  		internal static int toInt(byte[] buffer, int bufferIndex) {
  			int i0 = (buffer[bufferIndex++] & 0xFF);
  			int i1 = (buffer[bufferIndex++] & 0xFF);
  			int i2 = (buffer[bufferIndex++] & 0xFF);
  			int i3 = (buffer[bufferIndex++] & 0xFF);
  		
  			return ((i0 << 24) | (i1 << 16) | (i2 << 8) | (i3));
  		}	
  		// Puts an int into an array of bytes.
  		private static void toBytes(int i, byte[] buffer, int bufferIndex) {
  			buffer[bufferIndex++] = (byte)((i >> 24) & 0xFF);
  			buffer[bufferIndex++] = (byte)((i >> 16) & 0xFF);
  			buffer[bufferIndex++] = (byte)((i >> 8 ) & 0xFF);
  			buffer[bufferIndex++] = (byte)((i      ) & 0xFF);
  		}
  		// Puts a group name into an array of bytes.
  		private static void toBytes(SpreadGroup group, byte[] buffer, int bufferIndex) {
  			// Get the group's name.
  			byte[] name;
  			try {
  				name = System.Text.Encoding.ASCII.GetBytes(group.ToString());
  			}
  			catch(Exception e) {
  				// Already checked for this exception in connect.
  				name = new byte[0];
  				Console.WriteLine(e);
  			}
  		
  			// Put a cap on the length.
  			int len = name.Length;
  			if(len > MAX_GROUP_NAME)
  				len = MAX_GROUP_NAME;
  
  			// Copy the name into the buffer.
  			System.Array.Copy(name,0,buffer,bufferIndex,len);
  			for( ; len < MAX_GROUP_NAME ; len++ )
  				buffer[bufferIndex + len] = 0;
  		}
  		// Get the private group name.
  		private void ReadGroup() {
  			// Read the length.
  			int len;
  			try {
  				len = stream.ReadByte();
  			}
  			catch(Exception e) {
  				throw new SpreadException("read(): " + e);
  			}
  		
  			// Check for no more data.
  			if(len == -1) {
  				throw new SpreadException("Connection closed during connect attempt");
  			}
  		
  			// Read the name.
  			byte[] buffer = new byte[len];
  			int numRead;
  			try {
  				numRead = stream.Read(buffer,0,buffer.Length);
  			}
  			catch(Exception e) {
  
gt: 				throw new SpreadException("read(): " + e);