NotificationConnectionHandler.cs :  » Network-Clients » MSNP-Helper-API » MSNP » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » Network Clients » MSNP Helper API 
MSNP Helper API » MSNP » NotificationConnectionHandler.cs
/*
 * (standard BSD license)
 * 
 * Copyright (c) 2002, Chad Myers, et al.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. Redistributions in binary form
 * must reproduce the above copyright notice, this list of conditions and the
 * following disclaimer in the documentation and/or other materials provided with
 * the distribution. Neither the name of SourceForge, nor Microsoft nor the names
 * of its contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;

namespace MSNP{
  /// <summary>
  /// Handles a Notification Server connection.
  /// </summary>
  /// <remarks>
  /// <para>The Notification Server connection is the main conversation point for MSNP.
  /// Requests for new sessions or contact list updates are made through here, and any
  /// notifications received from other users or the MSN system itself are sent through here.</para>
  /// </remarks>
  internal class NotificationConnectionHandler : ConnectionHandler
  {
    /**********************************************/
    // Enumerations
    /**********************************************/
    /// <summary>
    /// Determines which state the <see cref="ProcessConnection"/> method is in this cycle.
    /// </summary>
    private enum ConnectionState :int {STARTING, SETPROTOCOL, AUTHMD5, AUTHENTICATED, GETLIST, SENTONLINE, ONLINE, CLOSING}

    /// <summary>
    /// Determines which phase of the MD5 authentication the connection is in.
    /// </summary>
    private enum MD5State :int {STARTING, POLICYREQUEST, AUTHREQUEST, AUTHRESPONSE}

    /**********************************************/
    // Fields
    /**********************************************/
    private int      m_State    = (int) ConnectionState.STARTING;  // Current connection state
    private int      m_MD5State  = (int) MD5State.STARTING;      // Current MD5 auth state
    private int      m_TotalFLContacts = 0;              // Total number of forward-list contacts
    private int      m_ReceivedFLContacts = 0;            // Total number of received FL contacts
    private String    m_FriendlyName;                  // Local user's friendly name
    private Hashtable  m_FLContacts  = Hashtable.Synchronized(new Hashtable(48)); // Contacts on the forward-list
    private Hashtable  m_Sessions  = Hashtable.Synchronized(new Hashtable(16));   // Currently open sessions
    private ISessionHandler m_SessionHandler;              // API implementer session handler for new sessions
    private Queue    m_PendingSessions = Queue.Synchronized(new Queue(10)); // Pending requests for new sessions


    /**********************************************/
    // Delegates and Events
    /**********************************************/
    /// <summary>
    /// Used for all Notification-related events
    /// </summary>
    internal delegate void NotificationEventHandler(object sender, MSNPEventArgs ne);

    /// <summary>
    /// Thrown when a contact has change its state (online to offline, or vice versa)
    /// </summary>
    internal event NotificationEventHandler ContactStateChangedEvent;

    /// <summary>
    /// Thrown when the NS has connected and authenticated.
    /// </summary>
    internal event EventHandler OnlineEvent;

    /// <summary>
    /// Thrown when this connection has been stopped or was terminated from the other side.
    /// </summary>
    internal event EventHandler SignedOutEvent;

    /**********************************************/
    // Constructors
    /**********************************************/
    /// <summary>
    /// Configures this connection
    /// </summary>
    /// <param name="host">The DNS name or IP address of the NS to connect to.</param>
    /// <param name="port">The port of the NS server</param>
    /// <param name="user">The username with which to authenticate</param>
    /// <param name="pass">The user's password</param>
    /// <param name="handler">The implemented session handler for any new sessions</param>
    internal NotificationConnectionHandler(String host, int port, String user, String pass, ISessionHandler handler) :
      base(host, port, user, pass)
    {
      m_SessionHandler = handler;
    }

    /**********************************************/
    // Properties
    /**********************************************/
    /// <summary>
    /// Current forward contact list.
    /// </summary>
    /// <remarks>
    /// This is a clone of the actual list, so any changes are not realized.
    /// </remarks>
    public ICollection FLContacts{ get{ return ((IDictionary)m_FLContacts.Clone()).Values; } }

    /**********************************************/
    // Protected Methods
    /**********************************************/

    /// <summary>
    /// Implementation of the abstract <see cref="ConnectionHandler.ProcessConnection"/> method.
    /// </summary>
    /// <remarks>
    /// <para>This is the main processing method for this class. This method is called
    /// every cycle in the connection thread.</para>
    /// <para>This thread will process the initial protocol initialization dialogue and then
    /// enter the main notification phase and process any new notifications.</para>
    /// </remarks>
    protected override void ProcessConnection()
    {
      Response[] responses = GetResponses();

      // Determine if a specific XFR was sent from the NS and process it immediately.
      Response XFRResponse = LookForSpecificResponse(responses, XFR);
      if( XFRResponse != null && XFRResponse.Parameters[0] == NS)
        HandleXFR(XFRResponse);

      switch(m_State)
      {
        // Initial connection, send the VER command 
        case (int) ConnectionState.STARTING:
          SetProtocol(DEFAULT_PROTOCOL);
          m_State = (int) ConnectionState.SETPROTOCOL;
          break;

        // Ensure proper VER reply was sent, then request policy information.
        case (int) ConnectionState.SETPROTOCOL:
          Response response = LookForProtocolResponse(responses);
          if( response == null )
            break;

          if( response.Parameters.Length != 1 || response.Parameters[0] != DEFAULT_PROTOCOL )
            throw new Exception("Unexpected version response");

          response = null;

          AddRequest( new Request(INF, null) );
          m_State = (int) ConnectionState.AUTHMD5;
          m_MD5State = (int) MD5State.POLICYREQUEST;
          break;

        /*
         * Check INF response and ensure it's MD5 as that's all we support right now
         * Handle the MD5 auth dialog in a seperate method with a seperate state.
         * It's possible that on the final auth success that other messages could be
         * tacked on to the end, so after ProcessAuthMD5 is done, process any other
         * messages.
         */
        case (int) ConnectionState.AUTHMD5:
          ProcessAuthMD5(responses);
          if( m_State == (int) ConnectionState.AUTHENTICATED )
            ProcessResponses(responses);
          break;

        // Auth successful. Request forwarding list.
        case (int) ConnectionState.AUTHENTICATED:
          // Send LST command
          AddRequest( new Request(LST, FL) );
          m_State = (int) ConnectionState.GETLIST;
          break;

        // Check for LST replies and populate our contact list, then set ourselves ONLINE (NLN)
        case (int) ConnectionState.GETLIST:
          for( int i = 0; i < responses.Length; i++ )
          {
            if( responses[i].Command == "LST" && responses[i].Parameters.Length >= 3)
            {
              if( responses[i].Parameters[3] != "0" )
              {
                if( m_TotalFLContacts == 0 )
                  m_TotalFLContacts = Int32.Parse(responses[i].Parameters[3]);

                m_ReceivedFLContacts++;

                Contact newContact = new Contact(responses[i].Parameters[4], responses[i].Parameters[5]);

                m_FLContacts.Add(newContact.UserName, newContact);
              }
            }
          }
          responses = null;

          if( m_ReceivedFLContacts == m_TotalFLContacts )
          {
            AddRequest( new Request(CHG, NLN) );
            m_State = (int) ConnectionState.SENTONLINE;
          }
          break;

        // Check response to CHG command and process any messages that are stuffed
          // in between the reply such as contact state and such.
        case (int) ConnectionState.SENTONLINE:

          ProcessResponses(responses);
          response = LookForSpecificResponse(responses, CHG);

          if( response == null )
          {            
            break;
          }
          else        
          {
            m_State = (int) ConnectionState.ONLINE;
            OnOnlineEvent();
          }

          responses = null;

          break;

        // Online. Process each response
        case (int) ConnectionState.ONLINE:
          ProcessResponses(responses);
          break;

        // Signout requested, begin shutting down
        case (int) ConnectionState.CLOSING:
          CloseConnection();

          // Get opened session
          IDictionary sessions;
          lock( m_Sessions.SyncRoot )
          {
            // Clone the sessions so we don't cause a deadlock 
            // as each session closes.
            sessions = (IDictionary) m_Sessions.Clone();
          }

          // Close each session
          foreach ( object session in sessions.Values )
          {
            ((SwitchboardConnectionHandler)session).Stop();
          }

          // Signal the signout event
          OnSignedOutEvent();
          break;
      }
    }

    /**********************************************/
    // Internal Methods
    /**********************************************/

    /// <summary>
    /// Signal this connection to stop and disconnect gracefully.
    /// </summary>
    internal void Stop()
    {
      lock(this)
      {
        AddRequest( new Request(CHG, FLN) );
        AddRequest( new Request(OUT, null) );
        m_State = (int) ConnectionState.CLOSING;
      }
    }

    /// <summary>
    /// Request a new switchboard connection for chat
    /// </summary>
    /// <param name="user">Initial user to invite</param>
    /// <param name="sessionIdentifier">Implementer-specified identifier for the created session</param>
    internal void StartSBSession(String user, object sessionIdentifier)
    {
      m_PendingSessions.Enqueue(new SessionStartInfo(user, sessionIdentifier));
      AddRequest( new Request(XFR, "SB") );
    }

    /// <summary>
    /// Changes how this client appears to other clients
    /// </summary>
    /// <param name="state">The state to set (currently NLN or FLN)</param>
    /// <param name="substate">The substate (only valid for NLN)</param>
    internal void ChangeState(String state, String substate)
    {
      if( state == NLN )
      {
        if( substate != null && substate.Length > 0 )
          AddRequest( new Request(CHG, substate) );
        else
          AddRequest( new Request(CHG, state) );
      }
      else if( state == FLN || state == HDN )
        AddRequest( new Request(CHG, state) );
      else
        throw new Exception("Unexpected state for state change: " + state);
    }

    /**********************************************/
    // Private Methods
    /**********************************************/

    /// <summary>
    /// Handle an XFR command from the server.
    /// </summary>
    /// <param name="response">The actual XFR response from the server.</param>
    private void HandleXFR(Response response)
    {
      String[] hostAndPort = response.Parameters[1].Split(new char[]{':'});
      Host = hostAndPort[0];
      Port = Int32.Parse(hostAndPort[1]);
      m_State = (int) ConnectionState.STARTING;
      m_MD5State = (int) MD5State.STARTING;
      Reconnect();
    }

    /// <summary>
    /// Handle the MD5 authentication dialog
    /// </summary>
    /// <param name="responses">Current received responses</param>
    private void ProcessAuthMD5(Response[] responses)
    {
      switch(m_MD5State)
      {
        // Policy sent, check to make sure the reply has MD5 as its supported policy
        // Next, send USR command to begin the challenge/response phase.
        case (int) MD5State.POLICYREQUEST:
          Response response = LookForSpecificResponse(responses, INF);
          if( response == null )
            break;

          if( response.Parameters.Length == 0 || response.Parameters[0] != "MD5" )
            throw new Exception("Unexpected policy response from server");

          response = null;

          AddRequest( new Request(USR, "MD5 I " + User) );

          m_MD5State = (int) MD5State.AUTHREQUEST;
          break;

        // Look for USR response and calculate the response to the auth challenge
        case (int) MD5State.AUTHREQUEST:
          response = LookForSpecificResponse(responses, USR);
          if( response == null )
            break;

          if( response.Parameters.Length != 3 )
            throw new Exception("Unexpected authentication response from server");

          // the server-issued challenge string
          string challenge = response.Parameters[2];

          response = null;

          MD5 md5 = new MD5CryptoServiceProvider();

          // Compute a hash of the challenge + the user password
          byte[] hash = md5.ComputeHash(Encoding.ASCII.GetBytes(challenge + Pass));

          string hashHexString = "";

          // Convert it to hex-string (i.e. "abcdef")
          for(int i = 0; i < hash.Length; i++)
          {
            hashHexString += hash[i].ToString("x2");
          }

          // We send USR tid MD5 S responseinfo
          AddRequest( new Request(USR, "MD5 S " + hashHexString) );

          m_MD5State = (int) MD5State.AUTHRESPONSE;
          break;

        // Look for successful response to auth submittal
        case (int) MD5State.AUTHRESPONSE:
          response = LookForSpecificResponse(responses, USR);
          if( response == null )
            break;

          if( response.Parameters.Length != 3 )
            throw new Exception("Unexpected authentication response from server");

          m_FriendlyName = response.Parameters[2];

          response = null;

          m_State = (int) ConnectionState.AUTHENTICATED;
          break;
      }
    }

    /// <summary>
    /// Process current responses
    /// </summary>
    /// <remarks>
    /// <para>This is the main guts of the NotificationConnectionHandler. Here is where
    /// all the notifications from the server actually get processed. All notifications
    /// pass through here and are handled such as contact status change (online/offline),
    /// new session requests, property changes, etc.</para>
    /// </remarks>
    /// <param name="responses">Current received responses</param>
    private void ProcessResponses(Response[] responses)
    {
      for(int i = 0; i < responses.Length; i++ )
      {
        //Debug.WriteLine("Processing response: " + responses[i]);
        if( responses[i].Command == RNG )
        {
          String sessionID = responses[i].Parameters[0];
          
          if( ! m_Sessions.ContainsKey(sessionID) )
          {
            String[] hostAndPort = responses[i].Parameters[1].Split(new char[]{':'});
            String challenge = responses[i].Parameters[3];
            String callerFriendly = responses[i].Parameters[4];
            String caller = responses[i].Parameters[5];

            SwitchboardConnectionHandler sch = new SwitchboardConnectionHandler(
              hostAndPort[0], Int32.Parse(hostAndPort[1]), User, Pass, m_FriendlyName,
              challenge, sessionID, m_SessionHandler, null);

            sch.SessionEndEvent += new EventHandler(GotSessionEnd);
            sch.Start();

            m_Sessions.Add(sessionID, sch);
          }
        }
        else if( 
          responses[i].Command == ILN ||
          responses[i].Command == NLN ||
          responses[i].Command == FLN )
        {          
          setFLContactState(responses[i]);          
        }
        else if( responses[i].Command == XFR && responses[i].Parameters[0] == SB)
        {
          SessionStartInfo sessionInfo = (SessionStartInfo) m_PendingSessions.Dequeue();

          String[] hostAndPort = responses[i].Parameters[1].Split(new char[]{':'});
          String challenge = responses[i].Parameters[3];

          SwitchboardConnectionHandler sch = new SwitchboardConnectionHandler(
            hostAndPort[0], Int32.Parse(hostAndPort[1]), User, Pass, null, challenge,
            null, m_SessionHandler, sessionInfo.SessionIdentifier);

          sch.SessionIDObtained += new EventHandler(GotSessionIDObtained);
          sch.SessionEndEvent += new EventHandler(GotSessionEnd);
          sch.Start();
          sch.InviteContact(sessionInfo.UserHandle);
        }
      }
    }

    /// <summary>
    /// Update or add a contact to the contact list with the specified state.
    /// </summary>
    /// <param name="response">The response containg the contact status change</param>
    private void setFLContactState(Response response)
    {
      String state = response.Command;
      String substate;
      String handle;
      String friendlyName;

      if( state == FLN )
      {
        // If offline, there is no substate and
        // we don't know the friendly name
        substate = "";
        handle = response.Parameters[0];
        friendlyName = null;
      }
      else
      {
        substate = response.Parameters[0];
        handle = response.Parameters[1];
        friendlyName = response.Parameters[2];
      }

      // See if we already have the contact, otherwise
      // create a new one
      Contact contact = (Contact)m_FLContacts[handle];

      if( contact == null )
      {
        contact = new Contact(handle, friendlyName);
        m_FLContacts.Add(handle, contact);
      }

      // Set the state and substate
      contact.setState(state);
      contact.setSubstate(substate);
      
      // Update the friendly name if necessary
      if( friendlyName != null )
        contact.setFriendlyName(friendlyName);

      // Signal the event that the contact's status has changed
      OnContactStateChange(contact);
    }

    /// <summary>
    /// Triggers an <see cref="OnlineEvent"/>
    /// </summary>
    private void OnOnlineEvent()
    {
      if( OnlineEvent != null )
        OnlineEvent(this,  new EventArgs());
    }

    /// <summary>
    /// Triggers a <see cref="SignedOutEvent"/>
    /// </summary>
    private void OnSignedOutEvent()
    {
      if( SignedOutEvent != null )
        SignedOutEvent(this, new EventArgs());
    }

    /// <summary>
    /// Triggers a <see cref="ContactStateChangedEvent"/>
    /// </summary>
    /// <param name="user">The contact whose status has changed</param>
    private void OnContactStateChange(Contact user)
    {
      if( ContactStateChangedEvent != null )
        ContactStateChangedEvent(this, new MSNPEventArgs(user));
    }

    /// <summary>
    /// Called when a session has ended.
    /// </summary>
    /// <param name="sender">The <see cref="SwitchboardConnectionHandler"/> who threw the event.</param>
    /// <param name="args">Not used by this event</param>
    private void GotSessionEnd(object sender, EventArgs args)
    {
      SwitchboardConnectionHandler sch = (SwitchboardConnectionHandler) sender;
      if( sch.SessionID != null )
        m_Sessions.Remove(sch.SessionID);
    }

    /// <summary>
    /// Called when an outgoing session has its ID set
    /// </summary>
    /// <remarks>
    /// When a new session is created for the purposes of calling out,
    /// the session ID is not immediately returned from MSN. Instead,
    /// the response to the first CAL command gives you the current
    /// session's ID. This event is called when that happens.
    /// </remarks>
    /// <param name="sender">The <see cref="SwitchboardConnectionHandler"/> which threw the event.</param>
    /// <param name="args">Not used for this event.</param>
    private void GotSessionIDObtained(object sender, EventArgs args)
    {
      SwitchboardConnectionHandler sch = (SwitchboardConnectionHandler) sender;
      if( m_Sessions[sch.SessionID] == null )
      {
        m_Sessions.Add(sch.SessionID, sch);
      }
    }

    /// <summary>
    /// Class used to hold information for stating a new session.
    /// </summary>
    private class SessionStartInfo
    {
      private String m_UserHandle;
      private object m_SessionIdentifier;

      public SessionStartInfo(String userHandle, object sessionIdentifier)
      {
        m_UserHandle = userHandle;
        m_SessionIdentifier = sessionIdentifier;
      }

      public String UserHandle{ get{ return m_UserHandle; } }
      public object SessionIdentifier{ get{ return m_SessionIdentifier; } }
    }
  }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.