Logo Search packages:      
Sourcecode: galaxium version File versions  Download package

CommandConnection.cs

/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * Copyright (C) 2007 Paul Burton <paulburton89@gmail.com>
 * Copyright (C) 2008 Philippe Durand <draekz@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

using Anculus.Core;

using Galaxium.Core;
using Galaxium.Protocol;

namespace Galaxium.Protocol.Msn
{
      public delegate void CommandHandler<T> (T cmd) where T : IMsnCommand;
      public delegate void CommandHandler (IMsnCommand cmd);
      
      public delegate void ContentHandler<T> (T content) where T : IMsnContent;
      public delegate void ContentHandler (IMsnContent content);
      
      public class CommandConnection : IDisposable
      {
            public event EventHandler<ConnectionEventArgs> Closed;
            public event EventHandler<ConnectionEventArgs> Established;
            public event EventHandler<ConnectionErrorEventArgs> ErrorOccurred;
            
            protected IConnection _connection;
            protected bool _reconnecting;
            
            protected const int _msgPayloadMax = 1400;
            
            // The maximum number of commands (that expect responses) that we'll
            // send before stopping and waiting for responses
            protected const int _maxWaiting = 1;
            
            protected byte[] _buffer = new byte[0];
            protected int _trid = 0;
            protected MsnConnectionType _type;
            
            protected static int _nsCount;
            protected static int _sbCount;
            protected int _dbgId;

            protected Dictionary<Type, List<Delegate>> _commandDelegates = new Dictionary<Type, List<Delegate>> ();
            protected Dictionary<Type, List<Delegate>> _contentDelegates = new Dictionary<Type, List<Delegate>> ();
            
            protected List<IMsnCommand> _outQueue = new List<IMsnCommand> ();
            protected Dictionary<int, IMsnCommand> _waitingResponse = new Dictionary<int, IMsnCommand> ();
            protected Dictionary<IMsnCommand, CommandHandler> _commandQueuedHandlers = new Dictionary<IMsnCommand, CommandHandler> ();
            protected Dictionary<int, CommandHandler> _commandResponseHandlers = new Dictionary<int, CommandHandler> ();
            
            protected static readonly byte _cr = (byte)13;
            protected static readonly byte _lf = (byte)10;
            protected static readonly byte _space = (byte)32;
            
            public MsnSession Session
            {
                  get { return _connection.Session as MsnSession; }
            }
            
            public IConnection Connection
            {
                  get { return _connection; }
            }
            
            public bool Reconnecting
            {
                  get { return _reconnecting; }
            }
            
            public virtual bool CanSend
            {
                  get { return _connection.IsConnected && (!_connection.IsSending) && (_waitingResponse.Count <= _maxWaiting); }
            }
            
            public List<IMsnCommand> OutQueue
            {
                  get { return _outQueue; }
            }
            
            public CommandConnection (MsnSession session, IConnectionInfo info, MsnConnectionType type)
            {
                  if (!info.UseHTTP)
                  {
                        _connection = new TCPConnection (session, info);
                  }
                  else
                  {
                        _connection = new HTTPConnection (session, info);
                        
                        if (type == MsnConnectionType.Notification)
                        {
                              info.HostName = "gateway.messenger.hotmail.com";
                              ((HTTPConnection)_connection).IP = "messenger.hotmail.com";
                              ((HTTPConnection)_connection).Type = HTTPServerTypes.NS;
                        }
                        else if (type == MsnConnectionType.Switchboard)
                        {
                              ((HTTPConnection)_connection).IP = info.HostName;
                              ((HTTPConnection)_connection).Type = HTTPServerTypes.SB;
                        }
                  }
                  
                  if (type == MsnConnectionType.Notification)
                        _dbgId = ++_nsCount;
                  else if (type == MsnConnectionType.Switchboard)
                        _dbgId = ++_sbCount;
                  
                  _connection.Closed += OnClosed;
                  _connection.Established += OnEstablished;
                  _connection.ErrorOccurred += OnErrorOccurred;
                  _connection.DataReceived += OnDataReceived;
                  _connection.AfterConnect += OnAfterConnect;
                  _connection.SendComplete += OnSendComplete;
                  
                  _type = type;
                  
                  ClearHandlers ();
                  AutoAddHandlers ();
            }
            
            public void Connect ()
            {
                  _connection.Connect ();
            }
            
            public void Disconnect ()
            {
                  _connection.Disconnect ();
            }
            
            public void Reconnect ()
            {
                  _reconnecting = true;
                  _connection.Reconnect ();
            }
            
            public virtual void Dispose ()
            {
                  _connection.Dispose ();
            }
            
            protected virtual void OnEstablished (object sender, ConnectionEventArgs args)
            {
                  if (_reconnecting)
                  {
                        _reconnecting = false;
                        return;
                  }
                  
                  if (Established != null)
                        Established (sender, args);
            }
            
            protected virtual void OnAfterConnect (object sender, ConnectionEventArgs args)
            {
                  _trid = 0;
            }
            
            protected virtual void OnSendComplete (object sender, EventArgs args)
            {
                  ProcessOutQueue ();
            }
            
            protected virtual void OnReady (object sender, EventArgs args)
            {
                  
            }
            
            protected virtual void OnClosed (object sender, ConnectionEventArgs args)
            {
                  if (!_reconnecting)
                  {
                        Log.Debug ("Firing closed event.");
                        
                        if (Closed != null)
                              Closed (sender, args);
                  }
                  
                  _outQueue.Clear ();
                  _waitingResponse.Clear ();
                  _commandQueuedHandlers.Clear ();
                  _commandResponseHandlers.Clear ();
            }
            
            protected virtual void OnErrorOccurred (object sender, ConnectionErrorEventArgs args)
            {
                  if (ErrorOccurred != null)
                        ErrorOccurred (sender, args);
            }
            
            protected virtual void OnDataReceived (object sender, ConnectionDataEventArgs args)
            {
                  ThreadUtility.Check ();
                  
                  //lock (_buffer)
                  {
                        AppendToBuffer (args.Buffer, args.Length);
                  
                        while (true)
                        {     
                              int iCrlf = GetCrlfIndex ();
                              
                              if (iCrlf < 0)
                                    return;
                              
                              string cmdName = Encoding.UTF8.GetString (_buffer, 0, 3);
                              Type cmdType = MsnCommandAttribute.FindType (cmdName, _type);
                              
                              if (cmdType == null)
                                    cmdType = typeof (ErrorCommand);
                              
                              byte[] cmdData = null;
                              
                              MsnCommandAttribute commandAtt = MsnCommandAttribute.Find (cmdType);
                              PayloadCommandAttribute payloadAtt = PayloadCommandAttribute.Find (cmdType);
                              
                              if ((payloadAtt != null) && payloadAtt.IsIn)
                              {
                                    int size = GetPayloadSize (iCrlf);
                                    
                                    if (size < 0)
                                          return;
                                    
                                    int required = iCrlf + 2 + size;
                                    
                                    // Check we have received the whole command
                                    if (required > _buffer.Length)
                                          return;
                                    
                                    cmdData = ReadBuffer (required);
                                    ShrinkBuffer (required);
                              }
                              else
                              {
                                    cmdData = ReadBuffer (iCrlf);
                                    ShrinkBuffer (iCrlf + 2);
                              }
                              
                              IMsnCommand cmd = null;
                              
                              try
                              {
                                    if (commandAtt.Parser != null)
                                          cmd = commandAtt.Parser.Parse ((MsnSession)Session, cmdData);
                                    else
                                          cmd = (IMsnCommand)Activator.CreateInstance (cmdType, Session, cmdData);
                              }
                              catch (Exception ex)
                              {
                                    Log.Error (ex, "Error parsing command!");
                                    
                                    try
                                    {
                                          Log.Debug (Encoding.UTF8.GetString (cmdData));
                                    }
                                    catch
                                    {
                                          Log.Warn ("Unable to decode cmdData");
                                          
                                          string dataStr = string.Empty;
                                          foreach (byte b in cmdData)
                                                dataStr += string.Format ("{0:x2} ", b);
                                          
                                          Log.Warn (dataStr);
                                    }
                                    
                                    cmd = null;
                              }
                              
                              if (cmd != null)
                              {
                                    List<Delegate> delegates = new List<Delegate> ();
                                    
                                    if (!_commandDelegates.ContainsKey (cmd.GetType ()))
                                    {
                                          if (cmd is ContentCommand)
                                                delegates.AddRange (_commandDelegates[typeof (ContentCommand)]);
                                    }
                                    else
                                          delegates.AddRange (_commandDelegates[cmd.GetType ()]);
                                    
                                    if (cmd.IsTransIn)
                                    {
                                          if (_commandResponseHandlers.ContainsKey (cmd.TransactionID))
                                          {
                                                delegates.Add (_commandResponseHandlers[cmd.TransactionID]);
                                                _commandResponseHandlers.Remove (cmd.TransactionID);
                                          }
                                          
                                          if (_waitingResponse.ContainsKey (cmd.TransactionID))
                                                _waitingResponse.Remove (cmd.TransactionID);
                                    }
                                    
                                    Log.Info ("<< {0} {1} {2} [{3}]: {4}", _type == MsnConnectionType.Notification ? "NS" : "SB", _dbgId, _connection.ConnectionInfo.UseHTTP ? "HTTP" : "TCP", _connection.ConnectionInfo.HostName, cmd);
                                    
                                    if (delegates.Count == 0)
                                          Log.Warn ("Unexpected command ({0}):", cmd.GetType ().FullName);
                                    
                                    foreach (Delegate del in delegates)
                                    {
                                          try
                                          {
                                                del.DynamicInvoke (cmd);
                                          }
                                          catch (Exception ex)
                                          {
                                                Log.Error (ex, "Error Invoking {0} Handler {1}.{2}", cmd.GetType ().Name, del.Method.DeclaringType.Name, del.Method.Name);
                                          }
                                    }
                                    
                                    // If we've received a response for a command, we might now be ready to send
                                    ProcessOutQueue ();
                              }
                        }
                  }
            }
            
            public virtual int Send (IMsnCommand cmd, CommandHandler handler, bool force)
            {
                  ThrowUtility.ThrowIfNull ("cmd", cmd);
                  
                  if (handler != null)
                        _commandQueuedHandlers.Add (cmd, handler);
                  
                  if (force)
                        TrueSend (cmd);
                  else
                  {
                        _outQueue.Add (cmd);
                        ProcessOutQueue ();
                  }
                  
                  return _trid;
            }
            
            public virtual int Send (IMsnCommand cmd, CommandHandler handler)
            {
                  return Send (cmd, handler, false);
            }
            
            public virtual int Send (IMsnCommand cmd)
            {
                  return Send (cmd, null);
            }
            
            public virtual int Send (IMsnCommand cmd, bool force)
            {
                  return Send (cmd, null, force);
            }
            
            void TrueSend (IMsnCommand cmd)
            {
                  if (cmd.IsTransOut)
                  {
                        cmd.TransactionID = ++_trid;
                        
                        if (_commandQueuedHandlers.ContainsKey (cmd))
                        {
                              CommandHandler handler = _commandQueuedHandlers[cmd];
                              _commandQueuedHandlers.Remove (cmd);
                              _commandResponseHandlers[cmd.TransactionID] = handler;
                              
                              //Log.Debug ("Queued {0} response handler {1}.{2} assigned TransID {3}", cmd.Command, (handler as Delegate).Method.DeclaringType.Name, (handler as Delegate).Method.Name, cmd.TransactionID);
                        }
                        //else
                        //    Log.Debug ("No queued {0} response handler", cmd.Command);
                        
                        if (cmd.ExpectResponse)
                              _waitingResponse[cmd.TransactionID] = cmd;
                  }
                  
                  Log.Info (">> {0} {1} {2} [{3}]: {4}", _type == MsnConnectionType.Notification ? "NS" : "SB", _dbgId, _connection.ConnectionInfo.UseHTTP ? "HTTP" : "TCP", _connection.ConnectionInfo.HostName, cmd);
                  
                  // If we are using HTTP method, we may need to setup some stuff
                  if (Connection.ConnectionInfo.UseHTTP)
                  {
                        HTTPConnection httpConnection = (HTTPConnection)Connection;
                        
                        if (cmd is VERCommand || cmd is SBUSRCommand || cmd is ANSCommand)
                              httpConnection.Action = HTTPActions.Open;
                        else
                              httpConnection.Action = HTTPActions.None;
                  }
                  
                  _connection.Send (cmd.ToByteArray ());
            }
            
            protected virtual void ProcessOutQueue ()
            {
                  // We aren't ready to send right now
                  if (!CanSend)
                        return;
                  
                  //Log.Debug ("{0} Messages Queued", _outQueue.Count);
                  
                  if (_outQueue.Count == 0)
                  {
                        // We're ready, we've got nothing to send, something might want to know
                        OnReady (this, EventArgs.Empty);
                        return;
                  }
                  
                  IMsnCommand cmd = _outQueue[0];
                  _outQueue.RemoveAt (0);
                  
                  TrueSend (cmd);
            }
            
            protected void SendCleanDisconnect ()
            {
                  Send (new OUTCommand (Session as MsnSession));
            }
            
            private int GetPayloadSize (int crlf)
            {
                  for (int i = crlf; i > 0; i--)
                  {
                        byte b = _buffer[i];

                        if (b == _space)
                        {
                              int len = crlf - i - 1;
                              byte[] sizeBuffer = new byte[len];
                              Array.Copy (_buffer, i + 1, sizeBuffer, 0, len);

                              int payload = 0;
                              int.TryParse (Encoding.UTF8.GetString (sizeBuffer), out payload);

                              return payload;
                        }
                  }

                  return -1;
            }

            private byte[] ReadBuffer (int size)
            {
                  byte[] data = new byte[size];
                  Array.Copy (_buffer, 0, data, 0, size);
                  return data;
            }

            private void ShrinkBuffer (int skip)
            {
                  if (skip > _buffer.Length)
                        skip = _buffer.Length;
                  
                  byte[] newBuffer = new byte[_buffer.Length - skip];

                  if (newBuffer.Length > 0)
                        Array.Copy (_buffer, skip, newBuffer, 0, newBuffer.Length);

                  _buffer = newBuffer;
            }

            private int GetCrlfIndex ()
            {
                  byte prev = 0;
                  
                  for (int i = 3 /*skip the command*/; i < _buffer.Length; i++)
                  {
                        byte b = _buffer[i];

                        if (b == _lf && prev == _cr)
                              return i - 1;

                        prev = b;
                  }

                  return -1;
            }

            private void AppendToBuffer (byte[] data, int length)
            {
                  byte[] newBuffer = new byte[_buffer.Length + length];

                  Array.Copy (_buffer, 0, newBuffer, 0, _buffer.Length);
                  Array.Copy (data, 0, newBuffer, _buffer.Length, length);

                  _buffer = newBuffer;
            }
            
            [CommandHandler]
            protected virtual void OnContentReceived (ContentCommand cmd)
            {
                  IMsnContent content = cmd.Content;
                  
                  if (content == null)
                  {
                        Log.Warn ("Unknown Content Type {0}", cmd.ContentType);
                        return;
                  }
                  
                  if (!_contentDelegates.ContainsKey (content.GetType ()))
                  {
                        Log.Warn ("Unexpected Content {0}", content);
                        return;
                  }
                  
                  // Make a local copy of the list in case any get removed by a handler
                  List<Delegate> tmp = new List<Delegate> ();
                  tmp.AddRange (_contentDelegates[content.GetType ()]);
                  
                  foreach (Delegate del in tmp)
                  {
                        try
                        {
                              del.DynamicInvoke (content);
                        }
                        catch (Exception ex)
                        {
                              Log.Error (ex, "Error Invoking {0} Handler {1}.{2}", content.GetType ().Name, del.Method.DeclaringType.Name, del.Method.Name);
                        }
                  }
            }
            
            public void AddCommandHandler (Type cmdType, Delegate handler)
            {
                  if (!_commandDelegates.ContainsKey (cmdType))
                        _commandDelegates.Add (cmdType, new List<Delegate> ());
                  
                  ThrowUtility.ThrowIfTrue ("Handler already present", _commandDelegates[cmdType].Contains (handler));
                  
                  _commandDelegates[cmdType].Add (handler);
            }

            public void AddCommandHandler<T> (CommandHandler<T> handler) where T : IMsnCommand
            {
                  AddCommandHandler (typeof (T), handler);
            }
            
            public void RemoveCommandHandler (Type cmdType, Delegate handler)
            {
                  ThrowUtility.ThrowIfFalse ("Handler not found", _commandDelegates.ContainsKey (cmdType));
                  ThrowUtility.ThrowIfFalse ("Handler not found", _commandDelegates[cmdType].Contains (handler));

                  _commandDelegates[cmdType].Remove (handler);
                  
                  if (_commandDelegates[cmdType].Count == 0)
                        _commandDelegates.Remove (cmdType);
            }
            
            public void RemoveCommandHandler<T> (CommandHandler<T> handler) where T : IMsnCommand
            {
                  RemoveCommandHandler (typeof (T), handler);
            }
            
            public void AddContentHandler (Type contentType, Delegate handler)
            {
                  if (!_contentDelegates.ContainsKey (contentType))
                        _contentDelegates.Add (contentType, new List<Delegate> ());
                  
                  ThrowUtility.ThrowIfTrue ("Handler already present", _contentDelegates[contentType].Contains (handler));

                  _contentDelegates[contentType].Add (handler);
            }

            public void AddContentHandler<T> (ContentHandler<T> handler) where T : IMsnContent
            {
                  AddContentHandler (typeof (T), handler);
            }
            
            public void RemoveContentHandler (Type contentType, Delegate handler)
            {
                  ThrowUtility.ThrowIfFalse ("Handler not found", _contentDelegates.ContainsKey (contentType));
                  ThrowUtility.ThrowIfFalse ("Handler not found", _contentDelegates[contentType].Contains (handler));

                  _contentDelegates[contentType].Remove (handler);
                  
                  if (_contentDelegates[contentType].Count == 0)
                        _contentDelegates.Remove (contentType);
            }
            
            public void RemoveContentHandler<T> (ContentHandler<T> handler) where T : IMsnContent
            {
                  RemoveContentHandler (typeof (T), handler);
            }
            
            public void ClearHandlers ()
            {
                  _commandDelegates.Clear ();
                  _contentDelegates.Clear ();
            }
            
            public void AutoAddHandlers ()
            {
                  foreach (MethodInfo method in this.GetType ().GetMethods (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
                  {
                        if (method.GetCustomAttributes (typeof (CommandHandlerAttribute), false).Length > 0)
                        {
                              Type cmdType = null;
                              try {
                                    cmdType = method.GetParameters ()[0].ParameterType;
                                    AddCommandHandler (cmdType, CreateDelegate (method, cmdType, typeof (CommandHandler)));
                              } catch (Exception e) {
                                    Log.Error (e, "Unable to create command handler (obj={0}, method={1}, type={2}).",
                                          this, method.Name, cmdType == null ? "null" : cmdType.FullName);
                              }
                        }
                        
                        if (method.GetCustomAttributes (typeof (ContentHandlerAttribute), false).Length > 0)
                        {
                              Type contentType = null;
                              try {
                                    contentType = method.GetParameters ()[0].ParameterType;
                                    AddContentHandler (contentType, CreateDelegate (method, contentType, typeof (ContentHandler)));
                              } catch (Exception e) {
                                    Log.Error (e, "Unable to create content handler (obj={0}, method={1}, type={2}).",
                                          this, method.Name, contentType == null ? "null" : contentType.FullName);
                              }
                        }
                  }
            }
            
            private Delegate CreateDelegate (MethodInfo method, Type paramType, Type delegateType)
            {
                  try
                  {
                        DynamicMethod dynm = new DynamicMethod (String.Empty, null,
                                                                new Type[] { typeof (object), typeof (object) },
                                                                                    method.DeclaringType);
                        
                        ILGenerator ilgen = dynm.GetILGenerator ();
                        
                        ilgen.Emit (OpCodes.Ldarg_0);
                        ilgen.Emit (OpCodes.Castclass, method.DeclaringType);
                        ilgen.Emit (OpCodes.Ldarg_1);
                        ilgen.Emit (OpCodes.Castclass, paramType);
                        
                        if (method.IsFinal)
                              ilgen.EmitCall (OpCodes.Call, method, null);
                        else
                              ilgen.EmitCall (OpCodes.Callvirt, method, null);
                        
                        ilgen.Emit (OpCodes.Ret);
                        
                        return dynm.CreateDelegate (delegateType, this);
                  }
                  catch (InvalidProgramException)
                  {
                        // This happens on mono <= 1.2.4
                        
                        return Delegate.CreateDelegate (delegateType, this, method);
                  }
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index