/* * 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.Collections.Generic; using System.Threading; using System.Net; using System.Net.Sockets; using OpenMetaverse.Packets; namespace OpenMetaverse { #region Enums /// /// Simulator (region) properties /// [Flags] public enum RegionFlags : ulong { /// No flags set None = 0, /// Agents can take damage and be killed AllowDamage = 1 << 0, /// Landmarks can be created here AllowLandmark = 1 << 1, /// Home position can be set in this sim AllowSetHome = 1 << 2, /// Home position is reset when an agent teleports away ResetHomeOnTeleport = 1 << 3, /// Sun does not move SunFixed = 1 << 4, /// No object, land, etc. taxes TaxFree = 1 << 5, /// Disable heightmap alterations (agents can still plant /// foliage) BlockTerraform = 1 << 6, /// Land cannot be released, sold, or purchased BlockLandResell = 1 << 7, /// All content is wiped nightly Sandbox = 1 << 8, /// Unknown: Related to the availability of an overview world map tile.(Think mainland images when zoomed out.) NullLayer = 1 << 9, /// Unknown: Related to region debug flags. Possibly to skip processing of agent interaction with world. SkipAgentAction = 1 << 10, /// Region does not update agent prim interest lists. Internal debugging option. SkipUpdateInterestList = 1 << 11, /// No collision detection for non-agent objects SkipCollisions = 1 << 12, /// No scripts are ran SkipScripts = 1 << 13, /// All physics processing is turned off SkipPhysics = 1 << 14, /// Region can be seen from other regions on world map. (Legacy world map option?) ExternallyVisible = 1 << 15, /// Region can be seen from mainland on world map. (Legacy world map option?) MainlandVisible = 1 << 16, /// Agents not explicitly on the access list can visit the region. PublicAllowed = 1 << 17, /// Traffic calculations are not run across entire region, overrides parcel settings. BlockDwell = 1 << 18, /// Flight is disabled (not currently enforced by the sim) NoFly = 1 << 19, /// Allow direct (p2p) teleporting AllowDirectTeleport = 1 << 20, /// Estate owner has temporarily disabled scripting EstateSkipScripts = 1 << 21, /// Restricts the usage of the LSL llPushObject function, applies to whole region. RestrictPushObject = 1 << 22, /// Deny agents with no payment info on file DenyAnonymous = 1 << 23, /// Deny agents with payment info on file DenyIdentified = 1 << 24, /// Deny agents who have made a monetary transaction DenyTransacted = 1 << 25, /// Parcels within the region may be joined or divided by anyone, not just estate owners/managers. AllowParcelChanges = 1 << 26, /// Abuse reports sent from within this region are sent to the estate owner defined email. AbuseEmailToEstateOwner = 1 << 27, /// Region is Voice Enabled AllowVoice = 1 << 28, /// Removes the ability from parcel owners to set their parcels to show in search. BlockParcelSearch = 1 << 29, /// Deny agents who have not been age verified from entering the region. DenyAgeUnverified = 1 << 30 } /// /// Region protocol flags /// [Flags] public enum RegionProtocols : ulong { /// Nothing special None = 0, /// Region supports Server side Appearance AgentAppearanceService = 1 << 0, /// Viewer supports Server side Appearance SelfAppearanceSupport = 1 << 2 } /// /// Access level for a simulator /// public enum SimAccess : byte { /// Unknown or invalid access level Unknown = 0, /// Trial accounts allowed Trial = 7, /// PG rating PG = 13, /// Mature rating Mature = 21, /// Adult rating Adult = 42, /// Simulator is offline Down = 254, /// Simulator does not exist NonExistent = 255 } #endregion Enums /// /// /// public class Simulator : UDPBase, IDisposable { #region Structs /// /// Simulator Statistics /// public struct SimStats { /// Total number of packets sent by this simulator to this agent public long SentPackets; /// Total number of packets received by this simulator to this agent public long RecvPackets; /// Total number of bytes sent by this simulator to this agent public long SentBytes; /// Total number of bytes received by this simulator to this agent public long RecvBytes; /// Time in seconds agent has been connected to simulator public int ConnectTime; /// Total number of packets that have been resent public int ResentPackets; /// Total number of resent packets recieved public int ReceivedResends; /// Total number of pings sent to this simulator by this agent public int SentPings; /// Total number of ping replies sent to this agent by this simulator public int ReceivedPongs; /// /// Incoming bytes per second /// /// It would be nice to have this claculated on the fly, but /// this is far, far easier public int IncomingBPS; /// /// Outgoing bytes per second /// /// It would be nice to have this claculated on the fly, but /// this is far, far easier public int OutgoingBPS; /// Time last ping was sent public int LastPingSent; /// ID of last Ping sent public byte LastPingID; /// public int LastLag; /// public int MissedPings; /// Current time dilation of this simulator public float Dilation; /// Current Frames per second of simulator public int FPS; /// Current Physics frames per second of simulator public float PhysicsFPS; /// public float AgentUpdates; /// public float FrameTime; /// public float NetTime; /// public float PhysicsTime; /// public float ImageTime; /// public float ScriptTime; /// public float AgentTime; /// public float OtherTime; /// Total number of objects Simulator is simulating public int Objects; /// Total number of Active (Scripted) objects running public int ScriptedObjects; /// Number of agents currently in this simulator public int Agents; /// Number of agents in neighbor simulators public int ChildAgents; /// Number of Active scripts running in this simulator public int ActiveScripts; /// public int LSLIPS; /// public int INPPS; /// public int OUTPPS; /// Number of downloads pending public int PendingDownloads; /// Number of uploads pending public int PendingUploads; /// public int VirtualSize; /// public int ResidentSize; /// Number of local uploads pending public int PendingLocalUploads; /// Unacknowledged bytes in queue public int UnackedBytes; } #endregion Structs #region Public Members /// A public reference to the client that this Simulator object /// is attached to public GridClient Client; /// A Unique Cache identifier for this simulator public UUID ID = UUID.Zero; /// The capabilities for this simulator public Caps Caps = null; /// public ulong Handle; /// The current version of software this simulator is running public string SimVersion = String.Empty; /// public string Name = String.Empty; /// A 64x64 grid of parcel coloring values. The values stored /// in this array are of the type public byte[] ParcelOverlay = new byte[4096]; /// public int ParcelOverlaysReceived; /// public float TerrainHeightRange00; /// public float TerrainHeightRange01; /// public float TerrainHeightRange10; /// public float TerrainHeightRange11; /// public float TerrainStartHeight00; /// public float TerrainStartHeight01; /// public float TerrainStartHeight10; /// public float TerrainStartHeight11; /// public float WaterHeight; /// public UUID SimOwner = UUID.Zero; /// public UUID TerrainBase0 = UUID.Zero; /// public UUID TerrainBase1 = UUID.Zero; /// public UUID TerrainBase2 = UUID.Zero; /// public UUID TerrainBase3 = UUID.Zero; /// public UUID TerrainDetail0 = UUID.Zero; /// public UUID TerrainDetail1 = UUID.Zero; /// public UUID TerrainDetail2 = UUID.Zero; /// public UUID TerrainDetail3 = UUID.Zero; /// true if your agent has Estate Manager rights on this region public bool IsEstateManager; /// public RegionFlags Flags; /// public SimAccess Access; /// public float BillableFactor; /// Statistics information for this simulator and the /// connection to the simulator, calculated by the simulator itself /// and the library public SimStats Stats; /// The regions Unique ID public UUID RegionID = UUID.Zero; /// The physical data center the simulator is located /// Known values are: /// /// Dallas /// Chandler /// SF /// /// public string ColoLocation; /// The CPU Class of the simulator /// Most full mainland/estate sims appear to be 5, /// Homesteads and Openspace appear to be 501 public int CPUClass; /// The number of regions sharing the same CPU as this one /// "Full Sims" appear to be 1, Homesteads appear to be 4 public int CPURatio; /// The billing product name /// Known values are: /// /// Mainland / Full Region (Sku: 023) /// Estate / Full Region (Sku: 024) /// Estate / Openspace (Sku: 027) /// Estate / Homestead (Sku: 029) /// Mainland / Homestead (Sku: 129) (Linden Owned) /// Mainland / Linden Homes (Sku: 131) /// /// public string ProductName; /// The billing product SKU /// Known values are: /// /// 023 Mainland / Full Region /// 024 Estate / Full Region /// 027 Estate / Openspace /// 029 Estate / Homestead /// 129 Mainland / Homestead (Linden Owned) /// 131 Linden Homes / Full Region /// /// public string ProductSku; /// /// Flags indicating which protocols this region supports /// public RegionProtocols Protocols; /// The current sequence number for packets sent to this /// simulator. Must be Interlocked before modifying. Only /// useful for applications manipulating sequence numbers public int Sequence; /// /// A thread-safe dictionary containing avatars in a simulator /// public InternalDictionary ObjectsAvatars = new InternalDictionary(); /// /// A thread-safe dictionary containing primitives in a simulator /// public InternalDictionary ObjectsPrimitives = new InternalDictionary(); public readonly TerrainPatch[] Terrain; public readonly Vector2[] WindSpeeds; /// /// Provides access to an internal thread-safe dictionary containing parcel /// information found in this simulator /// public InternalDictionary Parcels { get { if (Client.Settings.POOL_PARCEL_DATA) { return DataPool.Parcels; } if (_Parcels == null) _Parcels = new InternalDictionary(); return _Parcels; } } private InternalDictionary _Parcels; /// /// Provides access to an internal thread-safe multidimensional array containing a x,y grid mapped /// to each 64x64 parcel's LocalID. /// public int[,] ParcelMap { get { lock (this) { if (Client.Settings.POOL_PARCEL_DATA) { return DataPool.ParcelMap; } if (_ParcelMap == null) _ParcelMap = new int[64, 64]; return _ParcelMap; } } } /// /// Checks simulator parcel map to make sure it has downloaded all data successfully /// /// true if map is full (contains no 0's) public bool IsParcelMapFull() { for (int y = 0; y < 64; y++) { for (int x = 0; x < 64; x++) { if (this.ParcelMap[y, x] == 0) return false; } } return true; } /// /// Is it safe to send agent updates to this sim /// AgentMovementComplete message received /// public bool AgentMovementComplete; #endregion Public Members #region Properties /// The IP address and port of the server public IPEndPoint IPEndPoint { get { return remoteEndPoint; } } /// Whether there is a working connection to the simulator or /// not public bool Connected { get { return connected; } } /// Coarse locations of avatars in this simulator public InternalDictionary AvatarPositions { get { return avatarPositions; } } /// AvatarPositions key representing TrackAgent target public UUID PreyID { get { return preyID; } } /// Indicates if UDP connection to the sim is fully established public bool HandshakeComplete { get { return handshakeComplete; } } #endregion Properties #region Internal/Private Members /// Used internally to track sim disconnections internal bool DisconnectCandidate = false; /// Event that is triggered when the simulator successfully /// establishes a connection internal ManualResetEvent ConnectedEvent = new ManualResetEvent(false); /// Whether this sim is currently connected or not. Hooked up /// to the property Connected internal bool connected; /// Coarse locations of avatars in this simulator internal InternalDictionary avatarPositions = new InternalDictionary(); /// AvatarPositions key representing TrackAgent target internal UUID preyID = UUID.Zero; /// Sequence numbers of packets we've received /// (for duplicate checking) internal IncomingPacketIDCollection PacketArchive; /// Packets we sent out that need ACKs from the simulator internal SortedDictionary NeedAck = new SortedDictionary(); /// Sequence number for pause/resume internal int pauseSerial; /// Indicates if UDP connection to the sim is fully established internal bool handshakeComplete; private NetworkManager Network; private Queue InBytes, OutBytes; // ACKs that are queued up to be sent to the simulator private LocklessQueue PendingAcks = new LocklessQueue(); private Timer AckTimer; private Timer PingTimer; private Timer StatsTimer; // simulator <> parcel LocalID Map private int[,] _ParcelMap; public readonly SimulatorDataPool DataPool; internal bool DownloadingParcelMap { get { return Client.Settings.POOL_PARCEL_DATA ? DataPool.DownloadingParcelMap : _DownloadingParcelMap; } set { if (Client.Settings.POOL_PARCEL_DATA) DataPool.DownloadingParcelMap = value; _DownloadingParcelMap = value; } } internal bool _DownloadingParcelMap = false; private ManualResetEvent GotUseCircuitCodeAck = new ManualResetEvent(false); #endregion Internal/Private Members /// /// /// /// Reference to the GridClient object /// IPEndPoint of the simulator /// handle of the simulator public Simulator(GridClient client, IPEndPoint address, ulong handle) : base(address) { Client = client; if (Client.Settings.POOL_PARCEL_DATA || Client.Settings.CACHE_PRIMITIVES) { SimulatorDataPool.SimulatorAdd(this); DataPool = SimulatorDataPool.GetSimulatorData(Handle); } Handle = handle; Network = Client.Network; PacketArchive = new IncomingPacketIDCollection(Settings.PACKET_ARCHIVE_SIZE); InBytes = new Queue(Client.Settings.STATS_QUEUE_SIZE); OutBytes = new Queue(Client.Settings.STATS_QUEUE_SIZE); if (client.Settings.STORE_LAND_PATCHES) { Terrain = new TerrainPatch[16 * 16]; WindSpeeds = new Vector2[16 * 16]; } } /// /// Called when this Simulator object is being destroyed /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public virtual void Dispose(bool disposing) { if (disposing) { if (AckTimer != null) AckTimer.Dispose(); if (PingTimer != null) PingTimer.Dispose(); if (StatsTimer != null) StatsTimer.Dispose(); if (ConnectedEvent != null) ConnectedEvent.Close(); // Force all the CAPS connections closed for this simulator if (Caps != null) Caps.Disconnect(true); } } /// /// Attempt to connect to this simulator /// /// Whether to move our agent in to this sim or not /// True if the connection succeeded or connection status is /// unknown, false if there was a failure public bool Connect(bool moveToSim) { handshakeComplete = false; if (connected) { UseCircuitCode(true); if (moveToSim) Client.Self.CompleteAgentMovement(this); return true; } #region Start Timers // Timer for sending out queued packet acknowledgements if (AckTimer == null) AckTimer = new Timer(AckTimer_Elapsed, null, Settings.NETWORK_TICK_INTERVAL, Timeout.Infinite); // Timer for recording simulator connection statistics if (StatsTimer == null) StatsTimer = new Timer(StatsTimer_Elapsed, null, 1000, 1000); // Timer for periodically pinging the simulator if (PingTimer == null && Client.Settings.SEND_PINGS) PingTimer = new Timer(PingTimer_Elapsed, null, Settings.PING_INTERVAL, Settings.PING_INTERVAL); #endregion Start Timers Logger.Log("Connecting to " + this.ToString(), Helpers.LogLevel.Info, Client); try { // Create the UDP connection Start(); // Mark ourselves as connected before firing everything else up connected = true; // Initiate connection UseCircuitCode(true); Stats.ConnectTime = Environment.TickCount; // Move our agent in to the sim to complete the connection if (moveToSim) Client.Self.CompleteAgentMovement(this); if (!ConnectedEvent.WaitOne(Client.Settings.LOGIN_TIMEOUT, false)) { Logger.Log("Giving up on waiting for RegionHandshake for " + this.ToString(), Helpers.LogLevel.Warning, Client); //Remove the simulator from the list, not useful if we haven't recieved the RegionHandshake lock (Client.Network.Simulators) { Client.Network.Simulators.Remove(this); } } if (Client.Settings.SEND_AGENT_THROTTLE) Client.Throttle.Set(this); if (Client.Settings.SEND_AGENT_UPDATES) Client.Self.Movement.SendUpdate(true, this); return true; } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } return false; } /// /// Initiates connection to the simulator /// /// Should we block until ack for this packet is recieved public void UseCircuitCode(bool waitForAck) { // Send the UseCircuitCode packet to initiate the connection UseCircuitCodePacket use = new UseCircuitCodePacket(); use.CircuitCode.Code = Network.CircuitCode; use.CircuitCode.ID = Client.Self.AgentID; use.CircuitCode.SessionID = Client.Self.SessionID; if (waitForAck) { GotUseCircuitCodeAck.Reset(); } // Send the initial packet out SendPacket(use); if (waitForAck) { if (!GotUseCircuitCodeAck.WaitOne(Client.Settings.LOGIN_TIMEOUT, false)) { Logger.Log("Failed to get ACK for UseCircuitCode packet", Helpers.LogLevel.Error, Client); } } } public void SetSeedCaps(string seedcaps) { if (Caps != null) { if (Caps._SeedCapsURI == seedcaps) return; Logger.Log("Unexpected change of seed capability", Helpers.LogLevel.Warning, Client); Caps.Disconnect(true); Caps = null; } if (Client.Settings.ENABLE_CAPS) { // Connect to the new CAPS system if (!String.IsNullOrEmpty(seedcaps)) Caps = new Caps(this, seedcaps); else Logger.Log("Setting up a sim without a valid capabilities server!", Helpers.LogLevel.Error, Client); } } /// /// Disconnect from this simulator /// public void Disconnect(bool sendCloseCircuit) { if (connected) { connected = false; // Destroy the timers if (AckTimer != null) AckTimer.Dispose(); if (StatsTimer != null) StatsTimer.Dispose(); if (PingTimer != null) PingTimer.Dispose(); AckTimer = null; StatsTimer = null; PingTimer = null; // Kill the current CAPS system if (Caps != null) { Caps.Disconnect(true); Caps = null; } if (sendCloseCircuit) { // Try to send the CloseCircuit notice CloseCircuitPacket close = new CloseCircuitPacket(); UDPPacketBuffer buf = new UDPPacketBuffer(remoteEndPoint); byte[] data = close.ToBytes(); Buffer.BlockCopy(data, 0, buf.Data, 0, data.Length); buf.DataLength = data.Length; AsyncBeginSend(buf); } if (Client.Settings.POOL_PARCEL_DATA || Client.Settings.CACHE_PRIMITIVES) { SimulatorDataPool.SimulatorRelease(this); } // Shut the socket communication down Stop(); } } /// /// Instructs the simulator to stop sending update (and possibly other) packets /// public void Pause() { AgentPausePacket pause = new AgentPausePacket(); pause.AgentData.AgentID = Client.Self.AgentID; pause.AgentData.SessionID = Client.Self.SessionID; pause.AgentData.SerialNum = (uint)Interlocked.Exchange(ref pauseSerial, pauseSerial + 1); Client.Network.SendPacket(pause, this); } /// /// Instructs the simulator to resume sending update packets (unpause) /// public void Resume() { AgentResumePacket resume = new AgentResumePacket(); resume.AgentData.AgentID = Client.Self.AgentID; resume.AgentData.SessionID = Client.Self.SessionID; resume.AgentData.SerialNum = (uint)Interlocked.Exchange(ref pauseSerial, pauseSerial + 1); Client.Network.SendPacket(resume, this); } /// /// Retrieve the terrain height at a given coordinate /// /// Sim X coordinate, valid range is from 0 to 255 /// Sim Y coordinate, valid range is from 0 to 255 /// The terrain height at the given point if the /// lookup was successful, otherwise 0.0f /// True if the lookup was successful, otherwise false public bool TerrainHeightAtPoint(int x, int y, out float height) { if (Terrain != null && x >= 0 && x < 256 && y >= 0 && y < 256) { int patchX = x / 16; int patchY = y / 16; x = x % 16; y = y % 16; TerrainPatch patch = Terrain[patchY * 16 + patchX]; if (patch != null) { height = patch.Data[y * 16 + x]; return true; } } height = 0.0f; return false; } #region Packet Sending /// /// Sends a packet /// /// Packet to be sent public void SendPacket(Packet packet) { // DEBUG: This can go away after we are sure nothing in the library is trying to do this if (packet.Header.AppendedAcks || (packet.Header.AckList != null && packet.Header.AckList.Length > 0)) Logger.Log("Attempting to send packet " + packet.Type + " with ACKs appended before serialization", Helpers.LogLevel.Error); if (packet.HasVariableBlocks) { byte[][] datas; try { datas = packet.ToBytesMultiple(); } catch (NullReferenceException) { Logger.Log("Failed to serialize " + packet.Type + " packet to one or more payloads due to a missing block or field. StackTrace: " + Environment.StackTrace, Helpers.LogLevel.Error); return; } int packetCount = datas.Length; if (packetCount > 1) Logger.DebugLog("Split " + packet.Type + " packet into " + packetCount + " packets"); for (int i = 0; i < packetCount; i++) { byte[] data = datas[i]; SendPacketData(data, data.Length, packet.Type, packet.Header.Zerocoded); } } else { byte[] data = packet.ToBytes(); SendPacketData(data, data.Length, packet.Type, packet.Header.Zerocoded); } } public void SendPacketData(byte[] data, int dataLength, PacketType type, bool doZerocode) { UDPPacketBuffer buffer = new UDPPacketBuffer(remoteEndPoint, Packet.MTU); // Zerocode if needed if (doZerocode) { try { dataLength = Helpers.ZeroEncode(data, dataLength, buffer.Data); } catch (IndexOutOfRangeException) { // The packet grew larger than Packet.MTU bytes while zerocoding. // Remove the MSG_ZEROCODED flag and send the unencoded data // instead data[0] = (byte)(data[0] & ~Helpers.MSG_ZEROCODED); Buffer.BlockCopy(data, 0, buffer.Data, 0, dataLength); } } else { Buffer.BlockCopy(data, 0, buffer.Data, 0, dataLength); } buffer.DataLength = dataLength; #region Queue or Send NetworkManager.OutgoingPacket outgoingPacket = new NetworkManager.OutgoingPacket(this, buffer, type); // Send ACK and logout packets directly, everything else goes through the queue if (Client.Settings.THROTTLE_OUTGOING_PACKETS == false || type == PacketType.PacketAck || type == PacketType.LogoutRequest) { SendPacketFinal(outgoingPacket); } else { Network.PacketOutbox.Enqueue(outgoingPacket); } #endregion Queue or Send #region Stats Tracking if (Client.Settings.TRACK_UTILIZATION) { Client.Stats.Update(type.ToString(), OpenMetaverse.Stats.Type.Packet, dataLength, 0); } #endregion } internal void SendPacketFinal(NetworkManager.OutgoingPacket outgoingPacket) { UDPPacketBuffer buffer = outgoingPacket.Buffer; byte flags = buffer.Data[0]; bool isResend = (flags & Helpers.MSG_RESENT) != 0; bool isReliable = (flags & Helpers.MSG_RELIABLE) != 0; // Keep track of when this packet was sent out (right now) outgoingPacket.TickCount = Environment.TickCount; #region ACK Appending int dataLength = buffer.DataLength; // Keep appending ACKs until there is no room left in the packet or there are // no more ACKs to append uint ackCount = 0; uint ack; while (dataLength + 5 < Packet.MTU && PendingAcks.TryDequeue(out ack)) { Utils.UIntToBytesBig(ack, buffer.Data, dataLength); dataLength += 4; ++ackCount; } if (ackCount > 0) { // Set the last byte of the packet equal to the number of appended ACKs buffer.Data[dataLength++] = (byte)ackCount; // Set the appended ACKs flag on this packet buffer.Data[0] |= Helpers.MSG_APPENDED_ACKS; } buffer.DataLength = dataLength; #endregion ACK Appending if (!isResend) { // Not a resend, assign a new sequence number uint sequenceNumber = (uint)Interlocked.Increment(ref Sequence); Utils.UIntToBytesBig(sequenceNumber, buffer.Data, 1); outgoingPacket.SequenceNumber = sequenceNumber; if (isReliable) { // Add this packet to the list of ACK responses we are waiting on from the server lock (NeedAck) NeedAck[sequenceNumber] = outgoingPacket; } } // Put the UDP payload on the wire AsyncBeginSend(buffer); } /// /// /// public void SendPing() { uint oldestUnacked = 0; // Get the oldest NeedAck value, the first entry in the sorted dictionary lock (NeedAck) { if (NeedAck.Count > 0) { SortedDictionary.KeyCollection.Enumerator en = NeedAck.Keys.GetEnumerator(); en.MoveNext(); oldestUnacked = en.Current; } } //if (oldestUnacked != 0) // Logger.DebugLog("Sending ping with oldestUnacked=" + oldestUnacked); StartPingCheckPacket ping = new StartPingCheckPacket(); ping.PingID.PingID = Stats.LastPingID++; ping.PingID.OldestUnacked = oldestUnacked; ping.Header.Reliable = false; SendPacket(ping); Stats.LastPingSent = Environment.TickCount; } #endregion Packet Sending /// /// Returns Simulator Name as a String /// /// public override string ToString() { if (!String.IsNullOrEmpty(Name)) return String.Format("{0} ({1})", Name, remoteEndPoint); else return String.Format("({0})", remoteEndPoint); } /// /// /// /// public override int GetHashCode() { return Handle.GetHashCode(); } /// /// /// /// /// public override bool Equals(object obj) { Simulator sim = obj as Simulator; if (sim == null) return false; return (remoteEndPoint.Equals(sim.remoteEndPoint)); } public static bool operator ==(Simulator lhs, Simulator rhs) { // If both are null, or both are same instance, return true if (System.Object.ReferenceEquals(lhs, rhs)) { return true; } // If one is null, but not both, return false. if (((object)lhs == null) || ((object)rhs == null)) { return false; } return lhs.remoteEndPoint.Equals(rhs.remoteEndPoint); } public static bool operator !=(Simulator lhs, Simulator rhs) { return !(lhs == rhs); } protected override void PacketReceived(UDPPacketBuffer buffer) { Packet packet = null; // Check if this packet came from the server we expected it to come from if (!remoteEndPoint.Address.Equals(((IPEndPoint)buffer.RemoteEndPoint).Address)) { Logger.Log("Received " + buffer.DataLength + " bytes of data from unrecognized source " + ((IPEndPoint)buffer.RemoteEndPoint).ToString(), Helpers.LogLevel.Warning, Client); return; } // Update the disconnect flag so this sim doesn't time out DisconnectCandidate = false; #region Packet Decoding int packetEnd = buffer.DataLength - 1; try { packet = Packet.BuildPacket(buffer.Data, ref packetEnd, // Only allocate a buffer for zerodecoding if the packet is zerocoded ((buffer.Data[0] & Helpers.MSG_ZEROCODED) != 0) ? new byte[8192] : null); } catch (MalformedDataException) { Logger.Log(String.Format("Malformed data, cannot parse packet:\n{0}", Utils.BytesToHexString(buffer.Data, buffer.DataLength, null)), Helpers.LogLevel.Error); } // Fail-safe check if (packet == null) { Logger.Log("Couldn't build a message from the incoming data", Helpers.LogLevel.Warning, Client); return; } Interlocked.Add(ref Stats.RecvBytes, buffer.DataLength); Interlocked.Increment(ref Stats.RecvPackets); #endregion Packet Decoding if (packet.Header.Resent) Interlocked.Increment(ref Stats.ReceivedResends); #region ACK Receiving // Handle appended ACKs if (packet.Header.AppendedAcks && packet.Header.AckList != null) { lock (NeedAck) { for (int i = 0; i < packet.Header.AckList.Length; i++) { if (NeedAck.ContainsKey(packet.Header.AckList[i]) && NeedAck[packet.Header.AckList[i]].Type == PacketType.UseCircuitCode) { GotUseCircuitCodeAck.Set(); } NeedAck.Remove(packet.Header.AckList[i]); } } } // Handle PacketAck packets if (packet.Type == PacketType.PacketAck) { PacketAckPacket ackPacket = (PacketAckPacket)packet; lock (NeedAck) { for (int i = 0; i < ackPacket.Packets.Length; i++) { if (NeedAck.ContainsKey(ackPacket.Packets[i].ID) && NeedAck[ackPacket.Packets[i].ID].Type == PacketType.UseCircuitCode) { GotUseCircuitCodeAck.Set(); } NeedAck.Remove(ackPacket.Packets[i].ID); } } } #endregion ACK Receiving if (packet.Header.Reliable) { #region ACK Sending // Add this packet to the list of ACKs that need to be sent out uint sequence = (uint)packet.Header.Sequence; PendingAcks.Enqueue(sequence); // Send out ACKs if we have a lot of them if (PendingAcks.Count >= Client.Settings.MAX_PENDING_ACKS) SendAcks(); #endregion ACK Sending // Check the archive of received packet IDs to see whether we already received this packet if (!PacketArchive.TryEnqueue(packet.Header.Sequence)) { if (packet.Header.Resent) Logger.DebugLog( string.Format( "Received a resend of already processed packet #{0}, type: {1} from {2}", packet.Header.Sequence, packet.Type, Name)); else Logger.Log( string.Format( "Received a duplicate (not marked as resend) of packet #{0}, type: {1} for {2} from {3}", packet.Header.Sequence, packet.Type, Client.Self.Name, Name), Helpers.LogLevel.Warning); // Avoid firing a callback twice for the same packet return; } } #region Inbox Insertion NetworkManager.IncomingPacket incomingPacket; incomingPacket.Simulator = this; incomingPacket.Packet = packet; Network.PacketInbox.Enqueue(incomingPacket); #endregion Inbox Insertion #region Stats Tracking if (Client.Settings.TRACK_UTILIZATION) { Client.Stats.Update(packet.Type.ToString(), OpenMetaverse.Stats.Type.Packet, 0, packet.Length); } #endregion } protected override void PacketSent(UDPPacketBuffer buffer, int bytesSent) { // Stats tracking Interlocked.Add(ref Stats.SentBytes, bytesSent); Interlocked.Increment(ref Stats.SentPackets); Client.Network.RaisePacketSentEvent(buffer.Data, bytesSent, this); } /// /// Sends out pending acknowledgements /// /// Number of ACKs sent private int SendAcks() { uint ack; int ackCount = 0; if (PendingAcks.TryDequeue(out ack)) { List blocks = new List(); PacketAckPacket.PacketsBlock block = new PacketAckPacket.PacketsBlock(); block.ID = ack; blocks.Add(block); while (PendingAcks.TryDequeue(out ack)) { block = new PacketAckPacket.PacketsBlock(); block.ID = ack; blocks.Add(block); } PacketAckPacket packet = new PacketAckPacket(); packet.Header.Reliable = false; packet.Packets = blocks.ToArray(); ackCount = blocks.Count; SendPacket(packet); } return ackCount; } /// /// Resend unacknowledged packets /// private void ResendUnacked() { if (NeedAck.Count > 0) { NetworkManager.OutgoingPacket[] array; lock (NeedAck) { // Create a temporary copy of the outgoing packets array to iterate over array = new NetworkManager.OutgoingPacket[NeedAck.Count]; NeedAck.Values.CopyTo(array, 0); } int now = Environment.TickCount; // Resend packets for (int i = 0; i < array.Length; i++) { NetworkManager.OutgoingPacket outgoing = array[i]; if (outgoing.TickCount != 0 && now - outgoing.TickCount > Client.Settings.RESEND_TIMEOUT) { if (outgoing.ResendCount < Client.Settings.MAX_RESEND_COUNT) { if (Client.Settings.LOG_RESENDS) { Logger.DebugLog(String.Format("Resending {2} packet #{0}, {1}ms have passed", outgoing.SequenceNumber, now - outgoing.TickCount, outgoing.Type), Client); } // The TickCount will be set to the current time when the packet // is actually sent out again outgoing.TickCount = 0; // Set the resent flag outgoing.Buffer.Data[0] = (byte)(outgoing.Buffer.Data[0] | Helpers.MSG_RESENT); // Stats tracking Interlocked.Increment(ref outgoing.ResendCount); Interlocked.Increment(ref Stats.ResentPackets); SendPacketFinal(outgoing); } else { Logger.DebugLog(String.Format("Dropping packet #{0} after {1} failed attempts", outgoing.SequenceNumber, outgoing.ResendCount)); lock (NeedAck) NeedAck.Remove(outgoing.SequenceNumber); } } } } } private void AckTimer_Elapsed(object obj) { SendAcks(); ResendUnacked(); // Start the ACK handling functions again after NETWORK_TICK_INTERVAL milliseconds if (null == AckTimer) return; try { AckTimer.Change(Settings.NETWORK_TICK_INTERVAL, Timeout.Infinite); } catch (Exception) { } } private void StatsTimer_Elapsed(object obj) { long old_in = 0, old_out = 0; long recv = Stats.RecvBytes; long sent = Stats.SentBytes; if (InBytes.Count >= Client.Settings.STATS_QUEUE_SIZE) old_in = InBytes.Dequeue(); if (OutBytes.Count >= Client.Settings.STATS_QUEUE_SIZE) old_out = OutBytes.Dequeue(); InBytes.Enqueue(recv); OutBytes.Enqueue(sent); if (old_in > 0 && old_out > 0) { Stats.IncomingBPS = (int)(recv - old_in) / Client.Settings.STATS_QUEUE_SIZE; Stats.OutgoingBPS = (int)(sent - old_out) / Client.Settings.STATS_QUEUE_SIZE; //Client.Log("Incoming: " + IncomingBPS + " Out: " + OutgoingBPS + // " Lag: " + LastLag + " Pings: " + ReceivedPongs + // "/" + SentPings, Helpers.LogLevel.Debug); } } private void PingTimer_Elapsed(object obj) { SendPing(); Interlocked.Increment(ref Stats.SentPings); } } public sealed class IncomingPacketIDCollection { readonly uint[] Items; HashSet hashSet; int first; int next; int capacity; public IncomingPacketIDCollection(int capacity) { this.capacity = capacity; Items = new uint[capacity]; hashSet = new HashSet(); } public bool TryEnqueue(uint ack) { lock (hashSet) { if (hashSet.Add(ack)) { Items[next] = ack; next = (next + 1) % capacity; if (next == first) { hashSet.Remove(Items[first]); first = (first + 1) % capacity; } return true; } } return false; } } public class SimulatorDataPool { private static Timer InactiveSimReaper; private static void RemoveOldSims(object state) { lock (SimulatorDataPools) { int SimTimeout = Settings.SIMULATOR_POOL_TIMEOUT; List reap = new List(); foreach (var pool in SimulatorDataPools.Values) { if (pool.InactiveSince != DateTime.MaxValue && pool.InactiveSince.AddMilliseconds(SimTimeout) < DateTime.Now) { reap.Add(pool.Handle); } } foreach (var hndl in reap) { SimulatorDataPools.Remove(hndl); } } } public static void SimulatorAdd(Simulator sim) { lock (SimulatorDataPools) { if (InactiveSimReaper == null) { InactiveSimReaper = new Timer(RemoveOldSims, null, TimeSpan.FromMinutes(3), TimeSpan.FromMinutes(3)); } SimulatorDataPool pool = GetSimulatorData(sim.Handle); if (pool.ActiveClients < 1) pool.ActiveClients = 1; else pool.ActiveClients++; pool.InactiveSince = DateTime.MaxValue; } } public static void SimulatorRelease(Simulator sim) { ulong hndl = sim.Handle; lock (SimulatorDataPools) { SimulatorDataPool dataPool = GetSimulatorData(hndl); dataPool.ActiveClients--; if (dataPool.ActiveClients <= 0) { dataPool.InactiveSince = DateTime.Now; } } } static public Dictionary SimulatorDataPools = new Dictionary(); /// /// Simulator handle /// readonly public ulong Handle; /// /// Number of GridClients using this datapool /// public int ActiveClients; /// /// Time that the last client disconnected from the simulator /// public DateTime InactiveSince = DateTime.MaxValue; #region Pooled Items /// /// The cache of prims used and unused in this simulator /// public Dictionary PrimCache = new Dictionary(); /// /// Shared parcel info only when POOL_PARCEL_DATA == true /// public InternalDictionary Parcels = new InternalDictionary(); public int[,] ParcelMap = new int[64, 64]; public bool DownloadingParcelMap = false; #endregion Pooled Items private SimulatorDataPool(ulong hndl) { this.Handle = hndl; } public static SimulatorDataPool GetSimulatorData(ulong hndl) { SimulatorDataPool dict; lock (SimulatorDataPools) { if (!SimulatorDataPools.TryGetValue(hndl, out dict)) { dict = SimulatorDataPools[hndl] = new SimulatorDataPool(hndl); } } return dict; } #region Factories internal Primitive MakePrimitive(uint localID) { var dict = PrimCache; lock (dict) { Primitive prim; if (!dict.TryGetValue(localID, out prim)) { dict[localID] = prim = new Primitive { RegionHandle = Handle, LocalID = localID }; } return prim; } } internal bool NeedsRequest(uint localID) { var dict = PrimCache; lock (dict) return !dict.ContainsKey(localID); } #endregion Factories internal void ReleasePrims(List removePrims) { lock (PrimCache) { foreach (uint u in removePrims) { Primitive prim; if (PrimCache.TryGetValue(u, out prim)) prim.ActiveClients--; } } } } }