/****************************************************************************** * 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.Message.cs // // Author: // Sunil Kumar (Sunilk@novell.com) // // (C) 2003 Novell, Inc (http://www.novell.com) // using System; using Novell.Directory.Ldap.Rfc2251; using Novell.Directory.Ldap.Utilclass; namespace Novell.Directory.Ldap { /// Encapsulates an Ldap message, its state, and its replies. /* package */ class Message { private void InitBlock() { replies = new MessageVector(5, 5); } /// Get number of messages queued. /// Don't count the last message containing result code. /// virtual internal int Count { /* package */ get { int size = replies.Count; if (complete) { return (size > 0?(size - 1):size); } else { return size; } } } /// sets the agent for this message virtual internal MessageAgent Agent { /* package */ set { this.agent = value; return ; } } /// Returns true if replies are queued /// /// /// false if no replies are queued, otherwise true /// /* package */ internal virtual bool hasReplies() { if (replies == null) { // abandoned request return false; } return (replies.Count > 0); } virtual internal int MessageType { /* package */ get { if (msg == null) { return - 1; } return msg.Type; } } virtual internal int MessageID { /* package */ get { return msgId; } } /// gets the operation complete status for this message /// /// /// the true if the operation is complete, i.e. /// the LdapResult has been received. /// virtual internal bool Complete { /* package */ get { return complete; } } /// Gets the next reply from the reply queue or waits until one is there /// /// /// the next reply message on the reply queue or null /// /* package */ internal virtual System.Object waitForReply() { if (replies == null) { return null; } // sync on message so don't confuse with timer thread lock (replies.SyncRoot) { System.Object msg = null; while (waitForReply_Renamed_Field) { if ((replies.Count == 0)) { try { System.Threading.Monitor.Wait(replies.SyncRoot); } catch (System.Threading.ThreadInterruptedException ir) { ; // do nothing } if (waitForReply_Renamed_Field) { continue; } else { break; } } else { System.Object temp_object; temp_object = replies[0]; replies.RemoveAt(0); msg = temp_object; // Atomic get and remove } if ((complete || !acceptReplies) && (replies.Count == 0)) { // Remove msg from connection queue when last reply read conn.removeMessage(this); } else { } return msg; } return null; } } /// Gets the next reply from the reply queue if one exists /// /// /// the next reply message on the reply queue or null if none /// virtual internal System.Object Reply { /* package */ get { System.Object msg; if (replies == null) { return null; } lock (replies.SyncRoot) { // Test and remove must be atomic if ((replies.Count == 0)) { return null; // No data } System.Object temp_object; temp_object = replies[0]; replies.RemoveAt(0); msg = temp_object; // Atomic get and remove } if ((conn != null) && (complete || !acceptReplies) && (replies.Count == 0)) { // Remove msg from connection queue when last reply read conn.removeMessage(this); } return msg; } } /// Returns true if replies are accepted for this request. /// /// /// false if replies are no longer accepted for this request /// /* package */ internal virtual bool acceptsReplies() { return acceptReplies; } /// gets the LdapMessage request associated with this message /// /// /// the LdapMessage request associated with this message /// virtual internal LdapMessage Request { /*package*/ get { return msg; } } virtual internal bool BindRequest { /* package */ get { return (bindprops != null); } } /// gets the MessageAgent associated with this message /// /// /// the MessageAgent associated with this message /// virtual internal MessageAgent MessageAgent { /* package */ get { return agent; } } private LdapMessage msg; // msg request sent to server private Connection conn; // Connection object where msg sent private MessageAgent agent; // MessageAgent handling this request private LdapMessageQueue queue; // Application message queue private int mslimit; // client time limit in milliseconds private SupportClass.ThreadClass timer = null; // Timeout thread // Note: MessageVector is synchronized private MessageVector replies; // place to store replies private int msgId; // message ID of this request private bool acceptReplies = true; // false if no longer accepting replies private bool waitForReply_Renamed_Field = true; // true if wait for reply private bool complete = false; // true LdapResult received private System.String name; // String name used for Debug private BindProperties bindprops; // Bind properties if a bind request internal Message(LdapMessage msg, int mslimit, Connection conn, MessageAgent agent, LdapMessageQueue queue, BindProperties bindprops) { InitBlock(); this.msg = msg; this.conn = conn; this.agent = agent; this.queue = queue; this.mslimit = mslimit; this.msgId = msg.MessageID; this.bindprops = bindprops; return ; } internal void sendMessage() { conn.writeMessage(this); // Start the timer thread if (mslimit != 0) { // Don't start the timer thread for abandon or Unbind switch (msg.Type) { case LdapMessage.ABANDON_REQUEST: case LdapMessage.UNBIND_REQUEST: mslimit = 0; break; default: timer = new Timeout(this, mslimit, this); timer.IsBackground = true; // If this is the last thread running, allow exit. timer.Start(); break; } } return ; } internal virtual void Abandon(LdapConstraints cons, InterThreadException informUserEx) { if (!waitForReply_Renamed_Field) { return ; } acceptReplies = false; // don't listen to anyone waitForReply_Renamed_Field = false; // don't let sleeping threads lie if (!complete) { try { // If a bind, release bind semaphore & wake up waiting threads // Must do before writing abandon message, otherwise deadlock if (bindprops != null) { int id; if (conn.BindSemIdClear) { // Semaphore id for normal operations id = msgId; } else { // Semaphore id for sasl bind id = conn.BindSemId; conn.clearBindSemId(); } conn.freeWriteSemaphore(id); } // Create the abandon message, but don't track it. LdapControl[] cont = null; if (cons != null) { cont = cons.getControls(); } LdapMessage msg = new LdapAbandonRequest(msgId, cont); // Send abandon message to server conn.writeMessage(msg); } catch (LdapException ex) { ; // do nothing } // If not informing user, remove message from agent if (informUserEx == null) { agent.Abandon(msgId, null); } conn.removeMessage(this); } // Get rid of all replies queued if (informUserEx != null) { replies.Add(new LdapResponse(informUserEx, conn.ActiveReferral)); stopTimer(); // wake up waiting threads to receive exception sleepersAwake(); // Message will get cleaned up when last response removed from queue } else { // Wake up any waiting threads, so they can terminate. // If informing the user, we wake sleepers after // caller queues dummy response with error status sleepersAwake(); cleanup(); } return ; } private void cleanup() { stopTimer(); // Make sure timer stopped try { acceptReplies = false; if (conn != null) { conn.removeMessage(this); } // Empty out any accumuluated replies if (replies != null) { while (!(replies.Count == 0)) { System.Object temp_object; temp_object = replies[0]; replies.RemoveAt(0); System.Object generatedAux = temp_object; } } } catch (System.Exception ex) { // nothing } // Let GC clean up this stuff, leave name in case finalized is called conn = null; msg = null; // agent = null; // leave this reference queue = null; //replies = null; //leave this since we use it as a semaphore bindprops = null; return ; } ~Message() { cleanup(); return ; } internal virtual void putReply(RfcLdapMessage message) { if (!acceptReplies) { return ; } lock(replies) { replies.Add(message); } message.RequestingMessage = msg; // Save request message info switch (message.Type) { case LdapMessage.SEARCH_RESPONSE: case LdapMessage.SEARCH_RESULT_REFERENCE: case LdapMessage.INTERMEDIATE_RESPONSE: break; default: int res; stopTimer(); // Accept no more results for this message // Leave on connection queue so we can abandon if necessary acceptReplies = false; complete = true; if (bindprops != null) { res = ((RfcResponse) message.Response).getResultCode().intValue(); if (res == LdapException.SASL_BIND_IN_PROGRESS) { } else { // We either have success or failure on the bind if (res == LdapException.SUCCESS) { // Set bind properties into connection object conn.BindProperties = bindprops; } else { } // If not a sasl bind in-progress, release the bind // semaphore and wake up all waiting threads int id; if (conn.BindSemIdClear) { // Semaphore id for normal operations id = msgId; } else { // Semaphore id for sasl bind id = conn.BindSemId; conn.clearBindSemId(); } conn.freeWriteSemaphore(id); } } break; } // wake up waiting threads sleepersAwake(); return ; } /// stops the timeout timer from running /* package */ internal virtual void stopTimer() { // If timer thread started, stop it if (timer != null) { timer.Interrupt(); } return ; } /// Notifies all waiting threads private void sleepersAwake() { // Notify any thread waiting for this message id lock (replies.SyncRoot) { System.Threading.Monitor.Pulse(replies.SyncRoot); } // Notify a thread waiting for any message id agent.sleepersAwake(false); return ; } /// Timer class to provide timing for messages. Only called /// if time to wait is non zero. /// private sealed class Timeout:SupportClass.ThreadClass { private void InitBlock(Message enclosingInstance) { this.enclosingInstance = enclosingInstance; } private Message enclosingInstance; public Message Enclosing_Instance { get { return enclosingInstance; } } private int timeToWait = 0; private Message message; /* package */ internal Timeout(Message enclosingInstance, int interval, Message msg):base() { InitBlock(enclosingInstance); timeToWait = interval; message = msg; return ; } /// The timeout thread. If it wakes from the sleep, future input /// is stopped and the request is timed out. /// override public void Run() { try { System.Threading.Thread.Sleep(new System.TimeSpan(10000 * timeToWait)); message.acceptReplies = false; // Note: Abandon clears the bind semaphore after failed bind. message.Abandon(null, new InterThreadException("Client request timed out", null, LdapException.Ldap_TIMEOUT, null, message)); } catch (System.Threading.ThreadInterruptedException ie) { // the timer was stopped, do nothing } return ; } } /// sets the agent for this message } }