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

MsnP2PSession.cs

/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2008 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 Anculus.Core;

namespace Galaxium.Protocol.Msn
{
      public enum MsnP2PSessionState { Error, WaitingForLocal, WaitingForRemote, Active, Closing, Closed }

      public class MsnP2PSession : IDisposable
      {
            public event EventHandler Activated;
            public event EventHandler Closing;
            public event EventHandler Closed;
            public event EventHandler Error;
            public event EventHandler Waiting;
            
            IMsnP2PSessionApplication _app;
            IMsnP2PBridge _bridge;
            SLPRequestMessage _invite;
            MsnAccount _local;
            MsnContact _remote;
            MsnSession _session;
            MsnP2PSessionState _state = MsnP2PSessionState.Closed;
            uint _sessionID;
            uint _localID;
            uint _localBaseID;
            uint _remoteID;
            uint _remoteBaseID;
            
            Queue<P2PMessage> _outQueue = new Queue<P2PMessage> ();
            
            static Random _random = new Random ();

            public IMsnP2PApplication Application
            {
                  get { return _app; }
            }
            
            public IMsnP2PBridge Bridge
            {
                  get { return _bridge; }
            }
            
            public SLPRequestMessage Invite
            {
                  get { return _invite; }
            }
            
            public MsnAccount Local
            {
                  get { return _local; }
            }
            
            public MsnContact Remote
            {
                  get { return _remote; }
            }
            
            public uint LocalID
            {
                  get { return _localID; }
                  set { _localID = value; }
            }
            
            public uint RemoteID
            {
                  get { return _remoteID; }
            }
            
            public uint LocalBaseID
            {
                  get { return _localBaseID; }
            }
            
            public uint RemoteBaseID
            {
                  get { return _remoteBaseID; }
            }
            
            public MsnSession Session
            {
                  get { return _session; }
            }
            
            public uint SessionID
            {
                  get { return _sessionID; }
            }
            
            public MsnP2PSessionState State
            {
                  get { return _state; }
            }

            public MsnP2PSession (IMsnP2PSessionApplication app)
            {
                  _app = app;
                  _local = app.Local;
                  _remote = app.Remote;
                  _session = _local.Session;
                  _sessionID = (uint)_random.Next (10000, int.MaxValue);
                  
                  _localBaseID = (uint)_random.Next (10000, int.MaxValue);
                  _localID = _localBaseID;
                  
                  _app.P2PSession = this;
                  _app.Complete += AppComplete;
                  
                  Log.Debug ("P2PSession {0} created (Initiated locally)", _sessionID);
                  
                  _invite = new SLPRequestMessage (_remote, "INVITE");
                  _invite.ContentType = "application/x-msnmsgr-sessionreqbody";
                  _invite.MIMEBody["EUF-GUID"] = MsnP2PUtility.GetEufGuid (_app).ToString ("B").ToUpperInvariant ();
                  _invite.MIMEBody["SessionID"] = _sessionID.ToString ();
                  _invite.MIMEBody["AppID"] = _app.AppID.ToString ();
                  _invite.MIMEBody["Context"] = _app.CreateInviteContext ();
                  
                  _state = MsnP2PSessionState.WaitingForRemote;
                  Log.Debug ("P2PSession {0} state {1}", _sessionID, _state);
                  
                  OnWaiting ();
                  
                  Send (_invite, delegate (P2PMessage ack)
                  {
                        _remoteBaseID = ack.Header.MessageID;
                        _remoteID = _remoteBaseID;
                  });
            }
            
            public MsnP2PSession (P2PMessage invite)
            {
                  _invite = invite.SLPMessage as SLPRequestMessage;
                  
                  _session = _invite.Session;
                  _local = _invite.To as MsnAccount;
                  _remote = _invite.From as MsnContact;
                  
                  _localBaseID = (uint)_random.Next (10000, int.MaxValue);
                  _localID = _localBaseID;
                  _remoteBaseID = invite.Header.MessageID;
                  _remoteID = _remoteBaseID;
                  
                  if (!uint.TryParse (_invite.MIMEBody["SessionID"].Value, out _sessionID))
                        Log.Warn ("Unable to parse invite SessionID");
                  
                  Log.Debug ("P2PSession {0} created (Initiated remotely)", _sessionID);
                  
                  // Send Base ID
                  
                  Send (invite.CreateAck ());
                  
                  // Initialize P2P Application
                  
                  Type appType = MsnP2PUtility.GetApp (new Guid (_invite.MIMEBody["EUF-GUID"].Value));
                  
                  if (appType != null)
                        _app = Activator.CreateInstance (appType, this) as IMsnP2PSessionApplication;
                  else
                  {
                        Log.Warn ("Unknown app for EUF-GUID {0}", _invite.MIMEBody["EUF-GUID"].Value);
                        Log.Debug ("Invite:\n{0}", invite);
                  }
                  
                  if (!_app.CheckInvite (_invite))
                  {
                        Log.Warn ("P2PSession {0} app rejects invite\n{1}", _sessionID, invite);
                        
                        OnError ();
                        
                        SLPStatusMessage slp = new SLPStatusMessage (_remote, 500, "Internal Server Error");
                        slp.Branch = _invite.Branch;
                        slp.CallID = _invite.CallID;
                        slp.ContentType = "application/x-msnmsgr-sessionreqbody";
                        slp.MIMEBody["SessionID"] = _sessionID.ToString ();
                        
                        Send (slp, delegate
                        {
                              Close ();
                        });
                        
                        return;
                  }
                  
                  _app.Complete += AppComplete;
                  
                  _state = MsnP2PSessionState.WaitingForLocal;
                  Log.Debug ("P2PSession {0} state {1}", _sessionID, _state);
                  
                  if (_app.AutoAccept)
                        Accept ();
                  else
                        OnWaiting ();
            }
            
            public void Dispose ()
            {
                  DisposeApp ();
                  RemoveBridgeHandlers ();
                  _bridge = null;
            }
            
            void AppComplete (object sender, EventArgs args)
            {
                  DisposeApp ();
            }
            
            public void Accept ()
            {
                  if (_state != MsnP2PSessionState.WaitingForLocal)
                  {
                        Log.Warn ("Accept called, but we're not waiting for the local client (State {0})", _state);
                        return;
                  }
                  
                  Log.Debug ("P2PSession {0} accepted", _sessionID);
                  
                  SLPStatusMessage slp = new SLPStatusMessage (_remote, 200, "OK");
                  slp.Branch = _invite.Branch;
                  slp.CallID = _invite.CallID;
                  slp.ContentType = "application/x-msnmsgr-sessionreqbody";
                  slp.MIMEBody["SessionID"] = _sessionID.ToString ();
                  
                  Send (slp, delegate
                  {
                        OnActive ();
                        
                        if (_app != null)
                              _app.Begin ();
                        else
                              Log.Warn ("Unable to begin p2p application, object is null!");
                  });
            }
            
            public void Decline ()
            {
                  if (_state != MsnP2PSessionState.WaitingForLocal)
                  {
                        Log.Warn ("Declined called, but we're not waiting for the local client");
                        return;
                  }
                  
                  Log.Debug ("P2PSession {0} declined", _sessionID);
                  
                  SLPStatusMessage slp = new SLPStatusMessage (_remote, 603, "Decline");
                  slp.Branch = _invite.Branch;
                  slp.CallID = _invite.CallID;
                  slp.ContentType = "application/x-msnmsgr-sessionreqbody";
                  slp.MIMEBody["SessionID"] = _sessionID.ToString ();
                  
                  Send (slp, delegate
                  {
                        Close ();
                  });
            }
            
            public void Close ()
            {
                  Log.Debug ("P2PSession {0} closing", _sessionID);
                  
                  OnClosing ();
                  
                  SLPMessage slp = new SLPRequestMessage (_remote, "BYE");
                  slp.CallID = _invite.CallID;
                  slp.ContentType = "application/x-msnmsgr-sessionclosebody";
                  slp.MIMEBody["SessionID"] = _sessionID.ToString ();
                  
                  P2PMessage msg = new P2PMessage (_session);
                  msg.SLPMessage = slp;
                  
                  Send (msg, delegate
                  {
                        Log.Debug ("P2PSession {0} closed", _sessionID);
                        
                        OnClosed ();
                  });
            }
            
            public bool ProcessMessage (IMsnP2PBridge bridge, P2PMessage msg)
            {
                  _remoteID = msg.Header.MessageID;
                  
                  if ((msg.Header.Flags & P2PHeaderFlag.Waiting) == P2PHeaderFlag.Waiting)
                  {
                        //TODO: what should we do with these?
                        return true;
                  }
                  
                  if ((_state == MsnP2PSessionState.Closed) || (_state == MsnP2PSessionState.Closing) || (_state == MsnP2PSessionState.Error))
                  {
                        Log.Warn ("P2PSession {0} received message whilst in '{1}' state", _sessionID, _state);
                        return false;
                  }
                  
                  if (msg.SLPMessage != null)
                  {
                        if (msg.SLPMessage is SLPRequestMessage)
                        {
                              SLPRequestMessage req = msg.SLPMessage as SLPRequestMessage;
                              
                              if ((req.ContentType == "application/x-msnmsgr-sessionclosebody") && (req.Method == "BYE"))
                              {
                                    P2PMessage byeAck = msg.CreateAck ();
                                    byeAck.Header.Flags = P2PHeaderFlag.CloseSession;
                                    
                                    Send (byeAck);
                                    
                                    OnClosed ();
                                    
                                    return true;
                              }
                              else if (req.ContentType == "application/x-msnmsgr-transreqbody")
                              {
                                    // Direct connection invite
                                    
                                    Send (msg.CreateAck ());
                                    
                                    return true;
                              }
                        }
                        else if (msg.SLPMessage is SLPStatusMessage)
                        {
                              SLPStatusMessage status = msg.SLPMessage as SLPStatusMessage;
                              
                              Send (msg.CreateAck ());
                              
                              if (status.Code == 200) // OK
                              {
                                    OnActive ();
                                    
                                    _app.Begin ();
                                    
                                    return true;
                              }
                              else if (status.Code == 603) // Decline
                              {
                                    OnClosed ();
                                    
                                    return true;
                              }
                              else
                              {
                                    OnError ();
                                    
                                    return true;
                              }
                        }
                  }
                  
                  if (_app == null)
                  {
                        Log.Warn ("P2PSession {0}: Received message for P2P app, but it's either been disposed or not created", _sessionID);
                        return false;
                  }
                  
                  return _app.ProcessMessage (bridge, msg);
            }
            
            public void Send (P2PMessage msg, AckHandler ackHandler)
            {
                  msg.Header.MessageID = NextID ();
                  
                  P2PMessage[] msgs = MsnP2PUtility.SplitMessage (msg, _bridge != null ? _bridge.MaxDataSize : 1150);
                  
                  lock (_outQueue)
                  {
                        foreach (P2PMessage m in msgs)
                              _outQueue.Enqueue (m);
                  }
                  
                  if (ackHandler != null)
                        MsnP2PUtility.RegisterAckHandler (msg.Header.AckID, ackHandler);
                  
                  ProcessOutQueue (false);
            }
            
            public void Send (P2PMessage msg)
            {
                  Send (msg, null);
            }
            
            public void Send (SLPMessage slp, AckHandler ackHandler)
            {
                  P2PMessage msg = new P2PMessage (_session);
                  msg.SLPMessage = slp;
                  
                  Send (msg, ackHandler);
            }
            
            public void Send (SLPMessage slp)
            {
                  Send (slp, null);
            }
            
            void ProcessOutQueue (bool forceReady)
            {
                  if (_outQueue.Count == 0)
                  {
                        //Log.Debug ("Out queue is empty");
                        return;
                  }
                  
                  if (_bridge == null)
                  {
                        //Log.Debug ("Calling GETSWITCHBOARD from the MsnP2PSession");
                        
                        _bridge = _session.GetSwitchboard (false, _remote);
                        AddBridgeHandlers ();
                  }
                  
                  if (!(_bridge.Ready || forceReady))
                  {
                        //Log.Debug ("Bridge not ready");
                        return;
                  }
                  
                  lock (_outQueue)
                        _bridge.Send (_outQueue.Dequeue ());
            }
            
            void AddBridgeHandlers ()
            {
                  _bridge.BridgeReady += BridgeReady;
                  _bridge.BridgeClosed += BridgeClosed;
                  (_bridge as SBConnection).ContactLeft += BridgeContactLeft;
            }
            
            void RemoveBridgeHandlers ()
            {
                  if (_bridge == null)
                        return;
                  
                  _bridge.BridgeReady -= BridgeReady;
                  _bridge.BridgeClosed -= BridgeClosed;
                  (_bridge as SBConnection).ContactLeft -= BridgeContactLeft;
            }
            
            void BridgeReady (object sender, EventArgs args)
            {
                  //Log.Debug ("P2PSession {0} bridge ready, {1} messages queued", _sessionID, _outQueue.Count);
                  ProcessOutQueue (true);
            }
            
            void BridgeClosed (object sender, EventArgs args)
            {
                  RemoveBridgeHandlers ();
                  
                  _bridge = null;
                  
                  if (_outQueue.Count > 0)
                        ProcessOutQueue (false);
            }
            
            void BridgeContactLeft (object sender, ContactEventArgs args)
            {
                  RemoveBridgeHandlers ();
                  
                  _bridge = null;
                  
                  OnClosed ();
            }
            
            public uint NextID ()
            {
                  if (_localID == _localBaseID)
                        _localID++;
                  
                  return _localID++;
            }
            
            protected virtual void OnClosing ()
            {
                  _state = MsnP2PSessionState.Closing;
                  Log.Debug ("P2PSession {0} state {1}", _sessionID, _state);
                  
                  if (Closing != null)
                        Closing (this, EventArgs.Empty);
                  
                  DisposeApp ();
            }
            
            protected virtual void OnClosed ()
            {
                  _state = MsnP2PSessionState.Closed;
                  Log.Debug ("P2PSession {0} state {1}", _sessionID, _state);
                  
                  if (Closed != null)
                        Closed (this, EventArgs.Empty);
                  
                  DisposeApp ();
            }
            
            protected virtual void OnError ()
            {
                  _state = MsnP2PSessionState.Error;
                  Log.Debug ("P2PSession {0} state {1}", _sessionID, _state);
                  
                  if (Error != null)
                        Error (this, EventArgs.Empty);
                  
                  DisposeApp ();
            }
            
            protected virtual void OnWaiting ()
            {
                  if (Waiting != null)
                        Waiting (this, EventArgs.Empty);
            }
            
            protected virtual void OnActive ()
            {
                  _state = MsnP2PSessionState.Active;
                  Log.Debug ("P2PSession {0} state {1}", _sessionID, _state);
                  
                  if (Activated != null)
                        Activated (this, EventArgs.Empty);
            }
            
            void DisposeApp ()
            {
                  if (_app != null)
                  {
                        _app.Dispose ();
                        _app = null;
                  }
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index