/* * Copyright (c) 2006-2014, openmetaverse.org * 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. * - Neither the name of the openmetaverse.org 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.Threading; using System.Collections; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Globalization; using System.IO; using OpenMetaverse.Packets; using OpenMetaverse.StructuredData; using OpenMetaverse.Interfaces; using OpenMetaverse.Messages.Linden; namespace OpenMetaverse { /// /// NetworkManager is responsible for managing the network layer of /// OpenMetaverse. It tracks all the server connections, serializes /// outgoing traffic and deserializes incoming traffic, and provides /// instances of delegates for network-related events. /// public partial class NetworkManager { #region Enums /// /// Explains why a simulator or the grid disconnected from us /// public enum DisconnectType { /// The client requested the logout or simulator disconnect ClientInitiated, /// The server notified us that it is disconnecting ServerInitiated, /// Either a socket was closed or network traffic timed out NetworkTimeout, /// The last active simulator shut down SimShutdown } #endregion Enums #region Structs /// /// Holds a simulator reference and a decoded packet, these structs are put in /// the packet inbox for event handling /// public struct IncomingPacket { /// Reference to the simulator that this packet came from public Simulator Simulator; /// Packet that needs to be processed public Packet Packet; public IncomingPacket(Simulator simulator, Packet packet) { Simulator = simulator; Packet = packet; } } /// /// Holds a simulator reference and a serialized packet, these structs are put in /// the packet outbox for sending /// public class OutgoingPacket { /// Reference to the simulator this packet is destined for public readonly Simulator Simulator; /// Packet that needs to be sent public readonly UDPPacketBuffer Buffer; /// Sequence number of the wrapped packet public uint SequenceNumber; /// Number of times this packet has been resent public int ResendCount; /// Environment.TickCount when this packet was last sent over the wire public int TickCount; /// Type of the packet public PacketType Type; public OutgoingPacket(Simulator simulator, UDPPacketBuffer buffer, PacketType type) { Simulator = simulator; Buffer = buffer; this.Type = type; } } #endregion Structs #region Delegates /// The event subscribers, null of no subscribers private EventHandler m_PacketSent; ///Raises the PacketSent Event /// A PacketSentEventArgs object containing /// the data sent from the simulator protected virtual void OnPacketSent(PacketSentEventArgs e) { EventHandler handler = m_PacketSent; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_PacketSentLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler PacketSent { add { lock (m_PacketSentLock) { m_PacketSent += value; } } remove { lock (m_PacketSentLock) { m_PacketSent -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_LoggedOut; ///Raises the LoggedOut Event /// A LoggedOutEventArgs object containing /// the data sent from the simulator protected virtual void OnLoggedOut(LoggedOutEventArgs e) { EventHandler handler = m_LoggedOut; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_LoggedOutLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler LoggedOut { add { lock (m_LoggedOutLock) { m_LoggedOut += value; } } remove { lock (m_LoggedOutLock) { m_LoggedOut -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_SimConnecting; ///Raises the SimConnecting Event /// A SimConnectingEventArgs object containing /// the data sent from the simulator protected virtual void OnSimConnecting(SimConnectingEventArgs e) { EventHandler handler = m_SimConnecting; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_SimConnectingLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler SimConnecting { add { lock (m_SimConnectingLock) { m_SimConnecting += value; } } remove { lock (m_SimConnectingLock) { m_SimConnecting -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_SimConnected; ///Raises the SimConnected Event /// A SimConnectedEventArgs object containing /// the data sent from the simulator protected virtual void OnSimConnected(SimConnectedEventArgs e) { EventHandler handler = m_SimConnected; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_SimConnectedLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler SimConnected { add { lock (m_SimConnectedLock) { m_SimConnected += value; } } remove { lock (m_SimConnectedLock) { m_SimConnected -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_SimDisconnected; ///Raises the SimDisconnected Event /// A SimDisconnectedEventArgs object containing /// the data sent from the simulator protected virtual void OnSimDisconnected(SimDisconnectedEventArgs e) { EventHandler handler = m_SimDisconnected; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_SimDisconnectedLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler SimDisconnected { add { lock (m_SimDisconnectedLock) { m_SimDisconnected += value; } } remove { lock (m_SimDisconnectedLock) { m_SimDisconnected -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_Disconnected; ///Raises the Disconnected Event /// A DisconnectedEventArgs object containing /// the data sent from the simulator protected virtual void OnDisconnected(DisconnectedEventArgs e) { EventHandler handler = m_Disconnected; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_DisconnectedLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler Disconnected { add { lock (m_DisconnectedLock) { m_Disconnected += value; } } remove { lock (m_DisconnectedLock) { m_Disconnected -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_SimChanged; ///Raises the SimChanged Event /// A SimChangedEventArgs object containing /// the data sent from the simulator protected virtual void OnSimChanged(SimChangedEventArgs e) { EventHandler handler = m_SimChanged; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_SimChangedLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler SimChanged { add { lock (m_SimChangedLock) { m_SimChanged += value; } } remove { lock (m_SimChangedLock) { m_SimChanged -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_EventQueueRunning; ///Raises the EventQueueRunning Event /// A EventQueueRunningEventArgs object containing /// the data sent from the simulator protected virtual void OnEventQueueRunning(EventQueueRunningEventArgs e) { EventHandler handler = m_EventQueueRunning; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_EventQueueRunningLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler EventQueueRunning { add { lock (m_EventQueueRunningLock) { m_EventQueueRunning += value; } } remove { lock (m_EventQueueRunningLock) { m_EventQueueRunning -= value; } } } #endregion Delegates #region Properties /// Unique identifier associated with our connections to /// simulators public uint CircuitCode { get { return _CircuitCode; } set { _CircuitCode = value; } } /// The simulator that the logged in avatar is currently /// occupying public Simulator CurrentSim { get { return _CurrentSim; } set { _CurrentSim = value; } } /// Shows whether the network layer is logged in to the /// grid or not public bool Connected { get { return connected; } } /// Number of packets in the incoming queue public int InboxCount { get { return PacketInbox.Count; } } /// Number of packets in the outgoing queue public int OutboxCount { get { return PacketOutbox.Count; } } #endregion Properties /// All of the simulators we are currently connected to public List Simulators = new List(); /// Handlers for incoming capability events internal CapsEventDictionary CapsEvents; /// Handlers for incoming packets internal PacketEventDictionary PacketEvents; /// Incoming packets that are awaiting handling internal BlockingQueue PacketInbox = new BlockingQueue(Settings.PACKET_INBOX_SIZE); /// Outgoing packets that are awaiting handling internal BlockingQueue PacketOutbox = new BlockingQueue(Settings.PACKET_INBOX_SIZE); private GridClient Client; private Timer DisconnectTimer; private uint _CircuitCode; private Simulator _CurrentSim = null; private bool connected = false; /// /// Default constructor /// /// Reference to the GridClient object public NetworkManager(GridClient client) { Client = client; PacketEvents = new PacketEventDictionary(client); CapsEvents = new CapsEventDictionary(client); // Register internal CAPS callbacks RegisterEventCallback("EnableSimulator", new Caps.EventQueueCallback(EnableSimulatorHandler)); // Register the internal callbacks RegisterCallback(PacketType.RegionHandshake, RegionHandshakeHandler); RegisterCallback(PacketType.StartPingCheck, StartPingCheckHandler, false); RegisterCallback(PacketType.DisableSimulator, DisableSimulatorHandler); RegisterCallback(PacketType.KickUser, KickUserHandler); RegisterCallback(PacketType.LogoutReply, LogoutReplyHandler); RegisterCallback(PacketType.CompletePingCheck, CompletePingCheckHandler, false); RegisterCallback(PacketType.SimStats, SimStatsHandler, false); // GLOBAL SETTING: Don't force Expect-100: Continue headers on HTTP POST calls ServicePointManager.Expect100Continue = false; } /// /// Register an event handler for a packet. This is a low level event /// interface and should only be used if you are doing something not /// supported in the library /// /// Packet type to trigger events for /// Callback to fire when a packet of this type /// is received public void RegisterCallback(PacketType type, EventHandler callback) { RegisterCallback(type, callback, true); } /// /// Register an event handler for a packet. This is a low level event /// interface and should only be used if you are doing something not /// supported in the library /// /// Packet type to trigger events for /// Callback to fire when a packet of this type /// is received /// True if the callback should be ran /// asynchronously. Only set this to false (synchronous for callbacks /// that will always complete quickly) /// If any callback for a packet type is marked as /// asynchronous, all callbacks for that packet type will be fired /// asynchronously public void RegisterCallback(PacketType type, EventHandler callback, bool isAsync) { PacketEvents.RegisterEvent(type, callback, isAsync); } /// /// Unregister an event handler for a packet. This is a low level event /// interface and should only be used if you are doing something not /// supported in the library /// /// Packet type this callback is registered with /// Callback to stop firing events for public void UnregisterCallback(PacketType type, EventHandler callback) { PacketEvents.UnregisterEvent(type, callback); } /// /// Register a CAPS event handler. This is a low level event interface /// and should only be used if you are doing something not supported in /// the library /// /// Name of the CAPS event to register a handler for /// Callback to fire when a CAPS event is received public void RegisterEventCallback(string capsEvent, Caps.EventQueueCallback callback) { CapsEvents.RegisterEvent(capsEvent, callback); } /// /// Unregister a CAPS event handler. This is a low level event interface /// and should only be used if you are doing something not supported in /// the library /// /// Name of the CAPS event this callback is /// registered with /// Callback to stop firing events for public void UnregisterEventCallback(string capsEvent, Caps.EventQueueCallback callback) { CapsEvents.UnregisterEvent(capsEvent, callback); } /// /// Send a packet to the simulator the avatar is currently occupying /// /// Packet to send public void SendPacket(Packet packet) { // try CurrentSim, however directly after login this will // be null, so if it is, we'll try to find the first simulator // we're connected to in order to send the packet. Simulator simulator = CurrentSim; if (simulator == null && Client.Network.Simulators.Count >= 1) { Logger.DebugLog("CurrentSim object was null, using first found connected simulator", Client); simulator = Client.Network.Simulators[0]; } if (simulator != null && simulator.Connected) { simulator.SendPacket(packet); } else { //throw new NotConnectedException("Packet received before simulator packet processing threads running, make certain you are completely logged in"); Logger.Log("Packet received before simulator packet processing threads running, make certain you are completely logged in.", Helpers.LogLevel.Error); } } /// /// Send a packet to a specified simulator /// /// Packet to send /// Simulator to send the packet to public void SendPacket(Packet packet, Simulator simulator) { if (simulator != null) { simulator.SendPacket(packet); } else { Logger.Log("Packet received before simulator packet processing threads running, make certain you are completely logged in", Helpers.LogLevel.Error); } } /// /// Connect to a simulator /// /// IP address to connect to /// Port to connect to /// Handle for this simulator, to identify its /// location in the grid /// Whether to set CurrentSim to this new /// connection, use this if the avatar is moving in to this simulator /// URL of the capabilities server to use for /// this sim connection /// A Simulator object on success, otherwise null public Simulator Connect(IPAddress ip, ushort port, ulong handle, bool setDefault, string seedcaps) { IPEndPoint endPoint = new IPEndPoint(ip, (int)port); return Connect(endPoint, handle, setDefault, seedcaps); } /// /// Connect to a simulator /// /// IP address and port to connect to /// Handle for this simulator, to identify its /// location in the grid /// Whether to set CurrentSim to this new /// connection, use this if the avatar is moving in to this simulator /// URL of the capabilities server to use for /// this sim connection /// A Simulator object on success, otherwise null public Simulator Connect(IPEndPoint endPoint, ulong handle, bool setDefault, string seedcaps) { Simulator simulator = FindSimulator(endPoint); if (simulator == null) { // We're not tracking this sim, create a new Simulator object simulator = new Simulator(Client, endPoint, handle); // Immediately add this simulator to the list of current sims. It will be removed if the // connection fails lock (Simulators) Simulators.Add(simulator); } if (!simulator.Connected) { if (!connected) { // Mark that we are connecting/connected to the grid // connected = true; // Open the queues in case this is a reconnect and they were shut down PacketInbox.Open(); PacketOutbox.Open(); // Start the packet decoding thread Thread decodeThread = new Thread(new ThreadStart(IncomingPacketHandler)); decodeThread.Name = "Incoming UDP packet dispatcher"; decodeThread.Start(); // Start the packet sending thread Thread sendThread = new Thread(new ThreadStart(OutgoingPacketHandler)); sendThread.Name = "Outgoing UDP packet dispatcher"; sendThread.Start(); } // raise the SimConnecting event and allow any event // subscribers to cancel the connection if (m_SimConnecting != null) { SimConnectingEventArgs args = new SimConnectingEventArgs(simulator); OnSimConnecting(args); if (args.Cancel) { // Callback is requesting that we abort this connection lock (Simulators) { Simulators.Remove(simulator); } return null; } } // Attempt to establish a connection to the simulator if (simulator.Connect(setDefault)) { if (DisconnectTimer == null) { // Start a timer that checks if we've been disconnected DisconnectTimer = new Timer(new TimerCallback(DisconnectTimer_Elapsed), null, Client.Settings.SIMULATOR_TIMEOUT, Client.Settings.SIMULATOR_TIMEOUT); } if (setDefault) { SetCurrentSim(simulator, seedcaps); } // Raise the SimConnected event if (m_SimConnected != null) { OnSimConnected(new SimConnectedEventArgs(simulator)); } // If enabled, send an AgentThrottle packet to the server to increase our bandwidth if (Client.Settings.SEND_AGENT_THROTTLE) { Client.Throttle.Set(simulator); } return simulator; } else { // Connection failed, remove this simulator from our list and destroy it lock (Simulators) { Simulators.Remove(simulator); } return null; } } else if (setDefault) { // Move in to this simulator simulator.handshakeComplete = false; simulator.UseCircuitCode(true); Client.Self.CompleteAgentMovement(simulator); // We're already connected to this server, but need to set it to the default SetCurrentSim(simulator, seedcaps); // Send an initial AgentUpdate to complete our movement in to the sim if (Client.Settings.SEND_AGENT_UPDATES) { Client.Self.Movement.SendUpdate(true, simulator); } return simulator; } else { // Already connected to this simulator and wasn't asked to set it as the default, // just return a reference to the existing object return simulator; } } /// /// Begins the non-blocking logout. Makes sure that the LoggedOut event is /// called even if the server does not send a logout reply, and Shutdown() /// is properly called. /// public void BeginLogout() { // Wait for a logout response (by way of the LoggedOut event. If the // response is received, shutdown will be fired in the callback. // Otherwise we fire it manually with a NetworkTimeout type after LOGOUT_TIMEOUT System.Timers.Timer timeout = new System.Timers.Timer(); EventHandler callback = delegate(object sender, LoggedOutEventArgs e) { Shutdown(DisconnectType.ClientInitiated); timeout.Stop(); }; LoggedOut += callback; timeout.Interval = Client.Settings.LOGOUT_TIMEOUT; timeout.Elapsed += delegate(object sender, System.Timers.ElapsedEventArgs e) { timeout.Stop(); Shutdown(DisconnectType.NetworkTimeout); OnLoggedOut(new LoggedOutEventArgs(new List())); }; timeout.Start(); // Send the packet requesting a clean logout RequestLogout(); LoggedOut -= callback; } /// /// Initiate a blocking logout request. This will return when the logout /// handshake has completed or when Settings.LOGOUT_TIMEOUT /// has expired and the network layer is manually shut down /// public void Logout() { AutoResetEvent logoutEvent = new AutoResetEvent(false); EventHandler callback = delegate(object sender, LoggedOutEventArgs e) { logoutEvent.Set(); }; LoggedOut += callback; // Send the packet requesting a clean logout RequestLogout(); // Wait for a logout response. If the response is received, shutdown // will be fired in the callback. Otherwise we fire it manually with // a NetworkTimeout type if (!logoutEvent.WaitOne(Client.Settings.LOGOUT_TIMEOUT, false)) Shutdown(DisconnectType.NetworkTimeout); LoggedOut -= callback; } /// /// Initiate the logout process. The Shutdown() function /// needs to be manually called. /// public void RequestLogout() { // No need to run the disconnect timer any more if (DisconnectTimer != null) { DisconnectTimer.Dispose(); DisconnectTimer = null; } // This will catch a Logout when the client is not logged in if (CurrentSim == null || !connected) { Logger.Log("Ignoring RequestLogout(), client is already logged out", Helpers.LogLevel.Warning, Client); return; } Logger.Log("Logging out", Helpers.LogLevel.Info, Client); // Send a logout request to the current sim LogoutRequestPacket logout = new LogoutRequestPacket(); logout.AgentData.AgentID = Client.Self.AgentID; logout.AgentData.SessionID = Client.Self.SessionID; SendPacket(logout); } /// /// Close a connection to the given simulator /// /// /// public void DisconnectSim(Simulator simulator, bool sendCloseCircuit) { if (simulator != null) { simulator.Disconnect(sendCloseCircuit); // Fire the SimDisconnected event if a handler is registered if (m_SimDisconnected != null) { OnSimDisconnected(new SimDisconnectedEventArgs(simulator, DisconnectType.NetworkTimeout)); } lock (Simulators) Simulators.Remove(simulator); if (Simulators.Count == 0) Shutdown(DisconnectType.SimShutdown); } else { Logger.Log("DisconnectSim() called with a null Simulator reference", Helpers.LogLevel.Warning, Client); } } /// /// Shutdown will disconnect all the sims except for the current sim /// first, and then kill the connection to CurrentSim. This should only /// be called if the logout process times out on RequestLogout /// /// Type of shutdown public void Shutdown(DisconnectType type) { Shutdown(type, type.ToString()); } /// /// Shutdown will disconnect all the sims except for the current sim /// first, and then kill the connection to CurrentSim. This should only /// be called if the logout process times out on RequestLogout /// /// Type of shutdown /// Shutdown message public void Shutdown(DisconnectType type, string message) { Logger.Log("NetworkManager shutdown initiated", Helpers.LogLevel.Info, Client); // Send a CloseCircuit packet to simulators if we are initiating the disconnect bool sendCloseCircuit = (type == DisconnectType.ClientInitiated || type == DisconnectType.NetworkTimeout); lock (Simulators) { // Disconnect all simulators except the current one for (int i = 0; i < Simulators.Count; i++) { if (Simulators[i] != null && Simulators[i] != CurrentSim) { Simulators[i].Disconnect(sendCloseCircuit); // Fire the SimDisconnected event if a handler is registered if (m_SimDisconnected != null) { OnSimDisconnected(new SimDisconnectedEventArgs(Simulators[i], type)); } } } Simulators.Clear(); } if (CurrentSim != null) { // Kill the connection to the curent simulator CurrentSim.Disconnect(sendCloseCircuit); // Fire the SimDisconnected event if a handler is registered if (m_SimDisconnected != null) { OnSimDisconnected(new SimDisconnectedEventArgs(CurrentSim, type)); } } // Clear out all of the packets that never had time to process PacketInbox.Close(); PacketOutbox.Close(); connected = false; // Fire the disconnected callback if (m_Disconnected != null) { OnDisconnected(new DisconnectedEventArgs(type, message)); } } /// /// Searches through the list of currently connected simulators to find /// one attached to the given IPEndPoint /// /// IPEndPoint of the Simulator to search for /// A Simulator reference on success, otherwise null public Simulator FindSimulator(IPEndPoint endPoint) { lock (Simulators) { for (int i = 0; i < Simulators.Count; i++) { if (Simulators[i].IPEndPoint.Equals(endPoint)) return Simulators[i]; } } return null; } internal void RaisePacketSentEvent(byte[] data, int bytesSent, Simulator simulator) { if (m_PacketSent != null) { OnPacketSent(new PacketSentEventArgs(data, bytesSent, simulator)); } } /// /// Fire an event when an event queue connects for capabilities /// /// Simulator the event queue is attached to internal void RaiseConnectedEvent(Simulator simulator) { if (m_EventQueueRunning != null) { OnEventQueueRunning(new EventQueueRunningEventArgs(simulator)); } } private void OutgoingPacketHandler() { OutgoingPacket outgoingPacket = null; Simulator simulator; // FIXME: This is kind of ridiculous. Port the HTB code from Simian over ASAP! System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); while (connected) { if (PacketOutbox.Dequeue(100, ref outgoingPacket)) { simulator = outgoingPacket.Simulator; // Very primitive rate limiting, keeps a fixed buffer of time between each packet stopwatch.Stop(); if (stopwatch.ElapsedMilliseconds < 10) { //Logger.DebugLog(String.Format("Rate limiting, last packet was {0}ms ago", ms)); Thread.Sleep(10 - (int)stopwatch.ElapsedMilliseconds); } simulator.SendPacketFinal(outgoingPacket); stopwatch.Start(); } } } private void IncomingPacketHandler() { IncomingPacket incomingPacket = new IncomingPacket(); Packet packet = null; Simulator simulator = null; while (connected) { // Reset packet to null for the check below packet = null; if (PacketInbox.Dequeue(100, ref incomingPacket)) { packet = incomingPacket.Packet; simulator = incomingPacket.Simulator; if (packet != null) { // Skip blacklisted packets if (UDPBlacklist.Contains(packet.Type.ToString())) { Logger.Log(String.Format("Discarding Blacklisted packet {0} from {1}", packet.Type, simulator.IPEndPoint), Helpers.LogLevel.Warning); return; } // Fire the callback(s), if any PacketEvents.RaiseEvent(packet.Type, packet, simulator); } } } } private void SetCurrentSim(Simulator simulator, string seedcaps) { if (simulator != CurrentSim) { Simulator oldSim = CurrentSim; lock (Simulators) CurrentSim = simulator; // CurrentSim is synchronized against Simulators simulator.SetSeedCaps(seedcaps); // If the current simulator changed fire the callback if (m_SimChanged != null && simulator != oldSim) { OnSimChanged(new SimChangedEventArgs(oldSim)); } } } #region Timers private void DisconnectTimer_Elapsed(object obj) { if (!connected || CurrentSim == null) { if (DisconnectTimer != null) { DisconnectTimer.Dispose(); DisconnectTimer = null; } connected = false; } else if (CurrentSim.DisconnectCandidate) { // The currently occupied simulator hasn't sent us any traffic in a while, shutdown Logger.Log("Network timeout for the current simulator (" + CurrentSim.ToString() + "), logging out", Helpers.LogLevel.Warning, Client); if (DisconnectTimer != null) { DisconnectTimer.Dispose(); DisconnectTimer = null; } connected = false; // Shutdown the network layer Shutdown(DisconnectType.NetworkTimeout); } else { // Mark the current simulator as potentially disconnected each time this timer fires. // If the timer is fired again before any packets are received, an actual disconnect // sequence will be triggered CurrentSim.DisconnectCandidate = true; } } #endregion Timers #region Packet Callbacks /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void LogoutReplyHandler(object sender, PacketReceivedEventArgs e) { LogoutReplyPacket logout = (LogoutReplyPacket)e.Packet; if ((logout.AgentData.SessionID == Client.Self.SessionID) && (logout.AgentData.AgentID == Client.Self.AgentID)) { Logger.DebugLog("Logout reply received", Client); // Deal with callbacks, if any if (m_LoggedOut != null) { List itemIDs = new List(); foreach (LogoutReplyPacket.InventoryDataBlock InventoryData in logout.InventoryData) { itemIDs.Add(InventoryData.ItemID); } OnLoggedOut(new LoggedOutEventArgs(itemIDs)); } // If we are receiving a LogoutReply packet assume this is a client initiated shutdown Shutdown(DisconnectType.ClientInitiated); } else { Logger.Log("Invalid Session or Agent ID received in Logout Reply... ignoring", Helpers.LogLevel.Warning, Client); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void StartPingCheckHandler(object sender, PacketReceivedEventArgs e) { StartPingCheckPacket incomingPing = (StartPingCheckPacket)e.Packet; CompletePingCheckPacket ping = new CompletePingCheckPacket(); ping.PingID.PingID = incomingPing.PingID.PingID; ping.Header.Reliable = false; // TODO: We can use OldestUnacked to correct transmission errors // I don't think that's right. As far as I can tell, the Viewer // only uses this to prune its duplicate-checking buffer. -bushing SendPacket(ping, e.Simulator); } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void CompletePingCheckHandler(object sender, PacketReceivedEventArgs e) { CompletePingCheckPacket pong = (CompletePingCheckPacket)e.Packet; //String retval = "Pong2: " + (Environment.TickCount - e.Simulator.Stats.LastPingSent); //if ((pong.PingID.PingID - e.Simulator.Stats.LastPingID + 1) != 0) // retval += " (gap of " + (pong.PingID.PingID - e.Simulator.Stats.LastPingID + 1) + ")"; e.Simulator.Stats.LastLag = Environment.TickCount - e.Simulator.Stats.LastPingSent; e.Simulator.Stats.ReceivedPongs++; // Client.Log(retval, Helpers.LogLevel.Info); } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void SimStatsHandler(object sender, PacketReceivedEventArgs e) { if (!Client.Settings.ENABLE_SIMSTATS) { return; } SimStatsPacket stats = (SimStatsPacket)e.Packet; for (int i = 0; i < stats.Stat.Length; i++) { SimStatsPacket.StatBlock s = stats.Stat[i]; switch (s.StatID) { case 0: e.Simulator.Stats.Dilation = s.StatValue; break; case 1: e.Simulator.Stats.FPS = Convert.ToInt32(s.StatValue); break; case 2: e.Simulator.Stats.PhysicsFPS = s.StatValue; break; case 3: e.Simulator.Stats.AgentUpdates = s.StatValue; break; case 4: e.Simulator.Stats.FrameTime = s.StatValue; break; case 5: e.Simulator.Stats.NetTime = s.StatValue; break; case 6: e.Simulator.Stats.OtherTime = s.StatValue; break; case 7: e.Simulator.Stats.PhysicsTime = s.StatValue; break; case 8: e.Simulator.Stats.AgentTime = s.StatValue; break; case 9: e.Simulator.Stats.ImageTime = s.StatValue; break; case 10: e.Simulator.Stats.ScriptTime = s.StatValue; break; case 11: e.Simulator.Stats.Objects = Convert.ToInt32(s.StatValue); break; case 12: e.Simulator.Stats.ScriptedObjects = Convert.ToInt32(s.StatValue); break; case 13: e.Simulator.Stats.Agents = Convert.ToInt32(s.StatValue); break; case 14: e.Simulator.Stats.ChildAgents = Convert.ToInt32(s.StatValue); break; case 15: e.Simulator.Stats.ActiveScripts = Convert.ToInt32(s.StatValue); break; case 16: e.Simulator.Stats.LSLIPS = Convert.ToInt32(s.StatValue); break; case 17: e.Simulator.Stats.INPPS = Convert.ToInt32(s.StatValue); break; case 18: e.Simulator.Stats.OUTPPS = Convert.ToInt32(s.StatValue); break; case 19: e.Simulator.Stats.PendingDownloads = Convert.ToInt32(s.StatValue); break; case 20: e.Simulator.Stats.PendingUploads = Convert.ToInt32(s.StatValue); break; case 21: e.Simulator.Stats.VirtualSize = Convert.ToInt32(s.StatValue); break; case 22: e.Simulator.Stats.ResidentSize = Convert.ToInt32(s.StatValue); break; case 23: e.Simulator.Stats.PendingLocalUploads = Convert.ToInt32(s.StatValue); break; case 24: e.Simulator.Stats.UnackedBytes = Convert.ToInt32(s.StatValue); break; } } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void RegionHandshakeHandler(object sender, PacketReceivedEventArgs e) { RegionHandshakePacket handshake = (RegionHandshakePacket)e.Packet; Simulator simulator = e.Simulator; e.Simulator.ID = handshake.RegionInfo.CacheID; simulator.IsEstateManager = handshake.RegionInfo.IsEstateManager; simulator.Name = Utils.BytesToString(handshake.RegionInfo.SimName); simulator.SimOwner = handshake.RegionInfo.SimOwner; simulator.TerrainBase0 = handshake.RegionInfo.TerrainBase0; simulator.TerrainBase1 = handshake.RegionInfo.TerrainBase1; simulator.TerrainBase2 = handshake.RegionInfo.TerrainBase2; simulator.TerrainBase3 = handshake.RegionInfo.TerrainBase3; simulator.TerrainDetail0 = handshake.RegionInfo.TerrainDetail0; simulator.TerrainDetail1 = handshake.RegionInfo.TerrainDetail1; simulator.TerrainDetail2 = handshake.RegionInfo.TerrainDetail2; simulator.TerrainDetail3 = handshake.RegionInfo.TerrainDetail3; simulator.TerrainHeightRange00 = handshake.RegionInfo.TerrainHeightRange00; simulator.TerrainHeightRange01 = handshake.RegionInfo.TerrainHeightRange01; simulator.TerrainHeightRange10 = handshake.RegionInfo.TerrainHeightRange10; simulator.TerrainHeightRange11 = handshake.RegionInfo.TerrainHeightRange11; simulator.TerrainStartHeight00 = handshake.RegionInfo.TerrainStartHeight00; simulator.TerrainStartHeight01 = handshake.RegionInfo.TerrainStartHeight01; simulator.TerrainStartHeight10 = handshake.RegionInfo.TerrainStartHeight10; simulator.TerrainStartHeight11 = handshake.RegionInfo.TerrainStartHeight11; simulator.WaterHeight = handshake.RegionInfo.WaterHeight; simulator.Flags = (RegionFlags)handshake.RegionInfo.RegionFlags; simulator.BillableFactor = handshake.RegionInfo.BillableFactor; simulator.Access = (SimAccess)handshake.RegionInfo.SimAccess; simulator.RegionID = handshake.RegionInfo2.RegionID; simulator.ColoLocation = Utils.BytesToString(handshake.RegionInfo3.ColoName); simulator.CPUClass = handshake.RegionInfo3.CPUClassID; simulator.CPURatio = handshake.RegionInfo3.CPURatio; simulator.ProductName = Utils.BytesToString(handshake.RegionInfo3.ProductName); simulator.ProductSku = Utils.BytesToString(handshake.RegionInfo3.ProductSKU); if (handshake.RegionInfo4 != null && handshake.RegionInfo4.Length > 0) { simulator.Protocols = (RegionProtocols)handshake.RegionInfo4[0].RegionProtocols; // Yes, overwrite region flags if we have extended version of them simulator.Flags = (RegionFlags)handshake.RegionInfo4[0].RegionFlagsExtended; } // Send a RegionHandshakeReply RegionHandshakeReplyPacket reply = new RegionHandshakeReplyPacket(); reply.AgentData.AgentID = Client.Self.AgentID; reply.AgentData.SessionID = Client.Self.SessionID; reply.RegionInfo.Flags = (uint)RegionProtocols.SelfAppearanceSupport; SendPacket(reply, simulator); // We're officially connected to this sim simulator.connected = true; simulator.handshakeComplete = true; simulator.ConnectedEvent.Set(); } protected void EnableSimulatorHandler(string capsKey, IMessage message, Simulator simulator) { if (!Client.Settings.MULTIPLE_SIMS) return; EnableSimulatorMessage msg = (EnableSimulatorMessage)message; for (int i = 0; i < msg.Simulators.Length; i++) { IPAddress ip = msg.Simulators[i].IP; ushort port = (ushort)msg.Simulators[i].Port; ulong handle = msg.Simulators[i].RegionHandle; IPEndPoint endPoint = new IPEndPoint(ip, port); if (FindSimulator(endPoint) != null) return; if (Connect(ip, port, handle, false, null) == null) { Logger.Log("Unabled to connect to new sim " + ip + ":" + port, Helpers.LogLevel.Error, Client); } } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void DisableSimulatorHandler(object sender, PacketReceivedEventArgs e) { DisconnectSim(e.Simulator, false); } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void KickUserHandler(object sender, PacketReceivedEventArgs e) { string message = Utils.BytesToString(((KickUserPacket)e.Packet).UserInfo.Reason); // Shutdown the network layer Shutdown(DisconnectType.ServerInitiated, message); } #endregion Packet Callbacks } #region EventArgs public class PacketReceivedEventArgs : EventArgs { private readonly Packet m_Packet; private readonly Simulator m_Simulator; public Packet Packet { get { return m_Packet; } } public Simulator Simulator { get { return m_Simulator; } } public PacketReceivedEventArgs(Packet packet, Simulator simulator) { this.m_Packet = packet; this.m_Simulator = simulator; } } public class LoggedOutEventArgs : EventArgs { private readonly List m_InventoryItems; public List InventoryItems; public LoggedOutEventArgs(List inventoryItems) { this.m_InventoryItems = inventoryItems; } } public class PacketSentEventArgs : EventArgs { private readonly byte[] m_Data; private readonly int m_SentBytes; private readonly Simulator m_Simulator; public byte[] Data { get { return m_Data; } } public int SentBytes { get { return m_SentBytes; } } public Simulator Simulator { get { return m_Simulator; } } public PacketSentEventArgs(byte[] data, int bytesSent, Simulator simulator) { this.m_Data = data; this.m_SentBytes = bytesSent; this.m_Simulator = simulator; } } public class SimConnectingEventArgs : EventArgs { private readonly Simulator m_Simulator; private bool m_Cancel; public Simulator Simulator { get { return m_Simulator; } } public bool Cancel { get { return m_Cancel; } set { m_Cancel = value; } } public SimConnectingEventArgs(Simulator simulator) { this.m_Simulator = simulator; this.m_Cancel = false; } } public class SimConnectedEventArgs : EventArgs { private readonly Simulator m_Simulator; public Simulator Simulator { get { return m_Simulator; } } public SimConnectedEventArgs(Simulator simulator) { this.m_Simulator = simulator; } } public class SimDisconnectedEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly NetworkManager.DisconnectType m_Reason; public Simulator Simulator { get { return m_Simulator; } } public NetworkManager.DisconnectType Reason { get { return m_Reason; } } public SimDisconnectedEventArgs(Simulator simulator, NetworkManager.DisconnectType reason) { this.m_Simulator = simulator; this.m_Reason = reason; } } public class DisconnectedEventArgs : EventArgs { private readonly NetworkManager.DisconnectType m_Reason; private readonly String m_Message; public NetworkManager.DisconnectType Reason { get { return m_Reason; } } public String Message { get { return m_Message; } } public DisconnectedEventArgs(NetworkManager.DisconnectType reason, String message) { this.m_Reason = reason; this.m_Message = message; } } public class SimChangedEventArgs : EventArgs { private readonly Simulator m_PreviousSimulator; public Simulator PreviousSimulator { get { return m_PreviousSimulator; } } public SimChangedEventArgs(Simulator previousSimulator) { this.m_PreviousSimulator = previousSimulator; } } public class EventQueueRunningEventArgs : EventArgs { private readonly Simulator m_Simulator; public Simulator Simulator { get { return m_Simulator; } } public EventQueueRunningEventArgs(Simulator simulator) { this.m_Simulator = simulator; } } #endregion }