/*
* 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
}