/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* 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.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSim Project 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 DEVELOPERS ``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 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.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;
using System.Timers;
using libsecondlife;
using libsecondlife.Packets;
using OpenSim.Framework;
using OpenSim.Framework.Communications.Cache;
using OpenSim.Framework.Console;
using Timer=System.Timers.Timer;
namespace OpenSim.Region.ClientStack
{
public delegate bool PacketMethod(IClientAPI simClient, Packet packet);
///
/// Handles new client connections
/// Constructor takes a single Packet and authenticates everything
///
public partial class ClientView : IClientAPI
{
public static TerrainManager TerrainManager;
protected static Dictionary PacketHandlers =
new Dictionary(); //Global/static handlers for all clients
protected Dictionary m_packetHandlers = new Dictionary();
//local handlers for this instance
private LLUUID m_sessionId;
public LLUUID SecureSessionID = LLUUID.Zero;
public string firstName;
public string lastName;
private UseCircuitCodePacket cirpack;
public Thread ClientThread;
public LLVector3 startpos;
//private AgentAssetUpload UploadAssets;
private LLUUID newAssetFolder = LLUUID.Zero;
private int debug = 0;
protected IScene m_scene;
public IScene Scene
{
get { return m_scene; }
}
private ClientManager m_clientManager;
private AssetCache m_assetCache;
// private InventoryCache m_inventoryCache;
private int cachedtextureserial = 0;
protected AgentCircuitManager m_authenticateSessionsHandler;
private Encoding enc = Encoding.ASCII;
// Dead client detection vars
private Timer clientPingTimer;
private int packetsReceived = 0;
private int probesWithNoIngressPackets = 0;
private int lastPacketsReceived = 0;
// 1536000
private int throttleOutboundMax = 1536000; // Number of bytes allowed to go out per second. (256kbps per client)
// TODO: Make this variable. Lower throttle on un-ack. Raise over time?
private int throttleSentPeriod = 0; // Number of bytes sent this period
private int throttleOutbound = 162144; // Number of bytes allowed to go out per second. (256kbps per client)
// TODO: Make this variable. Lower throttle on un-ack. Raise over time
// All throttle times and number of bytes are calculated by dividing by this value
// This value also determines how many times per throttletimems the timer will run
// If throttleimems is 1000 ms, then the timer will fire every 1000/7 milliseconds
private int throttleTimeDivisor = 7;
private int throttletimems = 1000;
// Maximum -per type- throttle
private int ResendthrottleMAX = 100000;
private int LandthrottleMax = 100000;
private int WindthrottleMax = 100000;
private int CloudthrottleMax = 100000;
private int TaskthrottleMax = 800000;
private int AssetthrottleMax = 800000;
private int TexturethrottleMax = 800000;
// Minimum -per type- throttle
private int ResendthrottleMin = 5000; // setting resendmin to 0 results in mostly dropped packets
private int LandthrottleMin = 1000;
private int WindthrottleMin = 1000;
private int CloudthrottleMin = 1000;
private int TaskthrottleMin = 1000;
private int AssetthrottleMin = 1000;
private int TexturethrottleMin = 1000;
// Sim default per-client settings.
private int ResendthrottleOutbound = 50000;
private int ResendthrottleSentPeriod = 0;
private int LandthrottleOutbound = 100000;
private int LandthrottleSentPeriod = 0;
private int WindthrottleOutbound = 10000;
private int WindthrottleSentPeriod = 0;
private int CloudthrottleOutbound = 5000;
private int CloudthrottleSentPeriod = 0;
private int TaskthrottleOutbound = 100000;
private int TaskthrottleSentPeriod = 0;
private int AssetthrottleOutbound = 80000;
private int AssetthrottleSentPeriod = 0;
private int TexturethrottleOutbound = 100000;
private int TexturethrottleSentPeriod = 0;
private Timer throttleTimer;
public ClientView(EndPoint remoteEP, UseCircuitCodePacket initialcirpack, ClientManager clientManager,
IScene scene, AssetCache assetCache, PacketServer packServer,
AgentCircuitManager authenSessions)
{
m_moneyBalance = 1000;
m_scene = scene;
m_clientManager = clientManager;
m_assetCache = assetCache;
m_networkServer = packServer;
// m_inventoryCache = inventoryCache;
m_authenticateSessionsHandler = authenSessions;
MainLog.Instance.Verbose("CLIENT", "Started up new client thread to handle incoming request");
cirpack = initialcirpack;
userEP = remoteEP;
startpos = m_authenticateSessionsHandler.GetPosition(initialcirpack.CircuitCode.Code);
// While working on this, the BlockingQueue had me fooled for a bit.
// The Blocking queue causes the thread to stop until there's something
// in it to process. it's an on-purpose threadlock though because
// without it, the clientloop will suck up all sim resources.
PacketQueue = new BlockingQueue();
IncomingPacketQueue = new Queue();
OutgoingPacketQueue = new Queue();
ResendOutgoingPacketQueue = new Queue();
LandOutgoingPacketQueue = new Queue();
WindOutgoingPacketQueue = new Queue();
CloudOutgoingPacketQueue = new Queue();
TaskOutgoingPacketQueue = new Queue();
TextureOutgoingPacketQueue = new Queue();
AssetOutgoingPacketQueue = new Queue();
//this.UploadAssets = new AgentAssetUpload(this, m_assetCache, m_inventoryCache);
AckTimer = new Timer(750);
AckTimer.Elapsed += new ElapsedEventHandler(AckTimer_Elapsed);
AckTimer.Start();
throttleTimer = new Timer((int)(throttletimems/throttleTimeDivisor));
throttleTimer.Elapsed += new ElapsedEventHandler(throttleTimer_Elapsed);
throttleTimer.Start();
RegisterLocalPacketHandlers();
ClientThread = new Thread(new ThreadStart(AuthUser));
ClientThread.IsBackground = true;
ClientThread.Start();
}
void throttleTimer_Elapsed(object sender, ElapsedEventArgs e)
{
throttleSentPeriod = 0;
ResendthrottleSentPeriod = 0;
LandthrottleSentPeriod = 0;
WindthrottleSentPeriod = 0;
CloudthrottleSentPeriod = 0;
TaskthrottleSentPeriod = 0;
AssetthrottleSentPeriod = 0;
TexturethrottleSentPeriod = 0;
// I was considering this.. Will an event fire if the thread it's on is blocked?
// Then I figured out.. it doesn't really matter.. because this thread won't be blocked for long
// The General overhead of the UDP protocol gets sent to the queue un-throttled by this
// so This'll pick up about around the right time.
int MaxThrottleLoops = 4550; // 50*7 packets can be dequeued at once.
int throttleLoops = 0;
// We're going to dequeue all of the saved up packets until
// we've hit the throttle limit or there's no more packets to send
while ((throttleSentPeriod <= ((int)(throttleOutbound/throttleTimeDivisor)) &&
(ResendOutgoingPacketQueue.Count > 0 ||
LandOutgoingPacketQueue.Count > 0 ||
WindOutgoingPacketQueue.Count > 0 ||
CloudOutgoingPacketQueue.Count > 0 ||
TaskOutgoingPacketQueue.Count > 0 ||
AssetOutgoingPacketQueue.Count > 0 ||
TextureOutgoingPacketQueue.Count > 0)) && throttleLoops <= MaxThrottleLoops)
{
throttleLoops++;
//Now comes the fun part.. we dump all our elements into PacketQueue that we've saved up.
if (ResendthrottleSentPeriod <= ((int)(ResendthrottleOutbound/throttleTimeDivisor)) && ResendOutgoingPacketQueue.Count > 0)
{
QueItem qpack = ResendOutgoingPacketQueue.Dequeue();
PacketQueue.Enqueue(qpack);
throttleSentPeriod += qpack.Packet.ToBytes().Length;
ResendthrottleSentPeriod += qpack.Packet.ToBytes().Length;
}
if (LandthrottleSentPeriod <= ((int)(LandthrottleOutbound/throttleTimeDivisor)) && LandOutgoingPacketQueue.Count > 0)
{
QueItem qpack = LandOutgoingPacketQueue.Dequeue();
PacketQueue.Enqueue(qpack);
throttleSentPeriod += qpack.Packet.ToBytes().Length;
LandthrottleSentPeriod += qpack.Packet.ToBytes().Length;
}
if (WindthrottleSentPeriod <= ((int)(WindthrottleOutbound/throttleTimeDivisor)) && WindOutgoingPacketQueue.Count > 0)
{
QueItem qpack = WindOutgoingPacketQueue.Dequeue();
PacketQueue.Enqueue(qpack);
throttleSentPeriod += qpack.Packet.ToBytes().Length;
WindthrottleSentPeriod += qpack.Packet.ToBytes().Length;
}
if (CloudthrottleSentPeriod <= ((int)(CloudthrottleOutbound/throttleTimeDivisor)) && CloudOutgoingPacketQueue.Count > 0)
{
QueItem qpack = CloudOutgoingPacketQueue.Dequeue();
PacketQueue.Enqueue(qpack);
throttleSentPeriod += qpack.Packet.ToBytes().Length;
CloudthrottleSentPeriod += qpack.Packet.ToBytes().Length;
}
if (TaskthrottleSentPeriod <= ((int)(TaskthrottleOutbound/throttleTimeDivisor)) && TaskOutgoingPacketQueue.Count > 0)
{
QueItem qpack = TaskOutgoingPacketQueue.Dequeue();
PacketQueue.Enqueue(qpack);
throttleSentPeriod += qpack.Packet.ToBytes().Length;
TaskthrottleSentPeriod += qpack.Packet.ToBytes().Length;
}
if (TexturethrottleSentPeriod <= ((int)(TexturethrottleOutbound/throttleTimeDivisor)) && TextureOutgoingPacketQueue.Count > 0)
{
QueItem qpack = TextureOutgoingPacketQueue.Dequeue();
PacketQueue.Enqueue(qpack);
throttleSentPeriod += qpack.Packet.ToBytes().Length;
TexturethrottleSentPeriod += qpack.Packet.ToBytes().Length;
}
if (AssetthrottleSentPeriod <= ((int)(AssetthrottleOutbound/throttleTimeDivisor)) && AssetOutgoingPacketQueue.Count > 0)
{
QueItem qpack = AssetOutgoingPacketQueue.Dequeue();
PacketQueue.Enqueue(qpack);
throttleSentPeriod += qpack.Packet.ToBytes().Length;
AssetthrottleSentPeriod += qpack.Packet.ToBytes().Length;
}
}
}
public LLUUID SessionId
{
get { return m_sessionId; }
}
public void SetDebug(int newDebug)
{
debug = newDebug;
}
# region Client Methods
public void Close()
{
clientPingTimer.Stop();
m_scene.RemoveClient(AgentId);
ClientThread.Abort();
}
public void Stop()
{
clientPingTimer.Stop();
libsecondlife.Packets.DisableSimulatorPacket disable = new libsecondlife.Packets.DisableSimulatorPacket();
OutPacket(disable, ThrottleOutPacketType.Task);
ClientThread.Abort();
}
#endregion
# region Packet Handling
public static bool AddPacketHandler(PacketType packetType, PacketMethod handler)
{
bool result = false;
lock (PacketHandlers)
{
if (!PacketHandlers.ContainsKey(packetType))
{
PacketHandlers.Add(packetType, handler);
result = true;
}
}
return result;
}
public bool AddLocalPacketHandler(PacketType packetType, PacketMethod handler)
{
bool result = false;
lock (m_packetHandlers)
{
if (!m_packetHandlers.ContainsKey(packetType))
{
m_packetHandlers.Add(packetType, handler);
result = true;
}
}
return result;
}
protected virtual bool ProcessPacketMethod(Packet packet)
{
bool result = false;
bool found = false;
PacketMethod method;
if (m_packetHandlers.TryGetValue(packet.Type, out method))
{
//there is a local handler for this packet type
result = method(this, packet);
}
else
{
//there is not a local handler so see if there is a Global handler
lock (PacketHandlers)
{
found = PacketHandlers.TryGetValue(packet.Type, out method);
}
if (found)
{
result = method(this, packet);
}
}
return result;
}
protected void DebugPacket(string direction, Packet packet)
{
if (debug > 0)
{
string info = "";
if (debug < 255 && packet.Type == PacketType.AgentUpdate)
return;
if (debug < 254 && packet.Type == PacketType.ViewerEffect)
return;
if (debug < 253 && (
packet.Type == PacketType.CompletePingCheck ||
packet.Type == PacketType.StartPingCheck
))
return;
if (debug < 252 && packet.Type == PacketType.PacketAck)
return;
if (debug > 1)
{
info = packet.ToString();
}
else
{
info = packet.Type.ToString();
}
Console.WriteLine(m_circuitCode + ":" + direction + ": " + info);
}
}
protected virtual void ClientLoop()
{
bool queuedLast = false;
MainLog.Instance.Verbose("CLIENT", "Entered loop");
while (true)
{
QueItem nextPacket = PacketQueue.Dequeue();
if (nextPacket.Incoming)
{
queuedLast = false;
//is a incoming packet
if (nextPacket.Packet.Type != PacketType.AgentUpdate)
{
packetsReceived++;
}
DebugPacket("IN", nextPacket.Packet);
ProcessInPacket(nextPacket.Packet);
}
else
{
// Throw it back on the queue if it's going to cause us to flood the client
if (throttleSentPeriod > throttleOutboundMax)
{
PacketQueue.Enqueue(nextPacket);
MainLog.Instance.Verbose("Client over throttle limit, requeuing packet");
if (queuedLast)
{
MainLog.Instance.Verbose("No more sendable packets, need to sleep now");
Thread.Sleep(100); // Wait a little while if this was the last packet we saw
}
queuedLast = true;
}
else
{
queuedLast = false;
// TODO: May be a bit expensive doing this twice.
//Don't throttle AvatarPickerReplies!, they return a null .ToBytes()!
if (nextPacket.Packet.Type != PacketType.AvatarPickerReply)
throttleSentPeriod += nextPacket.Packet.ToBytes().Length;
//is a out going packet
DebugPacket("OUT", nextPacket.Packet);
ProcessOutPacket(nextPacket.Packet);
}
}
}
}
# endregion
protected void CheckClientConnectivity(object sender, ElapsedEventArgs e)
{
if (packetsReceived == lastPacketsReceived)
{
probesWithNoIngressPackets++;
if (probesWithNoIngressPackets > 30)
{
if (OnConnectionClosed != null)
{
OnConnectionClosed(this);
}
}
else
{
// this will normally trigger at least one packet (ping response)
SendStartPingCheck(0);
}
}
else
{
// Something received in the meantime - we can reset the counters
probesWithNoIngressPackets = 0;
lastPacketsReceived = packetsReceived;
}
}
# region Setup
protected virtual void InitNewClient()
{
clientPingTimer = new Timer(5000);
clientPingTimer.Elapsed += new ElapsedEventHandler(CheckClientConnectivity);
clientPingTimer.Enabled = true;
MainLog.Instance.Verbose("CLIENT", "Adding viewer agent to scene");
m_scene.AddNewClient(this, true);
}
protected virtual void AuthUser()
{
// AuthenticateResponse sessionInfo = m_gridServer.AuthenticateSession(cirpack.m_circuitCode.m_sessionId, cirpack.m_circuitCode.ID, cirpack.m_circuitCode.Code);
AuthenticateResponse sessionInfo =
m_authenticateSessionsHandler.AuthenticateSession(cirpack.CircuitCode.SessionID, cirpack.CircuitCode.ID,
cirpack.CircuitCode.Code);
if (!sessionInfo.Authorised)
{
//session/circuit not authorised
MainLog.Instance.Notice("CLIENT", "New user request denied to " + userEP.ToString());
ClientThread.Abort();
}
else
{
MainLog.Instance.Notice("CLIENT", "Got authenticated connection from " + userEP.ToString());
//session is authorised
m_agentId = cirpack.CircuitCode.ID;
m_sessionId = cirpack.CircuitCode.SessionID;
m_circuitCode = cirpack.CircuitCode.Code;
firstName = sessionInfo.LoginInfo.First;
lastName = sessionInfo.LoginInfo.Last;
if (sessionInfo.LoginInfo.SecureSession != LLUUID.Zero)
{
SecureSessionID = sessionInfo.LoginInfo.SecureSession;
}
InitNewClient();
ClientLoop();
}
}
# endregion
protected void KillThread()
{
ClientThread.Abort();
}
}
}