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

MsnP2PUtility.cs

/*
 * Galaxium Messenger
 * Copyright (C) 2007 Paul Burton <paulburton89@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.IO;
using System.Reflection;

using Anculus.Core;

namespace Galaxium.Protocol.Msn
{
      public delegate void AckHandler (P2PMessage ack);
      
      public static class MsnP2PUtility
      {
            struct P2PAppInfo
            {
                  public Type appType;
                  public IMsnP2PApplication app;
                  public Guid eufGuid;
                  public uint appId;
            }
            
            static List<P2PAppInfo> _apps = new List<P2PAppInfo> ();
            static List<MsnP2PSession> _sessions = new List<MsnP2PSession> ();
            
            static Dictionary<uint, MemoryStream> _messageStreams = new Dictionary<uint, MemoryStream> ();
            
            static Dictionary<uint, AckHandler> _ackHandlers = new Dictionary<uint, AckHandler> ();
            
            // Returns all non-session P2P applications
            public static IEnumerable<IMsnP2PApplication> Applications
            {
                  get
                  {
                        foreach (P2PAppInfo info in _apps)
                              if (info.app != null)
                                    yield return info.app;
                  }
            }
            
            public static IEnumerable<MsnP2PSession> Sessions
            {
                  get { return _sessions; }
            }
            
            static MsnP2PUtility ()
            {
                  try
                  {
                        AutoAddAssembly (Assembly.GetExecutingAssembly ());
                  }
                  catch (Exception ex)
                  {
                        Log.Error (ex, "Exception initializing MsnP2PUtility");
                  }
            }
            
            public static void AutoAddAssembly (Assembly asm)
            {
                  foreach (Type t in asm.GetTypes ())
                  {
                        if (t.GetCustomAttributes (typeof (MsnP2PApplicationAttribute), false).Length > 0)
                              AddType (t);
                  }
            }
            
            public static void AddType (Type t)
            {
                  foreach (MsnP2PApplicationAttribute att in t.GetCustomAttributes (typeof (MsnP2PApplicationAttribute), false))
                  {
                        P2PAppInfo appInfo = new P2PAppInfo ();
                        appInfo.appType = t;
                        appInfo.appId = att.AppID;
                        appInfo.eufGuid = att.EufGuid;
                        
                        if (t.GetInterface ("IMsnP2PSessionApplication") == null)
                              appInfo.app = Activator.CreateInstance (t) as IMsnP2PApplication;

                        _apps.Add (appInfo);
                  }
            }
            
            public static void Add (IMsnP2PSessionApplication app)
            {
                  MsnP2PSession session = new MsnP2PSession (app);
                  session.Closed += SessionClosed;
                  
                  _sessions.Add (session);
            }
            
            public static MsnP2PSession FindSession (P2PMessage msg)
            {
                  uint sessionID = msg.Header.SessionID;
                  
                  if ((sessionID == 0) && (msg.SLPMessage != null))
                  {
                        if (msg.SLPMessage.MIMEBody.ContainsKey ("SessionID"))
                        {
                              if (!uint.TryParse (msg.SLPMessage.MIMEBody["SessionID"].Value, out sessionID))
                              {
                                    Log.Warn ("Unable to parse SLP message SessionID");
                                    sessionID = 0;
                              }
                        }
                        
                        if (sessionID == 0)
                        {
                              // We don't get a session ID in BYE requests
                              // so we need to find the session by its call ID
                              
                              foreach (MsnP2PSession session in _sessions)
                              {
                                    if (session.Invite.CallID == msg.SLPMessage.CallID)
                                          return session;
                              }
                        }
                  }
                  
                  // Sometimes we only have a message ID to find the session with...
                  // e.g. the waiting (flag 4) messages wlm sends sometimes
                  if ((sessionID == 0) && (msg.Header.MessageID != 0))
                  {
                        foreach (MsnP2PSession session in _sessions)
                        {
                              uint expected = session.RemoteID + 1;
                              
                              if (expected == session.RemoteBaseID)
                                    expected++;
                              
                              //Log.Debug ("MessageID {0}, expected {1}", msg.Header.MessageID, expected);
                              
                              if (msg.Header.MessageID == expected)
                                    return session;
                        }
                  }
                  
                  if (sessionID == 0)
                        return null;
                  
                  foreach (MsnP2PSession session in _sessions)
                  {
                        if (session.SessionID == sessionID)
                              return session;
                  }
                  
                  return null;
            }
            
            public static IMsnP2PApplication FindApplication (P2PMessage msg)
            {
                  MsnP2PSession session = FindSession (msg);
                  
                  if (session != null)
                        return session.Application;
                  
                  if (msg.Footer != 0)
                  {
                        // If the message is intended for a non-session based P2P application
                        // (like P2P based ink) then we find the application by the appID in the footer
                        //TODO: should we also check the sessionID (which seems to be constant?)
                        
                        foreach (P2PAppInfo info in _apps)
                        {
                              if (info.app == null)
                                    continue;
                              
                              if (info.appId == msg.Footer)
                                    return info.app;
                        }
                  }
                  
                  return null;
            }
            
            public static void ProcessMessage (IMsnP2PBridge bridge, P2PMessage msg)
            {
                  if (HandleSplitMessage (ref msg))
                        return;
                  
                  if ((msg.SLPMessage != null) && (msg.SLPMessage.To != msg.Session.Account))
                  {
                        Log.Debug ("Received P2P message intended for {0}, not us\n{1}", msg.SLPMessage.To.UniqueIdentifier, msg);
                        
                        if (msg.SLPMessage.From.Local)
                              Log.Error ("We received a message from ourselves?");
                        else
                              SendStatus (bridge, msg, msg.SLPMessage.From, 404, "Not Found");
                        
                        return;
                  }
                  
                  if (msg.Header.AckUID != 0)
                  {
                        // This is an ack
                        
                        //TODO: is this the best way to detect acks?
                        // there's the Ack flag, but thats not set
                        // for the bye ack
                        
                        if (_ackHandlers.ContainsKey (msg.Header.AckUID))
                        {
                              // An AckHandler has been registered for this ack
                              
                              _ackHandlers[msg.Header.AckUID] (msg);
                              _ackHandlers.Remove (msg.Header.AckUID);
                        }
                        else
                              Log.Debug ("No AckHandler for ack {0}", msg.Header.AckUID);
                        
                        return;
                  }
                  
                  MsnP2PSession session = FindSession (msg);
                  
                  if (session != null)
                  {
                        if (!session.ProcessMessage (bridge, msg))
                        {
                              Log.Warn ("P2PSession {0} could not process message\n{1}", session.SessionID, msg);
                              SendStatus (bridge, msg, session.Remote, 500, "Internal Error");
                        }
                        
                        return;
                  }
                  
                  IMsnP2PApplication app = FindApplication (msg);
                  
                  if (app != null)
                  {
                        if (!app.ProcessMessage (bridge, msg))
                        {
                              Log.Warn ("P2PApp could not process message\n{0}", msg);
                              SendStatus (bridge, msg, app.Remote, 500, "Internal Error");
                        }
                        
                        return;
                  }
                  
                  if (msg.SLPMessage is SLPRequestMessage)
                  {
                        SLPRequestMessage req = msg.SLPMessage as SLPRequestMessage;
                        
                        if ((req.Method == "INVITE") && (req.ContentType == "application/x-msnmsgr-sessionreqbody"))
                        {
                              // Start a new session
                              
                              session = new MsnP2PSession (msg);
                              session.Closed += SessionClosed;
                              
                              lock (_sessions)
                                    _sessions.Add (session);
                              
                              return;
                        }
                  }
                  
                  if ((msg.Header.Flags & P2PHeaderFlag.Waiting) == P2PHeaderFlag.Waiting)
                  {
                        Log.Debug ("Received P2P waiting message");
                        return;
                  }
                  
                  // Nothing wants this message, but we will ack it anyway (if it's
                  // not an ACK itself) to (hopefully) keep the remote client happy
                  
                  if (msg.Header.AckUID == 0)
                        bridge.Send (msg.CreateAck ());
                  
                  if (msg.SLPMessage != null)
                        return;
                  
                  Log.Warn ("Unhandled P2P message!\n{0}", msg);
            }
            
            static void SessionClosed (object sender, EventArgs args)
            {
                  MsnP2PSession session = sender as MsnP2PSession;
                  
                  Log.Debug ("P2PSession {0} closed, removing", session.SessionID);
                  
                  lock (_sessions)
                        _sessions.Remove (session);
                  
                  session.Dispose ();
            }
            
            static void SendStatus (IMsnP2PBridge bridge, P2PMessage msg, IMsnEntity dest, int code, string phrase)
            {
                  SLPMessage slp = new SLPStatusMessage (dest, code, phrase);
                  
                  if (msg.SLPMessage != null)
                  {
                        slp.Branch = msg.SLPMessage.Branch;
                        slp.CallID = msg.SLPMessage.CallID;
                        slp.From = msg.SLPMessage.To;
                        slp.ContentType = msg.SLPMessage.ContentType;
                  }
                  else
                        slp.ContentType = "null";
                        
                  P2PMessage response = new P2PMessage (msg.Session);
                  response.SLPMessage = slp;
                        
                  bridge.Send (msg);
            }
            
            public static void RegisterAckHandler (uint ackID, AckHandler handler)
            {
                  _ackHandlers.Add (ackID, handler);
            }
            
            public static P2PMessage[] SplitMessage (P2PMessage msg, uint maxChunkSize)
            {
                  if (msg.Payload.Length <= maxChunkSize)
                        return new P2PMessage[] { msg };
                  
                  List<P2PMessage> chunks = new List<P2PMessage> ();
                  ulong offset = 0;
                  
                  while (offset < (ulong)msg.Payload.Length)
                  {
                        P2PMessage chunk = new P2PMessage (msg);
                        
                        uint chunkSize = Math.Min (maxChunkSize, (uint)((ulong)msg.Payload.Length - offset));
                        byte[] chunkPayload = new byte [chunkSize];
                        
                        Array.Copy (msg.Payload, (long)offset, chunkPayload, 0, chunkSize);
                        
                        chunk.Header.ChunkOffset = offset;
                        chunk.Header.ChunkSize = chunkSize;
                        chunk.Header.TotalSize = (ulong)msg.Payload.Length;
                        
                        chunk.Payload = chunkPayload;
                        
                        chunks.Add (chunk);
                        
                        offset += chunkSize;
                  }
                  
                  return chunks.ToArray ();
            }
            
            static bool HandleSplitMessage (ref P2PMessage msg)
            {
                  // If this is not a split message, return false
                  if (((msg.Header.Flags & P2PHeaderFlag.Data) == P2PHeaderFlag.Data) || (msg.Payload.Length == 0) || (msg.Header.ChunkSize == msg.Header.TotalSize))
                        return false;
                  
                  Log.Debug ("Caught split P2P message");
                  
                  if (!_messageStreams.ContainsKey (msg.Header.MessageID))
                        _messageStreams.Add (msg.Header.MessageID, new MemoryStream ());
                        
                  _messageStreams[msg.Header.MessageID].Write (msg.Payload, 0, msg.Payload.Length);
                  
                  if ((msg.Header.ChunkOffset + msg.Header.ChunkSize) >= msg.Header.TotalSize)
                  {
                        // We have the whole message
                        
                        msg.Header.ChunkOffset = 0;
                        msg.Header.ChunkSize = (uint)msg.Header.TotalSize;
                        
                        msg.Payload = _messageStreams[msg.Header.MessageID].ToArray ();
                        
                        _messageStreams[msg.Header.MessageID].Close ();
                        _messageStreams.Remove (msg.Header.MessageID);
                        
                        return false;
                  }
                  
                  return true;
            }
            
            internal static Guid GetEufGuid (IMsnP2PApplication app)
            {
                  foreach (P2PAppInfo info in _apps)
                  {
                        if (info.appType == app.GetType ())
                              return info.eufGuid;
                  }
                  
                  return new Guid ();
            }
            
            internal static uint GetAppID (IMsnP2PApplication app)
            {
                  foreach (P2PAppInfo info in _apps)
                  {
                        if (info.appType == app.GetType ())
                              return info.appId;
                  }
                  
                  return 0;
            }
            
            internal static Type GetApp (Guid eufGuid)
            {
                  foreach (P2PAppInfo info in _apps)
                  {
                        if (info.eufGuid == eufGuid)
                              return info.appType;
                  }
                  
                  return null;
            }
            
            internal static IMsnP2PApplication GetApp (uint appID)
            {
                  foreach (P2PAppInfo info in _apps)
                  {
                        if (info.app == null)
                              continue;
                        
                        if (info.appId == appID)
                              return info.app;
                  }
                  
                  return null;
            }
            
            internal static IMsnP2PApplication GetAppInstance (Type appType)
            {
                  foreach (P2PAppInfo info in _apps)
                  {
                        if (info.appType != appType)
                              continue;
                        
                        if (info.app != null)
                              return info.app;
                        
                        return null;
                  }
                  
                  return null;
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index