From 09dd4bd6834861791008e66652826a66724efa0e Mon Sep 17 00:00:00 2001 From: gareth Date: Tue, 27 Feb 2007 23:00:49 +0000 Subject: Brought in code from branches/gareth --- src/Server.cs | 707 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 707 insertions(+) create mode 100644 src/Server.cs (limited to 'src/Server.cs') diff --git a/src/Server.cs b/src/Server.cs new file mode 100644 index 0000000..1f19bf9 --- /dev/null +++ b/src/Server.cs @@ -0,0 +1,707 @@ +/* + * Copyright (c) OpenSim project, http://osgrid.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. +* * 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 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 ``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 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 libsecondlife; +using System.Collections; +using libsecondlife.Packets; +using libsecondlife.AssetSystem; +using System.Net; +using System.Net.Sockets; +using System.Timers; + +//really hacked , messy code + +namespace OpenSim +{ + /// + /// Description of Server. + /// + public interface ServerCallback + { + //should replace with delegates + void MainCallback(Packet pack, UserAgentInfo User_info); + void NewUserCallback(UserAgentInfo User_info); + void ErrorCallback(string text); + } + public class Server + { + /// A public reference to the client that this Simulator object + /// is attached to + //public SecondLife Client; + + /// The Region class that this Simulator wraps + // public Region Region; + + /// + /// Used internally to track sim disconnections, do not modify this + /// variable + /// + public bool DisconnectCandidate = false; + + /// + /// The ID number associated with this particular connection to the + /// simulator, used to emulate TCP connections. This is used + /// internally for packets that have a CircuitCode field + /// + public uint CircuitCode + { + get { return circuitCode; } + set { circuitCode = value; } + } + + /// + /// The IP address and port of the server + /// + public IPEndPoint IPEndPoint + { + get { return ipEndPoint; } + } + + /// + /// A boolean representing whether there is a working connection to the + /// simulator or not + /// + public bool Connected + { + get { return connected; } + } + + private ServerCallback CallbackObject; + 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; + private bool connected = false; + private uint circuitCode; + private IPEndPoint ipEndPoint; + private EndPoint endPoint; + private IPEndPoint ipeSender; + private EndPoint epSender; + private System.Timers.Timer AckTimer; + private Server_Settings Settings=new Server_Settings(); + public ArrayList User_agents=new ArrayList(); + + /// + /// Constructor for Simulator + /// + /// + /// + /// + /// + /// + public Server(ServerCallback s_callback) + { + + this.CallbackObject=s_callback; //should be using delegate + AckTimer = new System.Timers.Timer(Settings.NETWORK_TICK_LENGTH); + AckTimer.Elapsed += new ElapsedEventHandler(AckTimer_Elapsed); + + // Initialize the callback for receiving a new packet + ReceivedData = new AsyncCallback(this.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); + ipEndPoint = new IPEndPoint(IPAddress.Any, Globals.Instance.IpPort); + endPoint = (EndPoint)ipEndPoint; + + // Associate this simulator's socket with the given ip/port and start listening + Connection.Bind(endPoint); + ipeSender = new IPEndPoint(IPAddress.Any, 0); + //The epSender identifies the incoming clients + epSender = (EndPoint) ipeSender; + Connection.BeginReceiveFrom(RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref epSender, ReceivedData, null); + + // Start the ACK timer + AckTimer.Start(); + } + catch (Exception e) + { + + System.Console.WriteLine(e.Message); + } + } + + /// + /// 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, UserAgentInfo User_info) + { + Console.WriteLine("OUTGOING"); + Console.WriteLine(packet.ToString()); + byte[] buffer; + int bytes; + + if (!connected && packet.Type != PacketType.UseCircuitCode) + { + Console.WriteLine("Trying to send a " + packet.Type.ToString() + " packet when the socket is closed"); + + + return; + } + + /*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 > Settings.MAX_SEQUENCE) + Sequence = 1; + else + Sequence++; + packet.Header.Sequence = Sequence; + } + + if (packet.Header.Reliable) + { + lock (User_info.NeedAck) + { + if (!User_info.NeedAck.ContainsKey(packet.Header.Sequence)) + { + User_info.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 (User_info.PendingAcks) + { + if (User_info.PendingAcks.Count > 0 && User_info.PendingAcks.Count < Settings.MAX_APPENDED_ACKS && + packet.Type != PacketType.PacketAck && + packet.Type != PacketType.LogoutRequest) + { + packet.Header.AckList = new uint[User_info.PendingAcks.Count]; + int i = 0; + + foreach (uint ack in User_info.PendingAcks.Values) + { + packet.Header.AckList[i] = ack; + i++; + } + + User_info.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.SendTo(ZeroOutBuffer, bytes, SocketFlags.None,User_info.endpoint); + } + } + else + { + + Connection.SendTo(buffer, bytes, SocketFlags.None,User_info.endpoint); + } + } + catch (SocketException) + { + //Client.Log("Tried to send a " + packet.Type.ToString() + " on a closed socket", + // Helpers.LogLevel.Warning); + + Disconnect(); + } + } + + /// + /// 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 + /// + /// Returns Simulator Name as a String + /// + /// + public override string ToString() + { + return( " (" + ipEndPoint.ToString() + ")"); + } + + /// + /// Sends out pending acknowledgements + /// + private void SendAcks(UserAgentInfo User_info) + { + lock (User_info.PendingAcks) + { + if (connected && User_info.PendingAcks.Count > 0) + { + if (User_info.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; + } + + int i = 0; + PacketAckPacket acks = new PacketAckPacket(); + acks.Packets = new PacketAckPacket.PacketsBlock[User_info.PendingAcks.Count]; + + foreach (uint ack in User_info.PendingAcks.Values) + { + acks.Packets[i] = new PacketAckPacket.PacketsBlock(); + acks.Packets[i].ID = ack; + i++; + } + + acks.Header.Reliable = false; + //SendPacket(acks, true,User_info); + + User_info.PendingAcks.Clear(); + } + } + } + /// + /// Resend unacknowledged packets + /// + private void ResendUnacked(UserAgentInfo User_info) + { + if (connected) + { + int now = Environment.TickCount; + + lock (User_info.NeedAck) + { + foreach (Packet packet in User_info.NeedAck.Values) + { + if (now - packet.TickCount > Settings.RESEND_TIMEOUT) + { + // Client.Log("Resending " + packet.Type.ToString() + " packet, " + + // (now - packet.TickCount) + "ms have passed", Helpers.LogLevel.Info); + + //packet.Header.Resent = true; + // SendPacket(packet, false,User_info); + } + } + } + } + } + /// + /// Callback handler for incomming data + /// + /// + private void OnReceivedData(IAsyncResult result) + { + ipeSender = new IPEndPoint(IPAddress.Any, 0); + epSender = (EndPoint)ipeSender; + Packet packet = null; + int numBytes; + UserAgentInfo tempinfo; + + // If we're receiving data the sim connection is open + connected = true; + + // Update the disconnect flag so this sim doesn't time out + DisconnectCandidate = false; + UserAgentInfo User_info=null; + + lock (RecvBuffer) + { + // Retrieve the incoming packet + try + { + numBytes = Connection.EndReceiveFrom(result, ref epSender); + + //find user_agent_info + + int packetEnd = numBytes - 1; + packet = Packet.BuildPacket(RecvBuffer, ref packetEnd, ZeroBuffer); + + Console.WriteLine("INCOMING PACKET" + packet.TickCount.ToString() + " " + packet.Header.Sequence.ToString()); + Console.WriteLine(packet.ToString()); + libsecondlife.Packets.PacketAckPacket ack_it = new PacketAckPacket(); + ack_it.Packets = new PacketAckPacket.PacketsBlock[1]; + ack_it.Packets[0] = new PacketAckPacket.PacketsBlock(); + ack_it.Packets[0].ID = packet.Header.ID; + ack_it.Header.Reliable = false; + + tempinfo = new UserAgentInfo(); + tempinfo.endpoint = epSender; + this.SendPacket(ack_it, false, tempinfo); + if (packet.Header.Resent) + { + this.CallbackObject.ErrorCallback("resent"); + } + + if ((packet.Type == PacketType.StartPingCheck) && (packet.Header.Resent == false)) + { + //reply to pingcheck + libsecondlife.Packets.StartPingCheckPacket startping = (libsecondlife.Packets.StartPingCheckPacket)packet; + libsecondlife.Packets.CompletePingCheckPacket endping = new CompletePingCheckPacket(); + endping.PingID.PingID = startping.PingID.PingID; + endping.Header.Reliable = false; + tempinfo = new UserAgentInfo(); + tempinfo.endpoint = epSender; + this.SendPacket(endping, true, tempinfo); + } + + //should check if login/useconnection packet first + if ((packet.Type == PacketType.UseCircuitCode) && (packet.Header.Resent == false)) + { + Console.WriteLine("Got UseCircuitCode, confirming with grid..."); + UseCircuitCodePacket cir_pack=(UseCircuitCodePacket)packet; + + ArrayList requestParams = new ArrayList(); + requestParams.Add(Globals.Instance.GridSendKey); + requestParams.Add(cir_pack.CircuitCode.SessionID.ToString()); + requestParams.Add(cir_pack.CircuitCode.ID.ToString()); + + + Nwc.XmlRpc.XmlRpcRequest GridSessionInfo = new Nwc.XmlRpc.XmlRpcRequest("get_session_info",requestParams); + + Nwc.XmlRpc.XmlRpcResponse gridresponse = GridSessionInfo.Send(Globals.Instance.GridURL, 5000); + + Console.WriteLine("Processing response from grid server..."); + Hashtable gridreply = (Hashtable)gridresponse.Value; + if (gridresponse.IsFault) + { + Console.WriteLine("XML-RPC error when talking to grid: " + gridresponse.FaultString); + Connection.Disconnect(false); + } + if (((string)gridreply["agent_id"]).ToLower().Equals(cir_pack.CircuitCode.ID.ToString()) == false) + { + Console.WriteLine("Bad agent ID!"); + Connection.Disconnect(false); + } + else if (((string)gridreply["session_id"]).ToLower().Equals(cir_pack.CircuitCode.SessionID.ToString()) == false) + { + Console.WriteLine("Bad session ID!"); + Connection.Disconnect(false); + } + UserAgentInfo new_user = new UserAgentInfo(); + + new_user.AgentID = cir_pack.CircuitCode.ID; + new_user.circuitCode=cir_pack.CircuitCode.Code; + new_user.AgentID=cir_pack.CircuitCode.ID; + new_user.SessionID=cir_pack.CircuitCode.SessionID; + new_user.endpoint=epSender; + new_user.Inbox = new Queue(Settings.INBOX_SIZE); + new_user.first_name = (string)gridreply["firstname"]; + new_user.first_name = (string)gridreply["lastname"]; + + + this.CallbackObject.NewUserCallback(new_user); + this.User_agents.Add(new_user); + + } + + + UserAgentInfo temp_agent=null; + IPEndPoint send_ip=(IPEndPoint)epSender; + Console.WriteLine("incoming: address is "+send_ip.Address +"port number is: "+send_ip.Port.ToString()); + + for(int ii=0; ii Settings.MAX_PENDING_ACKS) + { + SendAcks(User_info); + } + + // Check if we already received this packet + if (User_info.Inbox.Contains(packet.Header.Sequence)) + { + //Client.Log("Received a duplicate " + packet.Type.ToString() + ", sequence=" + + // packet.Header.Sequence + ", resent=" + ((packet.Header.Resent) ? "Yes" : "No") + + // ", Inbox.Count=" + Inbox.Count + ", NeedAck.Count=" + NeedAck.Count, + // Helpers.LogLevel.Info); + + // Send an ACK for this packet immediately + + + // TESTING: Try just queuing up ACKs for resent packets instead of immediately triggering an ACK + /* lock (User_info.PendingAcks) + { + uint sequence = (uint)packet.Header.Sequence; + if (!User_info.PendingAcks.ContainsKey(sequence)) { User_info.PendingAcks[sequence] = sequence; } + }*/ + + // Avoid firing a callback twice for the same packet + this.CallbackObject.ErrorCallback("Avoiding callback"); + // this.callback_object.error("avoiding callback"); + return; + } + else + { + lock (User_info.PendingAcks) + { + uint sequence = (uint)packet.Header.Sequence; + // if (!User_info.PendingAcks.ContainsKey(sequence)) { User_info.PendingAcks[sequence] = sequence; } + } + } + } + + // Add this packet to our inbox + lock (User_info.Inbox) + { + while (User_info.Inbox.Count >= Settings.INBOX_SIZE) + { + User_info.Inbox.Dequeue(); + } + User_info.Inbox.Enqueue(packet.Header.Sequence); + } + + // Handle appended ACKs + if (packet.Header.AppendedAcks) + { + lock (User_info.NeedAck) + { + foreach (uint ack in packet.Header.AckList) + { + User_info.NeedAck.Remove(ack); + } + } + } + + // Handle PacketAck packets + if (packet.Type == PacketType.PacketAck) + { + PacketAckPacket ackPacket = (PacketAckPacket)packet; + + lock (User_info.NeedAck) + { + foreach (PacketAckPacket.PacketsBlock block in ackPacket.Packets) + { + User_info.NeedAck.Remove(block.ID); + } + } + } + + this.CallbackObject.MainCallback(packet,User_info); + + } + + private void AckTimer_Elapsed(object sender, ElapsedEventArgs ea) + { + if (connected) + { + + //TODO for each user_agent_info + for(int i=0; iThe version of libsecondlife (not the SL protocol itself) + public string VERSION = "libsecondlife 0.0.9"; + /// XML-RPC login server to connect to + public string LOGIN_SERVER = "http://www.garethnelson.com/ogs/login/"; + + /// Millisecond interval between ticks, where all ACKs are + /// sent out and the age of unACKed packets is checked + public readonly int NETWORK_TICK_LENGTH = 500; + /// The maximum value of a packet sequence number. After that + /// we assume the sequence number just rolls over? Or maybe the + /// protocol isn't able to sustain a connection past that + public readonly int MAX_SEQUENCE = 0xFFFFFF; + /// Number of milliseconds before a teleport attempt will time + /// out + public readonly int TELEPORT_TIMEOUT = 18 * 1000; + + /// Number of milliseconds before NetworkManager.Logout() will time out + public int LOGOUT_TIMEOUT = 5 * 1000; + /// Number of milliseconds for xml-rpc to timeout + public int LOGIN_TIMEOUT = 30 * 1000; + /// The maximum size of the sequence number inbox, used to + /// check for resent and/or duplicate packets + public int INBOX_SIZE = 100; + /// Milliseconds before a packet is assumed lost and resent + public int RESEND_TIMEOUT = 4000; + /// Milliseconds before the connection to a simulator is + /// assumed lost + public int SIMULATOR_TIMEOUT = 15000; + /// Maximum number of queued ACKs to be sent before SendAcks() + /// is forced + public int MAX_PENDING_ACKS = 10; + /// Maximum number of ACKs to append to a packet + public int MAX_APPENDED_ACKS = 10; + /// Cost of uploading an asset + public int UPLOAD_COST { get { return priceUpload; } } + + + private int priceUpload = 0; + + public Server_Settings() + { + + } + } + + public class UserAgentInfo + { + public EndPoint endpoint; + public LLUUID AgentID; + public LLUUID SessionID; + public uint circuitCode; + public string name; + public uint localID; + public string first_name; + public string last_name; + + public Dictionary NeedAck = new Dictionary(); + // Sequence numbers of packets we've received from the simulator + public Queue Inbox; + // ACKs that are queued up to be sent to the simulator + public Dictionary PendingAcks = new Dictionary(); + + public UserAgentInfo() + { + + } + } + +} -- cgit v1.1