/* * Copyright (c) 2007, Second Life Reverse Engineering Team * 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 Second Life Reverse Engineering Team 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 libsecondlife.Packets; namespace libsecondlife { /// /// Simulator is a wrapper for a network connection to a simulator and the /// Region class representing the block of land in the metaverse /// public class Simulator { /// A public reference to the client that this Simulator object /// is attached to public SecondLife Client; /// public LLUUID ID = LLUUID.Zero; /// public ulong Handle; /// public string Name = String.Empty; /// 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 LLUUID SimOwner = LLUUID.Zero; /// public LLUUID TerrainBase0 = LLUUID.Zero; /// public LLUUID TerrainBase1 = LLUUID.Zero; /// public LLUUID TerrainBase2 = LLUUID.Zero; /// public LLUUID TerrainBase3 = LLUUID.Zero; /// public LLUUID TerrainDetail0 = LLUUID.Zero; /// public LLUUID TerrainDetail1 = LLUUID.Zero; /// public LLUUID TerrainDetail2 = LLUUID.Zero; /// public LLUUID TerrainDetail3 = LLUUID.Zero; /// public bool IsEstateManager; /// public EstateTools Estate; /// Current time dilation of this simulator public float Dilation; /// The IP address and port of the server public IPEndPoint IPEndPoint { get { return ipEndPoint; } } /// Whether there is a working connection to the simulator or /// not public bool Connected { get { return connected; } } /// Used internally to track sim disconnections internal bool DisconnectCandidate = false; /// internal ManualResetEvent ConnectedEvent = new ManualResetEvent(false); /// internal bool connected; private NetworkManager Network; private uint Sequence = 0; private object SequenceLock = new object(); private byte[] RecvBuffer = new byte[4096]; private byte[] ZeroBuffer = new byte[8192]; private byte[] ZeroOutBuffer = new byte[4096]; private Socket Connection = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); private AsyncCallback ReceivedData; // Packets we sent out that need ACKs from the simulator private Dictionary NeedAck = new Dictionary(); // Sequence numbers of packets we've received from the simulator private Queue Inbox; // ACKs that are queued up to be sent to the simulator private SortedList PendingAcks = new SortedList(); private IPEndPoint ipEndPoint; private EndPoint endPoint; private System.Timers.Timer AckTimer; /// /// Default constructor /// /// Reference to the SecondLife client /// IP address of the simulator /// Port on the simulator to connect to /// Whether to move our agent in to this sim or not public Simulator(SecondLife client, IPAddress ip, int port, bool moveToSim) { Client = client; Estate = new EstateTools(Client); Network = client.Network; Inbox = new Queue(Client.Settings.INBOX_SIZE); // Start the ACK timer AckTimer = new System.Timers.Timer(Client.Settings.NETWORK_TICK_LENGTH); AckTimer.Elapsed += new System.Timers.ElapsedEventHandler(AckTimer_Elapsed); AckTimer.Start(); // Initialize the callback for receiving a new packet ReceivedData = new AsyncCallback(OnReceivedData); Client.Log("Connecting to " + ip.ToString() + ":" + port, Helpers.LogLevel.Info); try { // Create an endpoint that we will be communicating with (need it in two // types due to .NET weirdness) ipEndPoint = new IPEndPoint(ip, port); endPoint = (EndPoint)ipEndPoint; // Associate this simulator's socket with the given ip/port and start listening Connection.Connect(endPoint); Connection.BeginReceiveFrom(RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref endPoint, ReceivedData, null); // Send the UseCircuitCode packet to initiate the connection UseCircuitCodePacket use = new UseCircuitCodePacket(); use.CircuitCode.Code = Network.CircuitCode; use.CircuitCode.ID = Network.AgentID; use.CircuitCode.SessionID = Network.SessionID; // Send the initial packet out SendPacket(use, true); // Move our agent in to the sim to complete the connection if (moveToSim) Client.Self.CompleteAgentMovement(this); ConnectedEvent.Reset(); if (ConnectedEvent.WaitOne(Client.Settings.SIMULATOR_TIMEOUT, false) == false) { Client.Log("Giving up on waiting for RegionHandshake", Helpers.LogLevel.Warning); connected = true; } } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } /// /// Disconnect a Simulator /// public void Disconnect() { if (connected) { connected = false; AckTimer.Stop(); // Send the CloseCircuit notice CloseCircuitPacket close = new CloseCircuitPacket(); if (Connection.Connected) { try { Connection.Send(close.ToBytes()); } catch (SocketException) { // There's a high probability of this failing if the network is // disconnecting, so don't even bother logging the error } } try { // Shut the socket communication down Connection.Shutdown(SocketShutdown.Both); } catch (SocketException) { } } } /// /// Sends a packet /// /// Packet to be sent /// Increment sequence number? public void SendPacket(Packet packet, bool incrementSequence) { byte[] buffer; int bytes; if (packet.Header.AckList.Length > 0) { // Scrub any appended ACKs since all of the ACK handling is done here packet.Header.AckList = new uint[0]; } packet.Header.AppendedAcks = false; // Keep track of when this packet was sent out packet.TickCount = Environment.TickCount; if (incrementSequence) { // Set the sequence number lock (SequenceLock) { if (Sequence > Client.Settings.MAX_SEQUENCE) Sequence = 1; else Sequence++; packet.Header.Sequence = Sequence; } if (packet.Header.Reliable) { lock (NeedAck) { if (!NeedAck.ContainsKey(packet.Header.Sequence)) { NeedAck.Add(packet.Header.Sequence, packet); } else { Client.Log("Attempted to add a duplicate sequence number (" + packet.Header.Sequence + ") to the NeedAck dictionary for packet type " + packet.Type.ToString(), Helpers.LogLevel.Warning); } } // Don't append ACKs to resent packets, in case that's what was causing the // delivery to fail if (!packet.Header.Resent) { // Append any ACKs that need to be sent out to this packet lock (PendingAcks) { if (PendingAcks.Count > 0 && PendingAcks.Count < Client.Settings.MAX_APPENDED_ACKS && packet.Type != PacketType.PacketAck && packet.Type != PacketType.LogoutRequest) { packet.Header.AckList = new uint[PendingAcks.Count]; for (int i = 0; i < PendingAcks.Count; i++) { packet.Header.AckList[i] = PendingAcks.Values[i]; } PendingAcks.Clear(); packet.Header.AppendedAcks = true; } } } } } // Serialize the packet buffer = packet.ToBytes(); bytes = buffer.Length; try { // Zerocode if needed if (packet.Header.Zerocoded) { lock (ZeroOutBuffer) { bytes = Helpers.ZeroEncode(buffer, bytes, ZeroOutBuffer); Connection.Send(ZeroOutBuffer, bytes, SocketFlags.None); } } else { Connection.Send(buffer, bytes, SocketFlags.None); } } catch (SocketException) { Client.Log("Tried to send a " + packet.Type.ToString() + " on a closed socket, shutting down " + this.ToString(), Helpers.LogLevel.Info); Network.DisconnectSim(this); return; } } /// /// Send a raw byte array payload as a packet /// /// The packet payload /// Whether the second, third, and fourth bytes /// should be modified to the current stream sequence number public void SendPacket(byte[] payload, bool setSequence) { try { if (setSequence && payload.Length > 3) { lock (SequenceLock) { payload[1] = (byte)(Sequence >> 16); payload[2] = (byte)(Sequence >> 8); payload[3] = (byte)(Sequence % 256); Sequence++; } } Connection.Send(payload, payload.Length, SocketFlags.None); } catch (SocketException) { Client.Log("Tried to send a " + payload.Length + " byte payload on a closed socket, shutting down " + this.ToString(), Helpers.LogLevel.Info); Network.DisconnectSim(this); return; } } /// /// /// /// /// /// /// public void ParcelSubdivide(float west, float south, float east, float north) { ParcelDividePacket divide = new ParcelDividePacket(); divide.AgentData.AgentID = Client.Network.AgentID; divide.AgentData.SessionID = Client.Network.SessionID; divide.ParcelData.East = east; divide.ParcelData.North = north; divide.ParcelData.South = south; divide.ParcelData.West = west; SendPacket(divide, true); } /// /// /// /// /// /// /// public void ParcelJoin(float west, float south, float east, float north) { ParcelJoinPacket join = new ParcelJoinPacket(); join.AgentData.AgentID = Client.Network.AgentID; join.AgentData.SessionID = Client.Network.SessionID; join.ParcelData.East = east; join.ParcelData.North = north; join.ParcelData.South = south; join.ParcelData.West = west; SendPacket(join, true); } /// /// Returns Simulator Name as a String /// /// public override string ToString() { if (Name.Length > 0) return Name + " (" + ipEndPoint.ToString() + ")"; else return "(" + ipEndPoint.ToString() + ")"; } /// /// /// /// public override int GetHashCode() { return ID.GetHashCode(); } /// /// /// /// /// public override bool Equals(object obj) { Simulator sim = obj as Simulator; if (sim == null) return false; return ID.Equals(sim.ID); } /// /// Sends out pending acknowledgements /// private void SendAcks() { lock (PendingAcks) { if (PendingAcks.Count > 0) { if (PendingAcks.Count > 250) { // FIXME: Handle the odd case where we have too many pending ACKs queued up Client.Log("Too many ACKs queued up!", Helpers.LogLevel.Error); return; } PacketAckPacket acks = new PacketAckPacket(); acks.Header.Reliable = false; acks.Packets = new PacketAckPacket.PacketsBlock[PendingAcks.Count]; for (int i = 0; i < PendingAcks.Count; i++) { acks.Packets[i] = new PacketAckPacket.PacketsBlock(); acks.Packets[i].ID = PendingAcks.Values[i]; } SendPacket(acks, true); PendingAcks.Clear(); } } } /// /// Resend unacknowledged packets /// private void ResendUnacked() { int now = Environment.TickCount; lock (NeedAck) { foreach (Packet packet in NeedAck.Values) { if (now - packet.TickCount > Client.Settings.RESEND_TIMEOUT) { Client.DebugLog("Resending " + packet.Type.ToString() + " packet (" + packet.Header.Sequence + "), " + (now - packet.TickCount) + "ms have passed"); packet.Header.Resent = true; SendPacket(packet, false); } } } } /// /// Callback handler for incomming data /// /// private void OnReceivedData(IAsyncResult result) { Packet packet = null; int numBytes; // Update the disconnect flag so this sim doesn't time out DisconnectCandidate = false; #region Packet Decoding lock (RecvBuffer) { // Retrieve the incoming packet try { numBytes = Connection.EndReceiveFrom(result, ref endPoint); int packetEnd = numBytes - 1; packet = Packet.BuildPacket(RecvBuffer, ref packetEnd, ZeroBuffer); Connection.BeginReceiveFrom(RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref endPoint, ReceivedData, null); } catch (SocketException) { Client.Log(endPoint.ToString() + " socket is closed, shutting down " + this.ToString(), Helpers.LogLevel.Info); Network.DisconnectSim(this); return; } } // Fail-safe check if (packet == null) { Client.Log("Couldn't build a message from the incoming data", Helpers.LogLevel.Warning); return; } #endregion Packet Decoding #region Reliable Handling if (packet.Header.Reliable) { // Queue up ACKs for resent packets lock (PendingAcks) { uint sequence = (uint)packet.Header.Sequence; if (!PendingAcks.ContainsKey(sequence)) PendingAcks[sequence] = sequence; } // Send out ACKs if we have a lot of them if (PendingAcks.Count >= Client.Settings.MAX_PENDING_ACKS) SendAcks(); } #endregion Reliable Handling #region Inbox Tracking // The Inbox doesn't serve a functional purpose any more, it's only used for debugging lock (Inbox) { // Check if we already received this packet if (Inbox.Contains(packet.Header.Sequence)) { Client.DebugLog("Received a duplicate " + packet.Type.ToString() + ", sequence=" + packet.Header.Sequence + ", resent=" + ((packet.Header.Resent) ? "Yes" : "No") + ", Inbox.Count=" + Inbox.Count + ", NeedAck.Count=" + NeedAck.Count); // Avoid firing a callback twice for the same packet return; } else { // Keep the Inbox size within a certain capacity while (Inbox.Count >= Client.Settings.INBOX_SIZE) { Inbox.Dequeue(); Inbox.Dequeue(); Inbox.Dequeue(); Inbox.Dequeue(); } // Add this packet to the inbox Inbox.Enqueue(packet.Header.Sequence); } } #endregion Inbox Tracking #region ACK handling // Handle appended ACKs if (packet.Header.AppendedAcks) { lock (NeedAck) { for (int i = 0; i < packet.Header.AckList.Length; i++) 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++) NeedAck.Remove(ackPacket.Packets[i].ID); } } #endregion ACK handling #region FireCallbacks if (Network.Callbacks.ContainsKey(packet.Type)) { List callbackArray = Network.Callbacks[packet.Type]; // Fire any registered callbacks for (int i = 0; i < callbackArray.Count; i++) { if (callbackArray[i] != null) { try { callbackArray[i](packet, this); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } } if (Network.Callbacks.ContainsKey(PacketType.Default)) { List callbackArray = Network.Callbacks[PacketType.Default]; // Fire any registered callbacks for (int i = 0; i < callbackArray.Count; i++) { if (callbackArray[i] != null) { try { callbackArray[i](packet, this); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } } #endregion FireCallbacks } private void AckTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs ea) { SendAcks(); ResendUnacked(); } } }