/******************************************************************************
* The MIT License
* Copyright (c) 2003 Novell Inc.  www.novell.com
* 
* Permission is hereby granted, free of charge, to any person obtaining  a copy
* of this software and associated documentation files (the Software), to deal
* in the Software without restriction, including  without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
* copies of the Software, and to  permit persons to whom the Software is 
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in 
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*******************************************************************************/
//
// Novell.Directory.Ldap.Connection.cs
//
// Author:
//   Sunil Kumar (Sunilk@novell.com)
//
// (C) 2003 Novell, Inc (http://www.novell.com)
//

using System;
using System.Threading;
using Novell.Directory.Ldap.Asn1;
using Novell.Directory.Ldap.Rfc2251;
using Novell.Directory.Ldap.Utilclass;

namespace Novell.Directory.Ldap
{
	
	/// <summary> The class that creates a connection to the Ldap server. After the
	/// connection is made, a thread is created that reads data from the
	/// connection.
	/// 
	/// The application's thread sends a request to the MessageAgent class, which
	/// creates a Message class.  The Message class calls the writeMessage method
	/// of this class to send the request to the server. The application thread
	/// will then query the MessageAgent class for a response.
	/// 
	/// The reader thread multiplexes response messages received from the
	/// server to the appropriate Message class. Each Message class
	/// has its own message queue.
	/// 
	/// Unsolicited messages are process separately, and if the application
	/// has registered a handler, a separate thread is created for that
	/// application's handler to process the message.
	/// 
	/// Note: the reader thread must not be a "selfish" thread, since some
	/// operating systems do not time slice.
	/// 
	/// </summary>
	/*package*/
	sealed class Connection
	{
		private void  InitBlock()
		{
			writeSemaphore = new System.Object();
			encoder = new LBEREncoder();
			decoder = new LBERDecoder();
			stopReaderMessageID = CONTINUE_READING;
			messages = new MessageVector(5, 5);
			unsolicitedListeners = new System.Collections.ArrayList(3);
		}
		/// <summary>  Indicates whether clones exist for LdapConnection
		/// 
		/// </summary>
		/// <returns> true if clones exist, false otherwise.
		/// </returns>
		internal bool Cloned
		{
			/* package */
			
			get
			{
				return (cloneCount > 0);
			}
			
		}
		/// <summary> gets the host used for this connection</summary>
		internal System.String Host
		{
			/* package */
			
			get
			{
				return host;
			}
			
		}
		/// <summary> gets the port used for this connection</summary>
		internal int Port
		{
			/* package */
			
			get
			{
				return port;
			}
			
		}
		/// <summary> gets the writeSemaphore id used for active bind operation</summary>
		/// <summary> sets the writeSemaphore id used for active bind operation</summary>
		internal int BindSemId
		{
			/* package */
			
			get
			{
				return bindSemaphoreId;
			}
			
			/* package */
			
			set
			{
				bindSemaphoreId = value;
				return ;
			}
			
		}
		/// <summary> checks if the writeSemaphore id used for active bind operation is clear</summary>
		internal bool BindSemIdClear
		{
			/* package */
			
			get
			{
				if (bindSemaphoreId == 0)
				{
					return true;
				}
				return false;
			}
			
		}
		/// <summary> Return whether the application is bound to this connection.
		/// Note: an anonymous bind returns false - not bound
		/// </summary>
		internal bool Bound
		{
			/* package */
			
			get
			{
				if (bindProperties != null)
				{
					// Bound if not anonymous
					return (!bindProperties.Anonymous);
				}
				return false;
			}
			
		}
		/// <summary> Return whether a connection has been made</summary>
		internal bool Connected
		{
			/* package */
			
			get
			{
				return (in_Renamed != null);
			}
			
		}
		/// <summary> 
		/// Sets the authentication credentials in the object
		/// and set flag indicating successful bind.
		/// 
		/// 
		/// 
		/// </summary>
		/// <returns>  The BindProperties object for this connection.
		/// </returns>
		/// <summary> 
		/// Sets the authentication credentials in the object
		/// and set flag indicating successful bind.
		/// 
		/// 
		/// 
		/// </summary>
		/// <param name="bindProps">  The BindProperties object to set.
		/// </param>
		internal BindProperties BindProperties
		{
			/* package */
			
			get
			{
				return bindProperties;
			}
			
			/* package */
			
			set
			{
				bindProperties = value;
				return ;
			}
			
		}
		/// <summary> Gets the current referral active on this connection if created to
		/// follow referrals.
		/// 
		/// </summary>
		/// <returns> the active referral url
		/// </returns>
		/// <summary> Sets the current referral active on this connection if created to
		/// follow referrals.
		/// </summary>
		internal ReferralInfo ActiveReferral
		{

			
			get
			{
				return activeReferral;
			}
			

			
			set
			{
				activeReferral = value;
				return ;
			}
			
		}
		
		/// <summary> Returns the name of this Connection, used for debug only
		/// 
		/// </summary>
		/// <returns> the name of this connection
		/// </returns>
		internal System.String ConnectionName
		{
			/*package*/
			
			get
			{
				return name;
			}
			
		}
		
		private System.Object writeSemaphore;
		private int writeSemaphoreOwner = 0;
		private int writeSemaphoreCount = 0;
		
		// We need a message number for disconnect to grab the semaphore,
		// but may not have one, so we invent a unique one.
		private int ephemeralId = - 1;
		private BindProperties bindProperties = null;
		private int bindSemaphoreId = 0; // 0 is never used by to lock a semaphore
		
		private SupportClass.ThreadClass reader = null; // New thread that reads data from the server.
		private SupportClass.ThreadClass deadReader = null; // Identity of last reader thread
		private System.IO.IOException deadReaderException = null; // Last exception of reader
		
		private LBEREncoder encoder;
		private LBERDecoder decoder;
		
		/*
		* socket is the current socket being used.
		* nonTLSBackup is the backup socket if startTLS is called.
		* if nonTLSBackup is null then startTLS has not been called,
		* or stopTLS has been called to end TLS protection
		*/
		private System.Net.Sockets.TcpClient socket = null;
		private System.Net.Sockets.TcpClient nonTLSBackup = null;
		
		private System.IO.Stream in_Renamed = null;
		private System.IO.Stream out_Renamed = null;
		// When set to true the client connection is up and running
		private bool clientActive = true;
		
		// Indicates we have received a server shutdown unsolicited notification
		private bool unsolSvrShutDnNotification = false;
		
		//  Ldap message IDs are all positive numbers so we can use negative
		//  numbers as flags.  This are flags assigned to stopReaderMessageID
		//  to tell the reader what state we are in.
		private const int CONTINUE_READING = - 99;
		private const int STOP_READING = - 98;
		
		//  Stops the reader thread when a Message with the passed-in ID is read.
		//  This parameter is set by stopReaderOnReply and stopTLS
		private int stopReaderMessageID;
		
		
		// Place to save message information classes
		private MessageVector messages;
		
		// Connection created to follow referral
		private ReferralInfo activeReferral = null;
		
		// Place to save unsolicited message listeners
		private System.Collections.ArrayList unsolicitedListeners;
		
		// The LdapSocketFactory to be used as the default to create new connections
//		private static LdapSocketFactory socketFactory = null;
		// The LdapSocketFactory used for this connection
//		private LdapSocketFactory mySocketFactory;
		private System.String host = null;
		private int port = 0;
		// Number of clones in addition to original LdapConnection using this
		// connection.
		private int cloneCount = 0;
		// Connection number & name used only for debug
		private System.String name = "";
		private static System.Object nameLock; // protect connNum
		private static int connNum = 0;
		
		// These attributes can be retreived using the getProperty
		// method in LdapConnection.  Future releases might require
		// these to be local variables that can be modified using
		// the setProperty method.
		/* package */
		internal static System.String sdk;
		/* package */
		internal static System.Int32 protocol;
		/* package */
		internal static System.String security = "simple";
		
		/// <summary> Create a new Connection object
		/// 
		/// </summary>
		/// <param name="factory">specifies the factory to use to produce SSL sockets.
		/// </param>
		/* package */
//		internal Connection(LdapSocketFactory factory)
		internal Connection()
		{
			InitBlock();
			return ;
		}
		
		/// <summary> Copy this Connection object.
		/// 
		/// This is not a true clone, but creates a new object encapsulating
		/// part of the connection information from the original object.
		/// The new object will have the same default socket factory,
		/// designated socket factory, host, port, and protocol version
		/// as the original object.
		/// The new object is NOT be connected to the host.
		/// 
		/// </summary>
		/// <returns> a shallow copy of this object
		/// </returns>
		/* package */
		internal System.Object copy()
		{
			Connection c = new Connection();
			c.host = this.host;
			c.port = this.port;
			Novell.Directory.Ldap.Connection.protocol = Connection.protocol;
			return c;
		}
		
		/// <summary> Acquire a simple counting semaphore that synchronizes state affecting
		/// bind. This method generates an ephemeral message id (negative number).
		/// 
		/// We bind using the message ID because a different thread may unlock
		/// the semaphore than the one that set it.  It is cleared when the
		/// response to the bind is processed, or when the bind operation times out.
		/// 
		/// Returns when the semaphore is acquired
		/// 
		/// </summary>
		/// <returns> the ephemeral message id that identifies semaphore's owner
		/// </returns>
		/* package */
		internal int acquireWriteSemaphore()
		{
			return acquireWriteSemaphore(0);
		}
		
		/// <summary> Acquire a simple counting semaphore that synchronizes state affecting
		/// bind. The semaphore is held by setting a value in writeSemaphoreOwner.
		/// 
		/// We bind using the message ID because a different thread may unlock
		/// the semaphore than the one that set it.  It is cleared when the
		/// response to the bind is processed, or when the bind operation times out.
		/// Returns when the semaphore is acquired.
		/// 
		/// </summary>
		/// <param name="msgId">a value that identifies the owner of this semaphore. A
		/// value of zero means assign a unique semaphore value.
		/// 
		/// </param>
		/// <returns> the semaphore value used to acquire the lock
		/// </returns>
		/* package */
		internal int acquireWriteSemaphore(int msgId)
		{
			int id = msgId;
			lock (writeSemaphore)
			{
				if (id == 0)
				{
					ephemeralId = ((ephemeralId == System.Int32.MinValue)?(ephemeralId = - 1):--ephemeralId);
					id = ephemeralId;
				}
				while (true)
				{
					if (writeSemaphoreOwner == 0)
					{
						// we have acquired the semahpore
						writeSemaphoreOwner = id;
						break;
					}
					else
					{
						if (writeSemaphoreOwner == id)
						{
							// we already own the semahpore
							break;
						}
						try
						{
							// Keep trying for the lock
							System.Threading.Monitor.Wait(writeSemaphore);
							continue;
						}
						catch (System.Threading.ThreadInterruptedException ex)
						{
							// Keep trying for the lock
							continue;
						}
					}
				}
				writeSemaphoreCount++;
			}
			return id;
		}
		
		/// <summary> Release a simple counting semaphore that synchronizes state affecting
		/// bind.  Frees the semaphore when number of acquires and frees for this
		/// thread match.
		/// 
		/// </summary>
		/// <param name="msgId">a value that identifies the owner of this semaphore
		/// </param>
		/* package */
		internal void  freeWriteSemaphore(int msgId)
		{
			lock (writeSemaphore)
			{
				if (writeSemaphoreOwner == 0)
				{
					throw new System.SystemException("Connection.freeWriteSemaphore(" + msgId + "): semaphore not owned by any thread");
				}
				else if (writeSemaphoreOwner != msgId)
				{
					throw new System.SystemException("Connection.freeWriteSemaphore(" + msgId + "): thread does not own the semaphore, owned by " + writeSemaphoreOwner);
				}
				// if all instances of this semaphore for this thread are released,
				// wake up all threads waiting.
				if (--writeSemaphoreCount == 0)
				{
					writeSemaphoreOwner = 0;
					System.Threading.Monitor.Pulse(writeSemaphore);
				}
			}
			return ;
		}
		
		/*
		* Wait until the reader thread ID matches the specified parameter.
		* Null = wait for the reader to terminate
		* Non Null = wait for the reader to start
		* Returns when the ID matches, i.e. reader stopped, or reader started.
		*
		* @param the thread id to match
		*/
		private void  waitForReader(SupportClass.ThreadClass thread)
		{
			// wait for previous reader thread to terminate
			System.Threading.Thread rInst;
			System.Threading.Thread tInst;
			if(reader!=null)
			{
				rInst=reader.Instance;
			}
			else
			{
				rInst=null;
			}

			if(thread!=null)
			{
				tInst=thread.Instance;
			}
			else
			{
				tInst=null;
			}
//			while (reader != thread)
			while (!Object.Equals(rInst,tInst))
			{
				// Don't initialize connection while previous reader thread still
				// active.
				try
				{
					/*
					* The reader thread may start and immediately terminate.
					* To prevent the waitForReader from waiting forever
					* for the dead to rise, we leave traces of the deceased.
					* If the thread is already gone, we throw an exception.
					*/
					if (thread == deadReader)
					{
						if (thread == null)
						/* then we wanted a shutdown */
							return ;
						System.IO.IOException lex = deadReaderException;
						deadReaderException = null;
						deadReader = null;
						// Reader thread terminated
						throw new LdapException(ExceptionMessages.CONNECTION_READER, LdapException.CONNECT_ERROR, null, lex);
					}
					lock (this)
					{
						System.Threading.Monitor.Wait(this, TimeSpan.FromMilliseconds(5));
					}
				}
				catch (System.Threading.ThreadInterruptedException ex)
				{
					;
				}
				if(reader!=null)
				{
					rInst=reader.Instance;
				}
				else
				{
					rInst=null;
				}

				if(thread!=null)
				{
					tInst=thread.Instance;
				}
				else
				{
					tInst=null;
				}

			}
			deadReaderException = null;
			deadReader = null;
			return ;
		}
		
		/// <summary> Constructs a TCP/IP connection to a server specified in host and port.
		/// 
		/// </summary>
		/// <param name="host">The host to connect to.
		/// 
		/// </param>
		/// <param name="port">The port on the host to connect to.
		/// </param>
		/* package */
		internal void  connect(System.String host, int port)
		{
			connect(host, port, 0);
			return ;
		}
		
		/// <summary> Constructs a TCP/IP connection to a server specified in host and port.
		/// Starts the reader thread.
		/// 
		/// </summary>
		/// <param name="host">The host to connect to.
		/// 
		/// </param>
		/// <param name="port">The port on the host to connect to.
		/// 
		/// </param>
		/// <param name="semaphoreId">The write semaphore ID to use for the connect
		/// </param>
		private void  connect(System.String host, int port, int semaphoreId)
		{
			/* Synchronized so all variables are in a consistant state and
			* so that another thread isn't doing a connect, disconnect, or clone
			* at the same time.
			*/
			// Wait for active reader to terminate
			waitForReader(null);
			
			// Clear the server shutdown notification flag.  This should already
			// be false unless of course we are reusing the same Connection object
			// after a server shutdown notification
			unsolSvrShutDnNotification = false;
			
			int semId = acquireWriteSemaphore(semaphoreId);
			
			// Make socket connection to specified host and port
			if (port == 0)
			{
				port = 389;//LdapConnection.DEFAULT_PORT;
			}
			
			try
			{
				if ((in_Renamed == null) || (out_Renamed == null))
				{
   				    socket = new System.Net.Sockets.TcpClient(host, port);
					
					in_Renamed = (System.IO.Stream) socket.GetStream();
					out_Renamed = (System.IO.Stream) socket.GetStream();
				}
				else
				{
					Console.WriteLine( "connect input/out Stream specified");

				}
			}
			catch (System.IO.IOException ioe)
			{
				// Unable to connect to server host:port
//				freeWriteSemaphore(semId);
				throw new LdapException(ExceptionMessages.CONNECTION_ERROR, new System.Object[]{host, port}, LdapException.CONNECT_ERROR, null, ioe);
			}
			// Set host and port
			this.host = host;
			this.port = port;
			// start the reader thread
			this.startReader();
			freeWriteSemaphore(semId);
			clientActive = true; // Client is up
			return ;
		}
		
		/// <summary>  Increments the count of cloned connections</summary>
		/* package */
		internal void  incrCloneCount()
		{
			lock (this)
			{
				cloneCount++;
				return ;
			}
		}
		
		/// <summary> Destroys a clone of <code>LdapConnection</code>.
		/// 
		/// This method first determines if only one <code>LdapConnection</code>
		/// object is associated with this connection, i.e. if no clone exists.
		/// 
		/// If no clone exists, the socket is closed, and the current
		/// <code>Connection</code> object is returned.
		/// 
		/// If multiple <code>LdapConnection</code> objects are associated
		/// with this connection, i.e. clones exist, a {@link #copy} of the
		/// this object is made, but is not connected to any host. This
		/// disassociates that clone from the original connection.  The new
		/// <code>Connection</code> object is returned.
		/// 
		/// Only one destroyClone instance is allowed to run at any one time.
		/// 
		/// If the connection is closed, any threads waiting for operations
		/// on that connection will wake with an LdapException indicating
		/// the connection is closed.
		/// 
		/// </summary>
		/// <param name="apiCall"><code>true</code> indicates the application is closing the
		/// connection or or creating a new one by calling either the
		/// <code>connect</code> or <code>disconnect</code> methods
		/// of <code>LdapConnection</code>.  <code>false</code>
		/// indicates that <code>LdapConnection</code> is being finalized.
		/// 
		/// </param>
		/// <returns> a Connection object or null if finalizing.
		/// </returns>
		/* package */
		internal Connection destroyClone(bool apiCall)
		{
			lock (this)
			{
				Connection conn = this;
				
				if (cloneCount > 0)
				{
					cloneCount--;
					// This is a clone, set a new connection object.
					if (apiCall)
					{
						conn = (Connection) this.copy();
					}
					else
					{
						conn = null;
					}
				}
				else
				{
					if (in_Renamed != null)
					{
						// Not a clone and connected
						/*
						* Either the application has called disconnect or connect
						* resulting in the current connection being closed. If the
						* application has any queues waiting on messages, we
						* need wake these up so the application does not hang.
						* The boolean flag indicates whether the close came
						* from an API call or from the object being finalized.
						*/
						InterThreadException notify = new InterThreadException((apiCall?ExceptionMessages.CONNECTION_CLOSED:ExceptionMessages.CONNECTION_FINALIZED), null, LdapException.CONNECT_ERROR, null, null);
						// Destroy old connection
						shutdown("destroy clone", 0, notify);
					}
				}
				return conn;
			}
		}
		
		/// <summary> sets the default socket factory
		/// 
		/// </summary>
		/// <param name="factory">the default factory to set
		/// </param>
		/* package */
		/// <summary> gets the socket factory used for this connection
		/// 
		/// </summary>
		/// <returns> the default factory for this connection
		/// </returns>
		/* package */
		
		/// <summary> clears the writeSemaphore id used for active bind operation</summary>
		/* package */
		internal void  clearBindSemId()
		{
			bindSemaphoreId = 0;
			return ;
		}
		
		/// <summary> Writes an LdapMessage to the Ldap server over a socket.
		/// 
		/// </summary>
		/// <param name="info">the Message containing the message to write.
		/// </param>
		/* package */
		internal void  writeMessage(Message info)
		{
			messages.Add(info);
			// For bind requests, if not connected, attempt to reconnect
			if (info.BindRequest && (Connected == false) && ((System.Object) host != null))
			{
				connect(host, port, info.MessageID);
			}
			LdapMessage msg = info.Request;
			writeMessage(msg);
			return ;
		}
		
		
		/// <summary> Writes an LdapMessage to the Ldap server over a socket.
		/// 
		/// </summary>
		/// <param name="msg">the message to write.
		/// </param>
		/* package */
		internal void  writeMessage(LdapMessage msg)
		{
			int id;
			// Get the correct semaphore id for bind operations
			if (bindSemaphoreId == 0)
			{
				// Semaphore id for normal operations
				id = msg.MessageID;
			}
			else
			{
				// Semaphore id for sasl bind operations
				id = bindSemaphoreId;
			}
			System.IO.Stream myOut = out_Renamed;
			
			acquireWriteSemaphore(id);
			try
			{
				if (myOut == null)
				{
					throw new System.IO.IOException("Output stream not initialized");
				}
				sbyte[] ber = msg.Asn1Object.getEncoding(encoder);
				myOut.Write(SupportClass.ToByteArray(ber), 0, ber.Length);
				myOut.Flush();
			}
			catch (System.IO.IOException ioe)
			{
				
				/*
				* IOException could be due to a server shutdown notification which
				* caused our Connection to quit.  If so we send back a slightly
				* different error message.  We could have checked this a little
				* earlier in the method but that would be an expensive check each
				* time we send out a message.  Since this shutdown request is
				* going to be an infrequent occurence we check for it only when
				* we get an IOException.  shutdown() will do the cleanup.
				*/
				if (clientActive)
				{
					// We beliefe the connection was alive
					if (unsolSvrShutDnNotification)
					{
						// got server shutdown
						throw new LdapException(ExceptionMessages.SERVER_SHUTDOWN_REQ, new System.Object[]{host, port}, LdapException.CONNECT_ERROR, null, ioe);
					}
					
					// Other I/O Exceptions on host:port are reported as is
					throw new LdapException(ExceptionMessages.IO_EXCEPTION, new System.Object[]{host, port}, LdapException.CONNECT_ERROR, null, ioe);
				}
			}
			finally
			{
				freeWriteSemaphore(id);
			}
			return ;
		}
		
		/// <summary> Returns the message agent for this msg ID</summary>
		/* package */
		internal MessageAgent getMessageAgent(int msgId)
		{
			Message info = messages.findMessageById(msgId);
			return info.MessageAgent;
		}
		
		/// <summary> Removes a Message class from the Connection's list
		/// 
		/// </summary>
		/// <param name="info">the Message class to remove from the list
		/// </param>
		/* package */
		internal void  removeMessage(Message info)
		{
			bool done = SupportClass.VectorRemoveElement(messages, info);
			return ;
		}
		
		/// <summary> Cleans up resources associated with this connection.</summary>
		~Connection()
		{
			shutdown("Finalize", 0, null);
			return ;
		}
		/// <summary> Cleans up resources associated with this connection.
		/// This method may be called by finalize() for the connection, or it may
		/// be called by LdapConnection.disconnect().
		/// Should not have a writeSemaphore lock in place, as deadlock can occur
		/// while abandoning connections.
		/// </summary>
		private void  shutdown(System.String reason, int semaphoreId, InterThreadException notifyUser)
		{
			Message info = null;
			if (!clientActive)
			{
				return ;
			}
			clientActive = false;
			while (true)
			{
				// remove messages from connection list and send abandon
				try
				{
					System.Object temp_object;
					temp_object = messages[0];
					messages.RemoveAt(0);
					info = (Message) temp_object;
				}
				catch (ArgumentOutOfRangeException ex)
				{
					// No more messages
					break;
				}
				info.Abandon(null, notifyUser); // also notifies the application
			}
			
			int semId = acquireWriteSemaphore(semaphoreId);
			// Now send unbind if socket not closed
			if ((bindProperties != null) && (out_Renamed != null) && (!bindProperties.Anonymous))
			{
				try
				{
					LdapMessage msg = new LdapUnbindRequest(null);
					sbyte[] ber = msg.Asn1Object.getEncoding(encoder);
					out_Renamed.Write(SupportClass.ToByteArray(ber), 0, ber.Length);
					out_Renamed.Flush();
				}
				catch (System.Exception ex)
				{
					; // don't worry about error
				}
			}
			bindProperties = null;
			
			in_Renamed = null;
			out_Renamed = null;
			if (socket != null)
			{
				// Close the socket
				try
				{
					socket.Close();
				}
				catch (System.IO.IOException ie)
				{
					// ignore problem closing socket
				}
				socket = null;
			}
			freeWriteSemaphore(semId);
			return ;
		}
		
		/// <summary> This tests to see if there are any outstanding messages.  If no messages
		/// are in the queue it returns true.  Each message will be tested to
		/// verify that it is complete.
		/// <I>The writeSemaphore must be set for this method to be reliable!</I>
		/// 
		/// </summary>
		/// <returns> true if no outstanding messages
		/// </returns>
		/* package */
		internal bool areMessagesComplete()
		{
			System.Object[] messages = this.messages.ObjectArray;
			int length = messages.Length;
			
			// Check if SASL bind in progress
			if (bindSemaphoreId != 0)
			{
				return false;
			}
			
			// Check if any messages queued
			if (length == 0)
			{
				return true;
			}
			
			for (int i = 0; i < length; i++)
			{
				if (((Message) messages[i]).Complete == false)
					return false;
			}
			return true;
		}
		
		/// <summary> The reader thread will stop when a reply is read with an ID equal
		/// to the messageID passed in to this method.  This is used by
		/// LdapConnection.StartTLS.
		/// </summary>
		/* package */
		internal void  stopReaderOnReply(int messageID)
		{
			
			this.stopReaderMessageID = messageID;
			return ;
		}
		
		/// <summary>startReader
		/// startReader should be called when socket and io streams have been
		/// set or changed.  In particular after client.Connection.startTLS()
		/// It assumes the reader thread is not running.
		/// </summary>
		/* package */
		internal void  startReader()
		{
			// Start Reader Thread
			SupportClass.ThreadClass r =new SupportClass.ThreadClass(new System.Threading.ThreadStart(new ReaderThread(this).Run));
//			Thread r = new Thread(new ThreadStart(new ReaderThread(this).Run));
			r.IsBackground = true; // If the last thread running, allow exit.
			r.Start();
			waitForReader(r);
			return ;
		}
		
		/// <summary> StartsTLS, in this package, assumes the caller has:
		/// 1) Acquired the writeSemaphore
		/// 2) Stopped the reader thread
		/// 3) checked that no messages are outstanding on this connection.
		/// 
		/// After calling this method upper layers should start the reader
		/// by calling startReader()
		/// 
		/// In the client.Connection, StartTLS assumes Ldap.LdapConnection will
		/// stop and start the reader thread.  Connection.StopTLS will stop
		/// and start the reader thread.
		/// </summary>
		/* package */
/*		internal void  startTLS()
		{
			if (this.mySocketFactory == null)
			{
				throw new LdapException(ExceptionMessages.NO_TLS_FACTORY, LdapException.TLS_NOT_SUPPORTED, null);
			}
			else if (!(this.mySocketFactory is LdapTLSSocketFactory))
			{
				throw new LdapException(ExceptionMessages.WRONG_FACTORY, LdapException.TLS_NOT_SUPPORTED, null);
			}
			
			try
			{
				waitForReader(null);
				this.nonTLSBackup = this.socket;
				this.socket = ((LdapTLSSocketFactory) this.mySocketFactory).createSocket(this.socket);
				this.in_Renamed = (System.IO.Stream) socket.GetStream();
				this.out_Renamed = (System.IO.Stream) socket.GetStream();
				
				if (Debug.Ldap_DEBUG)
				{
					Debug.trace(Debug.TLS, "connection.startTLS, nonTLSBackup:" + nonTLSBackup + ", TLSSocket:" + socket + ", input:" + in_Renamed + "," + "output:" + out_Renamed);
				}
			}
			catch (System.Exception uhe)
			{
				this.nonTLSBackup = null;
				throw new LdapException("The host is unknown", LdapException.CONNECT_ERROR, null, uhe);
			}
			catch (System.IO.IOException ioe)
			{
				this.nonTLSBackup = null;
				throw new LdapException("Could not negotiate a secure connection", LdapException.CONNECT_ERROR, null, ioe);
			}
			return ;
		}
*/		
		/*
		* Stops TLS.
		*
		* StopTLS, in this package, assumes the caller has:
		*  1) blocked writing (acquireWriteSemaphore).
		*  2) checked that no messages are outstanding.
		*
		*  StopTLS Needs to do the following:
		*  1) close the current socket
		*      - This stops the reader thread
		*      - set STOP_READING flag on stopReaderMessageID so that
		*        the reader knows that the IOException is planned.
		*  2) replace the current socket with nonTLSBackup,
		*  3) and set nonTLSBackup to null;
		*  4) reset input and outputstreams
		*  5) start the reader thread by calling startReader
		*
		*  Note: Sun's JSSE doesn't allow the nonTLSBackup socket to be
		* used any more, even though autoclose was false: you get an IOException.
		* IBM's JSSE hangs when you close the JSSE socket.
		*/
		/* package */
/*		internal void  stopTLS()
		{
			try
			{
				this.stopReaderMessageID = Connection.STOP_READING;
				this.socket.Close();
				waitForReader(null);
				this.socket = this.nonTLSBackup;
				this.in_Renamed = (System.IO.Stream) this.socket.GetStream();
				this.out_Renamed = (System.IO.Stream) this.socket.GetStream();
				if (Debug.Ldap_DEBUG)
				{
					Debug.trace(Debug.TLS, "connection.stopTLS, nonTLSBackup:" + nonTLSBackup + ", TLSSocket:" + socket + ", input:" + in_Renamed + "," + "output:" + out_Renamed);
				}
				// Allow the new reader to start
				this.stopReaderMessageID = Connection.CONTINUE_READING;
			}
			catch (System.IO.IOException ioe)
			{
				throw new LdapException(ExceptionMessages.STOPTLS_ERROR, LdapException.CONNECT_ERROR, null, ioe);
			}
			finally
			{
				this.nonTLSBackup = null;
				startReader();
			}
			return ;
		}
*///TLS not supported in first release		

		public class ReaderThread
		{
			private void  InitBlock(Connection enclosingInstance)
			{
				this.enclosingInstance = enclosingInstance;
			}
			private Connection enclosingInstance;
			public Connection Enclosing_Instance
			{
				get
				{
					return enclosingInstance;
				}
				
			}
			public ReaderThread(Connection enclosingInstance)
			{
				InitBlock(enclosingInstance);
				return ;
			}
			
			/// <summary> This thread decodes and processes RfcLdapMessage's from the server.
			/// 
			/// Note: This thread needs a graceful shutdown implementation.
			/// </summary>
			public virtual void  Run()
			{
				
				System.String reason = "reader: thread stopping";
				InterThreadException notify = null;
				Message info = null;
				System.IO.IOException ioex = null;
				this.enclosingInstance.reader = SupportClass.ThreadClass.Current();				
//				Enclosing_Instance.reader = SupportClass.ThreadClass.Current();
//				Console.WriteLine("Inside run:" + this.enclosingInstance.reader.Name);
				try
				{
					for (; ; )
					{
						// -------------------------------------------------------
						// Decode an RfcLdapMessage directly from the socket.
						// -------------------------------------------------------
						Asn1Identifier asn1ID;
						System.IO.Stream myIn;
						/* get current value of in, keep value consistant
						* though the loop, i.e. even during shutdown
						*/
						myIn = this.enclosingInstance.in_Renamed;
						if (myIn == null)
						{
							break;
						}
						asn1ID = new Asn1Identifier(myIn);
						int tag = asn1ID.Tag;
						if (asn1ID.Tag != Asn1Sequence.TAG)
						{
							continue; // loop looking for an RfcLdapMessage identifier
						}
						
						// Turn the message into an RfcMessage class
						Asn1Length asn1Len = new Asn1Length(myIn);
						
						RfcLdapMessage msg = new RfcLdapMessage(this.enclosingInstance.decoder, myIn, asn1Len.Length);
						
						// ------------------------------------------------------------
						// Process the decoded RfcLdapMessage.
						// ------------------------------------------------------------
						int msgId = msg.MessageID;
						
						// Find the message which requested this response.
						// It is possible to receive a response for a request which
						// has been abandoned. If abandoned, throw it away
						try
						{
							info = this.enclosingInstance.messages.findMessageById(msgId);
							info.putReply(msg); // queue & wake up waiting thread
						}
						catch (System.FieldAccessException ex)
						{
							
							/*
							* We get the NoSuchFieldException when we could not find
							* a matching message id.  First check to see if this is
							* an unsolicited notification (msgID == 0). If it is not
							* we throw it away. If it is we call any unsolicited
							* listeners that might have been registered to listen for these
							* messages.
							*/
							
							
							/* Note the location of this code.  We could have required
							* that message ID 0 be just like other message ID's but
							* since message ID 0 has to be treated specially we have
							* a separate check for message ID 0.  Also note that
							* this test is after the regular message list has been
							* checked for.  We could have always checked the list
							* of messages after checking if this is an unsolicited
							* notification but that would have inefficient as
							* message ID 0 is a rare event (as of this time).
							*/
							if (msgId == 0)
							{
								
								
								// Notify any listeners that might have been registered
								this.enclosingInstance.notifyAllUnsolicitedListeners(msg);
								
								/*
								* Was this a server shutdown unsolicited notification.
								* IF so we quit. Actually calling the return will
								* first transfer control to the finally clause which
								* will do the necessary clean up.
								*/
								if (this.enclosingInstance.unsolSvrShutDnNotification)
								{
									notify = new InterThreadException(ExceptionMessages.SERVER_SHUTDOWN_REQ, new System.Object[]{this.enclosingInstance.host, this.enclosingInstance.port}, LdapException.CONNECT_ERROR, null, null);
									
									return ;
								}
							}
							else
							{
								
							}
						}
						if ((this.enclosingInstance.stopReaderMessageID == msgId) || (this.enclosingInstance.stopReaderMessageID == Novell.Directory.Ldap.Connection.STOP_READING))
						{
							// Stop the reader Thread.
							return ;
						}
					}
				}
				catch (System.IO.IOException ioe)
				{
					
					ioex = ioe;
					if ((this.enclosingInstance.stopReaderMessageID != Novell.Directory.Ldap.Connection.STOP_READING) && this.enclosingInstance.clientActive)
					{
						// Connection lost waiting for results from host:port
						notify = new InterThreadException(ExceptionMessages.CONNECTION_WAIT, new System.Object[]{this.enclosingInstance.host, this.enclosingInstance.port}, LdapException.CONNECT_ERROR, ioe, info);
					}
					// The connection is no good, don't use it any more
					this.enclosingInstance.in_Renamed = null;
					this.enclosingInstance.out_Renamed = null;
				}
				finally
				{
					/*
					* There can be four states that the reader can be in at this point:
					*  1) We are starting TLS and will be restarting the reader
					*     after we have negotiated TLS.
					*      - Indicated by whether stopReaderMessageID does not
					*        equal CONTINUE_READING.
					*      - Don't call Shutdown.
					*  2) We are stoping TLS and will be restarting after TLS is
					*     stopped.
					*      - Indicated by an IOException AND stopReaderMessageID equals
					*        STOP_READING - in which case notify will be null.
					*      - Don't call Shutdown
					*  3) We receive a Server Shutdown notification.
					*      - Indicated by messageID equal to 0.
					*      - call Shutdown.
					*  4) Another error occured
					*      - Indicated by an IOException AND notify is not NULL
					*      - call Shutdown.
					*/
					if ((!this.enclosingInstance.clientActive) || (notify != null))
					{
						//#3 & 4
						this.enclosingInstance.shutdown(reason, 0, notify);
					}
					else
					{
						this.enclosingInstance.stopReaderMessageID = Novell.Directory.Ldap.Connection.CONTINUE_READING;
					}
				}
				this.enclosingInstance.deadReaderException = ioex;
				this.enclosingInstance.deadReader = this.enclosingInstance.reader;
				this.enclosingInstance.reader = null;
				return ;
			}
		} // End class ReaderThread
		
		/// <summary>Add the specific object to the list of listeners that want to be
		/// notified when an unsolicited notification is received.
		/// </summary>
		/* package */
		internal void  AddUnsolicitedNotificationListener(LdapUnsolicitedNotificationListener listener)
		{
			unsolicitedListeners.Add(listener);
			return ;
		}
		
		/// <summary>Remove the specific object from current list of listeners</summary>
		/* package */
		internal void  RemoveUnsolicitedNotificationListener(LdapUnsolicitedNotificationListener listener)
		{
			SupportClass.VectorRemoveElement(unsolicitedListeners, listener);
			return ;
		}
		
		/// <summary>Inner class defined so that we can spawn off each unsolicited
		/// listener as a seperate thread.  We did not want to call the
		/// unsolicited listener method directly as this would have tied up our
		/// deamon listener thread in the applications unsolicited listener method.
		/// Since we do not know what the application unsolicited listener
		/// might be doing and how long it will take to process the uncoslicited
		/// notification.  We use this class to spawn off the unsolicited
		/// notification as a separate thread
		/// </summary>
		private class UnsolicitedListenerThread:SupportClass.ThreadClass
		{
			private void  InitBlock(Connection enclosingInstance)
			{
				this.enclosingInstance = enclosingInstance;
			}
			private Connection enclosingInstance;
			public Connection Enclosing_Instance
			{
				get
				{
					return enclosingInstance;
				}
				
			}
			private LdapUnsolicitedNotificationListener listenerObj;
			private LdapExtendedResponse unsolicitedMsg;
			
			/* package */
			internal UnsolicitedListenerThread(Connection enclosingInstance, LdapUnsolicitedNotificationListener l, LdapExtendedResponse m)
			{
				InitBlock(enclosingInstance);
				this.listenerObj = l;
				this.unsolicitedMsg = m;
				return ;
			}
			
			public override void  Run()
			{
				listenerObj.messageReceived(unsolicitedMsg);
				return ;
			}
		}
		
		private void  notifyAllUnsolicitedListeners(RfcLdapMessage message)
		{
			
			
			// MISSING:  If this is a shutdown notification from the server
			// set a flag in the Connection class so that we can throw an
			// appropriate LdapException to the application
			LdapMessage extendedLdapMessage = new LdapExtendedResponse(message);
			System.String notificationOID = ((LdapExtendedResponse) extendedLdapMessage).ID;
			if (notificationOID.Equals(LdapConnection.SERVER_SHUTDOWN_OID))
			{
				
				
				unsolSvrShutDnNotification = true;
			}
			
			int numOfListeners = unsolicitedListeners.Count;
			
			// Cycle through all the listeners
			for (int i = 0; i < numOfListeners; i++)
			{
				
				// Get next listener
				LdapUnsolicitedNotificationListener listener = (LdapUnsolicitedNotificationListener) unsolicitedListeners[i];
				
				
				// Create a new ExtendedResponse each time as we do not want each listener
				// to have its own copy of the message
				LdapExtendedResponse tempLdapMessage = new LdapExtendedResponse(message);
				
				// Spawn a new thread for each listener to go process the message
				// The reason we create a new thread rather than just call the
				// the messageReceived method directly is beacuse we do not know
				// what kind of processing the notification listener class will
				// do.  We do not want our deamon thread to block waiting for
				// the notification listener method to return.
				UnsolicitedListenerThread u = new UnsolicitedListenerThread(this, listener, tempLdapMessage);
				u.Start();
			}
			
			
			return ;
		}
		static Connection()
		{
			nameLock = new System.Object();
			sdk = new System.Text.StringBuilder("3.0").ToString();
			protocol = 3;
		}
	}
}
