diff --git a/Programs/Simian/Agent.cs b/Programs/Simian/Agent.cs index b576c30e..2330ada7 100644 --- a/Programs/Simian/Agent.cs +++ b/Programs/Simian/Agent.cs @@ -7,7 +7,7 @@ using OpenMetaverse.Packets; namespace Simian { - public class Agent : IDisposable + public class Agent { public UUID AgentID; public UUID SessionID; @@ -31,262 +31,5 @@ namespace Simian public PrimFlags Flags; public UUID InventoryRoot; public UUID InventoryLibRoot; - - public bool Disposed { get { return disposed; } } - bool disposed = false; - - /// Sequence numbers of packets we've received (for duplicate checking) - internal Queue packetArchive = new Queue(); - /// Packets we have sent that need to be ACKed by the client - internal Dictionary needAcks = new Dictionary(); - - UDPServer udpServer; - IPEndPoint address; - /// ACKs that are queued up, waiting to be sent to the client - SortedList pendingAcks = new SortedList(); - int currentSequence = 0; - Timer ackTimer; - - public IPEndPoint Address - { - get { return address; } - set { address = value; } - } - - public Agent(UDPServer udpServer) - { - this.udpServer = udpServer; - } - - public void Initialize(IPEndPoint address) - { - this.address = address; - - ackTimer = new Timer(new TimerCallback(AckTimer_Elapsed), null, Settings.NETWORK_TICK_INTERVAL, - Settings.NETWORK_TICK_INTERVAL); - } - - public void Dispose() - { - disposed = true; - ackTimer.Dispose(); - packetArchive.Clear(); - needAcks.Clear(); - } - - public void SendPacket(Packet packet) - { - SendPacket(packet, true); - } - - public void SendPacket(Packet packet, bool setSequence) - { - byte[] buffer; - int bytes; - - // Keep track of when this packet was sent out - packet.TickCount = Environment.TickCount; - - if (setSequence) - { - // Reset to zero if we've hit the upper sequence number limit - Interlocked.CompareExchange(ref currentSequence, 0, 0xFFFFFF); - // Increment and fetch the current sequence number - uint sequence = (uint)Interlocked.Increment(ref currentSequence); - packet.Header.Sequence = sequence; - - if (packet.Header.Reliable) - { - // Add this packet to the list of ACK responses we are waiting on from the client - lock (needAcks) - needAcks[sequence] = packet; - - if (packet.Header.Resent) - { - // This packet has already been sent out once, strip any appended ACKs - // off it and reinsert them into the outgoing ACK queue under the - // assumption that this packet will continually be rejected from the - // client or that the appended ACKs are possibly making the delivery fail - if (packet.Header.AckList.Length > 0) - { - Logger.DebugLog(String.Format("Purging ACKs from packet #{0} ({1}) which will be resent.", - packet.Header.Sequence, packet.GetType())); - - lock (pendingAcks) - { - foreach (uint ack in packet.Header.AckList) - { - if (!pendingAcks.ContainsKey(ack)) - pendingAcks[ack] = ack; - } - } - - packet.Header.AppendedAcks = false; - packet.Header.AckList = new uint[0]; - } - } - else - { - // This packet is not a resend, check if the conditions are favorable - // to ACK appending - if (packet.Type != PacketType.PacketAck) - { - lock (pendingAcks) - { - if (pendingAcks.Count > 0 && - pendingAcks.Count < 10) - { - // Append all of the queued up outgoing ACKs to this packet - 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; - } - } - } - } - } - else if (packet.Header.AckList.Length > 0) - { - // Sanity check for ACKS appended on an unreliable packet, this is bad form - Logger.Log("Sending appended ACKs on an unreliable packet", Helpers.LogLevel.Warning); - } - } - - // Serialize the packet - buffer = packet.ToBytes(); - bytes = buffer.Length; - //Stats.SentBytes += (ulong)bytes; - //++Stats.SentPackets; - - UDPPacketBuffer buf = new UDPPacketBuffer(address); - - // Zerocode if needed - if (packet.Header.Zerocoded) - bytes = Helpers.ZeroEncode(buffer, bytes, buf.Data); - else - Buffer.BlockCopy(buffer, 0, buf.Data, 0, bytes); - - buf.DataLength = bytes; - - udpServer.AsyncBeginSend(buf); - } - - public void QueueAck(uint ack) - { - // Add this packet to the list of ACKs that need to be sent out - lock (pendingAcks) - pendingAcks[ack] = ack; - - // Send out ACKs if we have a lot of them - if (pendingAcks.Count >= 10) - SendAcks(); - } - - public void ProcessAcks(List acks) - { - lock (needAcks) - { - foreach (uint ack in acks) - needAcks.Remove(ack); - } - } - - public void SendAck(uint ack) - { - PacketAckPacket acks = new PacketAckPacket(); - acks.Header.Reliable = false; - acks.Packets = new PacketAckPacket.PacketsBlock[1]; - acks.Packets[0] = new PacketAckPacket.PacketsBlock(); - acks.Packets[0].ID = ack; - - SendPacket(acks, true); - } - - void SendAcks() - { - PacketAckPacket acks = null; - - lock (pendingAcks) - { - if (pendingAcks.Count > 0) - { - if (pendingAcks.Count > 250) - { - Logger.Log("Too many ACKs queued up!", Helpers.LogLevel.Error); - return; - } - - 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]; - } - - pendingAcks.Clear(); - } - } - - if (acks != null) - SendPacket(acks, true); - } - - void ResendUnacked() - { - lock (needAcks) - { - List dropAck = new List(); - int now = Environment.TickCount; - - // Resend packets - foreach (Packet packet in needAcks.Values) - { - if (packet.TickCount != 0 && now - packet.TickCount > 4000) - { - if (packet.ResendCount < 3) - { - Logger.DebugLog(String.Format("Resending packet #{0} ({1}), {2}ms have passed", - packet.Header.Sequence, packet.GetType(), now - packet.TickCount)); - - packet.TickCount = 0; - packet.Header.Resent = true; - //++Stats.ResentPackets; - ++packet.ResendCount; - - SendPacket(packet, false); - } - else - { - Logger.Log(String.Format("Dropping packet #{0} ({1}) after {2} failed attempts", - packet.Header.Sequence, packet.GetType(), packet.ResendCount), Helpers.LogLevel.Warning); - - dropAck.Add(packet.Header.Sequence); - } - } - } - - if (dropAck.Count != 0) - { - foreach (uint seq in dropAck) - needAcks.Remove(seq); - } - } - } - - private void AckTimer_Elapsed(object obj) - { - if (!this.Disposed) - { - SendAcks(); - ResendUnacked(); - } - } } } diff --git a/Programs/Simian/EventDictionary.cs b/Programs/Simian/EventDictionary.cs index 6941e5b4..898cadf1 100644 --- a/Programs/Simian/EventDictionary.cs +++ b/Programs/Simian/EventDictionary.cs @@ -18,14 +18,14 @@ namespace Simian private struct PacketCallbackWrapper { /// Callback to fire for this packet - public UDPServer.PacketCallback Callback; + public PacketCallback Callback; /// Reference to the agent that this packet came from public Agent Agent; /// The packet that needs to be processed public Packet Packet; } - private Dictionary _EventTable = new Dictionary(); + private Dictionary _EventTable = new Dictionary(); private WaitCallback _ThreadPoolCallback; /// @@ -43,7 +43,7 @@ namespace Simian /// incoming packet /// Packet type to register the handler for /// Callback to be fired - public void RegisterEvent(PacketType packetType, UDPServer.PacketCallback eventHandler) + public void RegisterEvent(PacketType packetType, PacketCallback eventHandler) { lock (_EventTable) { @@ -59,7 +59,7 @@ namespace Simian /// /// Packet type to unregister the handler for /// Callback to be unregistered - public void UnregisterEvent(PacketType packetType, UDPServer.PacketCallback eventHandler) + public void UnregisterEvent(PacketType packetType, PacketCallback eventHandler) { lock (_EventTable) { @@ -76,7 +76,7 @@ namespace Simian /// Agent this packet was received from internal void BeginRaiseEvent(PacketType packetType, Packet packet, Agent agent) { - UDPServer.PacketCallback callback; + PacketCallback callback; PacketCallbackWrapper wrapper; // Default handler first, if one exists diff --git a/Programs/Simian/Extensions/AssetManager.cs b/Programs/Simian/Extensions/AssetManager.cs index 1c8f5565..1315fa8b 100644 --- a/Programs/Simian/Extensions/AssetManager.cs +++ b/Programs/Simian/Extensions/AssetManager.cs @@ -21,10 +21,10 @@ namespace Simian.Extensions { LoadDefaultAssets(Server.DataDir); - Server.UDPServer.RegisterPacketCallback(PacketType.AssetUploadRequest, new UDPServer.PacketCallback(AssetUploadRequestHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.SendXferPacket, new UDPServer.PacketCallback(SendXferPacketHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.AbortXfer, new UDPServer.PacketCallback(AbortXferHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.TransferRequest, new UDPServer.PacketCallback(TransferRequestHandler)); + Server.UDP.RegisterPacketCallback(PacketType.AssetUploadRequest, new PacketCallback(AssetUploadRequestHandler)); + Server.UDP.RegisterPacketCallback(PacketType.SendXferPacket, new PacketCallback(SendXferPacketHandler)); + Server.UDP.RegisterPacketCallback(PacketType.AbortXfer, new PacketCallback(AbortXferHandler)); + Server.UDP.RegisterPacketCallback(PacketType.TransferRequest, new PacketCallback(TransferRequestHandler)); } public void Stop() @@ -59,7 +59,7 @@ namespace Simian.Extensions complete.AssetBlock.Success = true; complete.AssetBlock.Type = request.AssetBlock.Type; complete.AssetBlock.UUID = request.AssetBlock.TransactionID; - agent.SendPacket(complete); + Server.UDP.SendPacket(agent.AgentID, complete, PacketCategory.Inventory); } else { @@ -85,7 +85,7 @@ namespace Simian.Extensions lock (CurrentUploads) CurrentUploads[xfer.XferID.ID] = asset; - agent.SendPacket(xfer); + Server.UDP.SendPacket(agent.AgentID, xfer, PacketCategory.Inventory); } } @@ -114,7 +114,7 @@ namespace Simian.Extensions ConfirmXferPacketPacket confirm = new ConfirmXferPacketPacket(); confirm.XferID.ID = xfer.XferID.ID; confirm.XferID.Packet = xfer.XferID.Packet; - agent.SendPacket(confirm); + Server.UDP.SendPacket(agent.AgentID, confirm, PacketCategory.Asset); } else { @@ -125,7 +125,7 @@ namespace Simian.Extensions ConfirmXferPacketPacket confirm = new ConfirmXferPacketPacket(); confirm.XferID.ID = xfer.XferID.ID; confirm.XferID.Packet = xfer.XferID.Packet; - agent.SendPacket(confirm); + Server.UDP.SendPacket(agent.AgentID, confirm, PacketCategory.Asset); if ((xfer.XferID.Packet & (uint)0x80000000) != 0) { @@ -142,7 +142,7 @@ namespace Simian.Extensions complete.AssetBlock.Success = true; complete.AssetBlock.Type = (sbyte)asset.AssetType; complete.AssetBlock.UUID = asset.AssetID; - agent.SendPacket(complete); + Server.UDP.SendPacket(agent.AgentID, complete, PacketCategory.Asset); } } } @@ -218,7 +218,7 @@ namespace Simian.Extensions response.TransferInfo.Status = (int)StatusCode.OK; response.TransferInfo.TargetType = (int)TargetType.Unknown; // Doesn't seem to be used by the client - agent.SendPacket(response); + Server.UDP.SendPacket(agent.AgentID, response, PacketCategory.Asset); // Transfer system does not wait for ACKs, just sends all of the // packets for this transfer out @@ -242,7 +242,7 @@ namespace Simian.Extensions else transfer.TransferData.Status = (int)StatusCode.OK; - agent.SendPacket(transfer); + Server.UDP.SendPacket(agent.AgentID, transfer, PacketCategory.Asset); } } else @@ -259,7 +259,7 @@ namespace Simian.Extensions response.TransferInfo.Status = (int)StatusCode.UnknownSource; response.TransferInfo.TargetType = (int)TargetType.Unknown; - agent.SendPacket(response); + Server.UDP.SendPacket(agent.AgentID, response, PacketCategory.Asset); } } else if (source == SourceType.SimEstate) diff --git a/Programs/Simian/Extensions/AvatarManager.cs b/Programs/Simian/Extensions/AvatarManager.cs index 3af54e07..de92e1fc 100644 --- a/Programs/Simian/Extensions/AvatarManager.cs +++ b/Programs/Simian/Extensions/AvatarManager.cs @@ -19,13 +19,13 @@ namespace Simian.Extensions public void Start() { - Server.UDPServer.RegisterPacketCallback(PacketType.AvatarPropertiesRequest, new UDPServer.PacketCallback(AvatarPropertiesRequestHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.AgentWearablesRequest, new UDPServer.PacketCallback(AgentWearablesRequestHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.AgentIsNowWearing, new UDPServer.PacketCallback(AgentIsNowWearingHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.AgentSetAppearance, new UDPServer.PacketCallback(AgentSetAppearanceHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.AgentAnimation, new UDPServer.PacketCallback(AgentAnimationHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.ViewerEffect, new UDPServer.PacketCallback(ViewerEffectHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.UUIDNameRequest, new UDPServer.PacketCallback(UUIDNameRequestHandler)); + Server.UDP.RegisterPacketCallback(PacketType.AvatarPropertiesRequest, new PacketCallback(AvatarPropertiesRequestHandler)); + Server.UDP.RegisterPacketCallback(PacketType.AgentWearablesRequest, new PacketCallback(AgentWearablesRequestHandler)); + Server.UDP.RegisterPacketCallback(PacketType.AgentIsNowWearing, new PacketCallback(AgentIsNowWearingHandler)); + Server.UDP.RegisterPacketCallback(PacketType.AgentSetAppearance, new PacketCallback(AgentSetAppearanceHandler)); + Server.UDP.RegisterPacketCallback(PacketType.AgentAnimation, new PacketCallback(AgentAnimationHandler)); + Server.UDP.RegisterPacketCallback(PacketType.ViewerEffect, new PacketCallback(ViewerEffectHandler)); + Server.UDP.RegisterPacketCallback(PacketType.UUIDNameRequest, new PacketCallback(UUIDNameRequestHandler)); } public void Stop() @@ -67,11 +67,7 @@ namespace Simian.Extensions sendAnim.AnimationList[i].AnimSequenceID = sequenceNums[i]; } - lock (Server.Agents) - { - foreach (Agent recipient in Server.Agents.Values) - recipient.SendPacket(sendAnim); - } + Server.UDP.BroadcastPacket(sendAnim, PacketCategory.State); } void AgentAnimationHandler(Packet packet, Agent agent) @@ -106,12 +102,7 @@ namespace Simian.Extensions effect.AgentData.AgentID = UUID.Zero; effect.AgentData.SessionID = UUID.Zero; - // Broadcast this to everyone - lock (Server.Agents) - { - foreach (Agent recipient in Server.Agents.Values) - recipient.SendPacket(effect); - } + Server.UDP.BroadcastPacket(effect, PacketCategory.State); } void AvatarPropertiesRequestHandler(Packet packet, Agent agent) @@ -139,8 +130,7 @@ namespace Simian.Extensions reply.PropertiesData.PartnerID = UUID.Zero; reply.PropertiesData.ProfileURL = Utils.StringToBytes(String.Empty); - agent.SendPacket(reply); - + Server.UDP.SendPacket(agent.AgentID, reply, PacketCategory.Transaction); break; } } @@ -182,7 +172,7 @@ namespace Simian.Extensions update.WearableData[4].ItemID = UUID.Random(); update.WearableData[4].WearableType = (byte)WearableType.Skin; - agent.SendPacket(update); + Server.UDP.SendPacket(agent.AgentID, update, PacketCategory.Asset); } void AgentIsNowWearingHandler(Packet packet, Agent agent) @@ -218,19 +208,11 @@ namespace Simian.Extensions ObjectUpdatePacket update = Movement.BuildFullUpdate(agent.Avatar, NameValue.NameValuesToString(agent.Avatar.NameValues), Server.RegionHandle, agent.State, agent.Flags | PrimFlags.ObjectYouOwner); - agent.SendPacket(update); + Server.UDP.SendPacket(agent.AgentID, update, PacketCategory.State); // Send out this appearance to all other connected avatars AvatarAppearancePacket appearance = BuildAppearancePacket(agent); - - lock (Server.Agents) - { - foreach (Agent recipient in Server.Agents.Values) - { - if (recipient != agent) - recipient.SendPacket(appearance); - } - } + Server.UDP.BroadcastPacket(appearance, PacketCategory.State); } void UUIDNameRequestHandler(Packet packet, Agent agent) @@ -268,7 +250,7 @@ namespace Simian.Extensions reply.UUIDNameBlock[i].LastName = Utils.StringToBytes(kvp.Value.LastName); } - agent.SendPacket(reply); + Server.UDP.SendPacket(agent.AgentID, reply, PacketCategory.Transaction); } } diff --git a/Programs/Simian/Extensions/CoarseLocationUpdates.cs b/Programs/Simian/Extensions/CoarseLocationUpdates.cs index 4439fb04..f883125a 100644 --- a/Programs/Simian/Extensions/CoarseLocationUpdates.cs +++ b/Programs/Simian/Extensions/CoarseLocationUpdates.cs @@ -33,33 +33,42 @@ namespace Simian.Extensions { lock (Server.Agents) { - List avatarPositions = new List(); - - CoarseLocationUpdatePacket update = new CoarseLocationUpdatePacket(); - update.AgentData = new CoarseLocationUpdatePacket.AgentDataBlock[Server.Agents.Count]; - update.Location = new CoarseLocationUpdatePacket.LocationBlock[Server.Agents.Count]; - - short i = 0; - foreach (Agent agent in Server.Agents.Values) - { - update.AgentData[i] = new CoarseLocationUpdatePacket.AgentDataBlock(); - update.AgentData[i].AgentID = agent.AgentID; - update.Location[i] = new CoarseLocationUpdatePacket.LocationBlock(); - update.Location[i].X = (byte)((int)agent.Avatar.Position.X); - update.Location[i].Y = (byte)((int)agent.Avatar.Position.Y); - update.Location[i].Z = (byte)((int)agent.Avatar.Position.Z / 4); - update.Index.Prey = -1; - i++; - } - - i = 0; foreach (Agent recipient in Server.Agents.Values) { - update.Index.You = i; - recipient.SendPacket(update); - i++; - } + int i = 0; + CoarseLocationUpdatePacket update = new CoarseLocationUpdatePacket(); + update.Index.Prey = -1; + update.Index.You = 0; + + update.AgentData = new CoarseLocationUpdatePacket.AgentDataBlock[Server.Agents.Count]; + update.Location = new CoarseLocationUpdatePacket.LocationBlock[Server.Agents.Count]; + + // Fill in this avatar + update.AgentData[0] = new CoarseLocationUpdatePacket.AgentDataBlock(); + update.AgentData[0].AgentID = recipient.AgentID; + update.Location[0] = new CoarseLocationUpdatePacket.LocationBlock(); + update.Location[0].X = (byte)((int)recipient.Avatar.Position.X); + update.Location[0].Y = (byte)((int)recipient.Avatar.Position.Y); + update.Location[0].Z = (byte)((int)recipient.Avatar.Position.Z / 4); + ++i; + + foreach (Agent agent in Server.Agents.Values) + { + if (agent != recipient) + { + update.AgentData[i] = new CoarseLocationUpdatePacket.AgentDataBlock(); + update.AgentData[i].AgentID = agent.AgentID; + update.Location[i] = new CoarseLocationUpdatePacket.LocationBlock(); + update.Location[i].X = (byte)((int)agent.Avatar.Position.X); + update.Location[i].Y = (byte)((int)agent.Avatar.Position.Y); + update.Location[i].Z = (byte)((int)agent.Avatar.Position.Z / 4); + ++i; + } + } + + Server.UDP.SendPacket(recipient.AgentID, update, PacketCategory.State); + } } } } diff --git a/Programs/Simian/Extensions/ConnectionManagement.cs b/Programs/Simian/Extensions/ConnectionManagement.cs index 9182ae9f..e41e28d4 100644 --- a/Programs/Simian/Extensions/ConnectionManagement.cs +++ b/Programs/Simian/Extensions/ConnectionManagement.cs @@ -15,9 +15,9 @@ namespace Simian.Extensions public void Start() { - server.UDPServer.RegisterPacketCallback(PacketType.UseCircuitCode, new UDPServer.PacketCallback(UseCircuitCodeHandler)); - server.UDPServer.RegisterPacketCallback(PacketType.StartPingCheck, new UDPServer.PacketCallback(StartPingCheckHandler)); - server.UDPServer.RegisterPacketCallback(PacketType.LogoutRequest, new UDPServer.PacketCallback(LogoutRequestHandler)); + server.UDP.RegisterPacketCallback(PacketType.UseCircuitCode, new PacketCallback(UseCircuitCodeHandler)); + server.UDP.RegisterPacketCallback(PacketType.StartPingCheck, new PacketCallback(StartPingCheckHandler)); + server.UDP.RegisterPacketCallback(PacketType.LogoutRequest, new PacketCallback(LogoutRequestHandler)); } @@ -54,7 +54,7 @@ namespace Simian.Extensions handshake.RegionInfo.TerrainStartHeight11 = 40f; handshake.RegionInfo2.RegionID = UUID.Random(); - agent.SendPacket(handshake); + server.UDP.SendPacket(agent.AgentID, handshake, PacketCategory.Transaction); } void StartPingCheckHandler(Packet packet, Agent agent) @@ -65,7 +65,7 @@ namespace Simian.Extensions complete.Header.Reliable = false; complete.PingID.PingID = start.PingID.PingID; - agent.SendPacket(complete); + server.UDP.SendPacket(agent.AgentID, complete, PacketCategory.Overhead); } void LogoutRequestHandler(Packet packet, Agent agent) @@ -79,24 +79,24 @@ namespace Simian.Extensions reply.InventoryData[0] = new LogoutReplyPacket.InventoryDataBlock(); reply.InventoryData[0].ItemID = UUID.Zero; - agent.SendPacket(reply); + server.UDP.SendPacket(agent.AgentID, reply, PacketCategory.Transaction); lock (server.Agents) { - if (server.Agents.ContainsKey(agent.Address)) + if (server.Agents.ContainsKey(agent.AgentID)) { KillObjectPacket kill = new KillObjectPacket(); kill.ObjectData = new KillObjectPacket.ObjectDataBlock[1]; kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock(); kill.ObjectData[0].ID = agent.Avatar.LocalID; - agent.Dispose(); - server.Agents.Remove(agent.Address); + server.UDP.BroadcastPacket(kill, PacketCategory.State); - foreach (Agent recipient in server.Agents.Values) - recipient.SendPacket(kill); + server.Agents.Remove(agent.AgentID); } } + + server.UDP.RemoveClient(agent); } } } diff --git a/Programs/Simian/Extensions/ImageDelivery.cs b/Programs/Simian/Extensions/ImageDelivery.cs index 3fcb6e03..0cd5b364 100644 --- a/Programs/Simian/Extensions/ImageDelivery.cs +++ b/Programs/Simian/Extensions/ImageDelivery.cs @@ -20,7 +20,7 @@ namespace Simian.Extensions public void Start() { - Server.UDPServer.RegisterPacketCallback(PacketType.RequestImage, new UDPServer.PacketCallback(RequestImageHandler)); + Server.UDP.RegisterPacketCallback(PacketType.RequestImage, new PacketCallback(RequestImageHandler)); Bitmap defaultImage = new Bitmap(32, 32); Graphics gfx = Graphics.FromImage(defaultImage); @@ -69,7 +69,7 @@ namespace Simian.Extensions } imageData.ImageID.Size = (uint)imageData.ImageData.Data.Length; - agent.SendPacket(imageData); + Server.UDP.SendPacket(agent.AgentID, imageData, PacketCategory.Texture); } } } diff --git a/Programs/Simian/Extensions/InventoryManager.cs b/Programs/Simian/Extensions/InventoryManager.cs index 7d2504a8..835a08bc 100644 --- a/Programs/Simian/Extensions/InventoryManager.cs +++ b/Programs/Simian/Extensions/InventoryManager.cs @@ -16,10 +16,10 @@ namespace Simian.Extensions public void Start() { - Server.UDPServer.RegisterPacketCallback(PacketType.CreateInventoryItem, new UDPServer.PacketCallback(CreateInventoryItemHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.CreateInventoryFolder, new UDPServer.PacketCallback(CreateInventoryFolderHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.UpdateInventoryItem, new UDPServer.PacketCallback(UpdateInventoryItemHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.FetchInventoryDescendents, new UDPServer.PacketCallback(FetchInventoryDescendentsHandler)); + Server.UDP.RegisterPacketCallback(PacketType.CreateInventoryItem, new PacketCallback(CreateInventoryItemHandler)); + Server.UDP.RegisterPacketCallback(PacketType.CreateInventoryFolder, new PacketCallback(CreateInventoryFolderHandler)); + Server.UDP.RegisterPacketCallback(PacketType.UpdateInventoryItem, new PacketCallback(UpdateInventoryItemHandler)); + Server.UDP.RegisterPacketCallback(PacketType.FetchInventoryDescendents, new PacketCallback(FetchInventoryDescendentsHandler)); } public void Stop() @@ -275,7 +275,7 @@ namespace Simian.Extensions } } - agent.SendPacket(descendents); + Server.UDP.SendPacket(agent.AgentID, descendents, PacketCategory.Inventory); } } else @@ -358,8 +358,7 @@ namespace Simian.Extensions update.InventoryData[0].SaleType = (byte)item.SaleType; update.InventoryData[0].Type = (sbyte)item.AssetType; - agent.SendPacket(update); - + Server.UDP.SendPacket(agent.AgentID, update, PacketCategory.Inventory); return item.ID; } else diff --git a/Programs/Simian/Extensions/Messaging.cs b/Programs/Simian/Extensions/Messaging.cs index d03f09cd..f45ff251 100644 --- a/Programs/Simian/Extensions/Messaging.cs +++ b/Programs/Simian/Extensions/Messaging.cs @@ -18,8 +18,8 @@ namespace Simian.Extensions public void Start() { - Server.UDPServer.RegisterPacketCallback(PacketType.ChatFromViewer, new UDPServer.PacketCallback(ChatFromViewerHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.ImprovedInstantMessage, new UDPServer.PacketCallback(ImprovedInstantMessageHandler)); + Server.UDP.RegisterPacketCallback(PacketType.ChatFromViewer, new PacketCallback(ChatFromViewerHandler)); + Server.UDP.RegisterPacketCallback(PacketType.ImprovedInstantMessage, new PacketCallback(ImprovedInstantMessageHandler)); } public void Stop() @@ -44,11 +44,7 @@ namespace Simian.Extensions chat.ChatData.FromName = Utils.StringToBytes(agent.Avatar.Name); chat.ChatData.Message = viewerChat.ChatData.Message; - lock (Server.Agents) - { - foreach(Agent recipient in Server.Agents.Values) - recipient.SendPacket(chat); - } + Server.UDP.BroadcastPacket(chat, PacketCategory.Transaction); } void ImprovedInstantMessageHandler(Packet packet, Agent agent) @@ -76,8 +72,9 @@ namespace Simian.Extensions sendIM.MessageBlock.Message = im.MessageBlock.Message; sendIM.MessageBlock.BinaryBucket = new byte[0]; sendIM.MessageBlock.Timestamp = 0; - sendIM.MessageBlock.Position = agent.Avatar.Position; - recipient.SendPacket(sendIM); + sendIM.MessageBlock.Position = agent.Avatar.Position; + + Server.UDP.SendPacket(recipient.AgentID, sendIM, PacketCategory.Transaction); break; } diff --git a/Programs/Simian/Extensions/Money.cs b/Programs/Simian/Extensions/Money.cs index 30c1c5c3..9f6381c3 100644 --- a/Programs/Simian/Extensions/Money.cs +++ b/Programs/Simian/Extensions/Money.cs @@ -17,8 +17,8 @@ namespace Simian.Extensions public void Start() { - Server.UDPServer.RegisterPacketCallback(PacketType.MoneyBalanceRequest, new UDPServer.PacketCallback(MoneyBalanceRequestHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.MoneyTransferRequest, new UDPServer.PacketCallback(MoneyTransferRequestHandler)); + Server.UDP.RegisterPacketCallback(PacketType.MoneyBalanceRequest, new PacketCallback(MoneyBalanceRequestHandler)); + Server.UDP.RegisterPacketCallback(PacketType.MoneyTransferRequest, new PacketCallback(MoneyTransferRequestHandler)); } public void Stop() @@ -33,7 +33,7 @@ namespace Simian.Extensions reply.MoneyData.TransactionID = transactionID; reply.MoneyData.Description = Utils.StringToBytes(message); - agent.SendPacket(reply); + Server.UDP.SendPacket(agent.AgentID, reply, PacketCategory.Transaction); } void MoneyBalanceRequestHandler(Packet packet, Agent agent) diff --git a/Programs/Simian/Extensions/Movement.cs b/Programs/Simian/Extensions/Movement.cs index f8af1319..e437065c 100644 --- a/Programs/Simian/Extensions/Movement.cs +++ b/Programs/Simian/Extensions/Movement.cs @@ -41,9 +41,9 @@ namespace Simian.Extensions public void Start() { - server.UDPServer.RegisterPacketCallback(PacketType.AgentUpdate, new UDPServer.PacketCallback(AgentUpdateHandler)); - server.UDPServer.RegisterPacketCallback(PacketType.AgentHeightWidth, new UDPServer.PacketCallback(AgentHeightWidthHandler)); - server.UDPServer.RegisterPacketCallback(PacketType.SetAlwaysRun, new UDPServer.PacketCallback(SetAlwaysRunHandler)); + server.UDP.RegisterPacketCallback(PacketType.AgentUpdate, new PacketCallback(AgentUpdateHandler)); + server.UDP.RegisterPacketCallback(PacketType.AgentHeightWidth, new PacketCallback(AgentHeightWidthHandler)); + server.UDP.RegisterPacketCallback(PacketType.SetAlwaysRun, new PacketCallback(SetAlwaysRunHandler)); updateTimer = new Timer(new TimerCallback(UpdateTimer_Elapsed)); LastTick = Environment.TickCount; @@ -324,11 +324,7 @@ namespace Simian.Extensions NameValue.NameValuesToString(agent.Avatar.NameValues), server.RegionHandle, agent.State, agent.Flags); - lock (server.Agents) - { - foreach (Agent recipient in server.Agents.Values) - recipient.SendPacket(fullUpdate); - } + server.UDP.BroadcastPacket(fullUpdate, PacketCategory.State); } void SetAlwaysRunHandler(Packet packet, Agent agent) diff --git a/Programs/Simian/Extensions/ObjectManager.cs b/Programs/Simian/Extensions/ObjectManager.cs index 8e8d041d..2d77f166 100644 --- a/Programs/Simian/Extensions/ObjectManager.cs +++ b/Programs/Simian/Extensions/ObjectManager.cs @@ -21,13 +21,13 @@ namespace Simian.Extensions public void Start() { - Server.UDPServer.RegisterPacketCallback(PacketType.ObjectAdd, new UDPServer.PacketCallback(ObjectAddHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.ObjectSelect, new UDPServer.PacketCallback(ObjectSelectHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.ObjectDeselect, new UDPServer.PacketCallback(ObjectDeselectHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.ObjectShape, new UDPServer.PacketCallback(ObjectShapeHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.DeRezObject, new UDPServer.PacketCallback(DeRezObjectHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.MultipleObjectUpdate, new UDPServer.PacketCallback(MultipleObjectUpdateHandler)); - Server.UDPServer.RegisterPacketCallback(PacketType.RequestObjectPropertiesFamily, new UDPServer.PacketCallback(RequestObjectPropertiesFamilyHandler)); + Server.UDP.RegisterPacketCallback(PacketType.ObjectAdd, new PacketCallback(ObjectAddHandler)); + Server.UDP.RegisterPacketCallback(PacketType.ObjectSelect, new PacketCallback(ObjectSelectHandler)); + Server.UDP.RegisterPacketCallback(PacketType.ObjectDeselect, new PacketCallback(ObjectDeselectHandler)); + Server.UDP.RegisterPacketCallback(PacketType.ObjectShape, new PacketCallback(ObjectShapeHandler)); + Server.UDP.RegisterPacketCallback(PacketType.DeRezObject, new PacketCallback(DeRezObjectHandler)); + Server.UDP.RegisterPacketCallback(PacketType.MultipleObjectUpdate, new PacketCallback(MultipleObjectUpdateHandler)); + Server.UDP.RegisterPacketCallback(PacketType.RequestObjectPropertiesFamily, new PacketCallback(RequestObjectPropertiesFamilyHandler)); } public void Stop() @@ -172,7 +172,7 @@ namespace Simian.Extensions // Send an update out to the creator ObjectUpdatePacket updateToOwner = Movement.BuildFullUpdate(prim, String.Empty, prim.RegionHandle, 0, prim.Flags | PrimFlags.CreateSelected | PrimFlags.ObjectYouOwner); - agent.SendPacket(updateToOwner); + Server.UDP.SendPacket(agent.AgentID, updateToOwner, PacketCategory.State); // Send an update out to everyone else ObjectUpdatePacket updateToOthers = Movement.BuildFullUpdate(prim, String.Empty, prim.RegionHandle, 0, @@ -182,7 +182,7 @@ namespace Simian.Extensions foreach (Agent recipient in Server.Agents.Values) { if (recipient != agent) - recipient.SendPacket(updateToOthers); + Server.UDP.SendPacket(recipient.AgentID, updateToOthers, PacketCategory.State); } } } @@ -223,7 +223,7 @@ namespace Simian.Extensions } } - agent.SendPacket(properties); + Server.UDP.SendPacket(agent.AgentID, properties, PacketCategory.Transaction); } void ObjectDeselectHandler(Packet packet, Agent agent) @@ -266,11 +266,7 @@ namespace Simian.Extensions // Send the update out to everyone ObjectUpdatePacket editedobj = Movement.BuildFullUpdate(obj.Prim, String.Empty, obj.Prim.RegionHandle, 0, obj.Prim.Flags); - lock (Server.Agents) - { - foreach (Agent recipient in Server.Agents.Values) - recipient.SendPacket(editedobj); - } + Server.UDP.BroadcastPacket(editedobj, PacketCategory.State); } else { @@ -408,11 +404,7 @@ namespace Simian.Extensions // Send the update out to everyone ObjectUpdatePacket editedobj = Movement.BuildFullUpdate(obj.Prim, String.Empty, obj.Prim.RegionHandle, 0, obj.Prim.Flags); - lock (Server.Agents) - { - foreach (Agent recipient in Server.Agents.Values) - recipient.SendPacket(editedobj); - } + Server.UDP.BroadcastPacket(editedobj, PacketCategory.State); } else { @@ -422,7 +414,7 @@ namespace Simian.Extensions kill.ObjectData = new KillObjectPacket.ObjectDataBlock[1]; kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock(); kill.ObjectData[0].ID = block.ObjectLocalID; - agent.SendPacket(kill); + Server.UDP.SendPacket(agent.AgentID, kill, PacketCategory.State); } } } @@ -453,7 +445,7 @@ namespace Simian.Extensions props.ObjectData.SalePrice = obj.Prim.Properties.SalePrice; props.ObjectData.SaleType = (byte)obj.Prim.Properties.SaleType; - agent.SendPacket(props); + Server.UDP.SendPacket(agent.AgentID, props, PacketCategory.Transaction); } else { @@ -474,11 +466,7 @@ namespace Simian.Extensions kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock(); kill.ObjectData[0].ID = obj.Prim.LocalID; - lock (Server.Agents) - { - foreach (Agent recipient in Server.Agents.Values) - recipient.SendPacket(kill); - } + Server.UDP.BroadcastPacket(kill, PacketCategory.State); } Vector3 FullSceneCollisionTest(Vector3 rayStart, Vector3 rayEnd) diff --git a/Programs/Simian/Extensions/SceneManager.cs b/Programs/Simian/Extensions/SceneManager.cs index 28ee8c5d..045e89c3 100644 --- a/Programs/Simian/Extensions/SceneManager.cs +++ b/Programs/Simian/Extensions/SceneManager.cs @@ -23,7 +23,7 @@ namespace Simian.Extensions public void Start() { - server.UDPServer.RegisterPacketCallback(PacketType.CompleteAgentMovement, new UDPServer.PacketCallback(CompleteAgentMovementHandler)); + server.UDP.RegisterPacketCallback(PacketType.CompleteAgentMovement, new PacketCallback(CompleteAgentMovementHandler)); LoadTerrain(server.DataDir + "heightmap.tga"); } @@ -72,7 +72,7 @@ namespace Simian.Extensions complete.Data.Timestamp = Utils.DateTimeToUnixTime(DateTime.Now); complete.SimData.ChannelVersion = Utils.StringToBytes("Simian"); - agent.SendPacket(complete); + server.UDP.SendPacket(agent.AgentID, complete, PacketCategory.Transaction); lock (server.Agents) { @@ -82,11 +82,11 @@ namespace Simian.Extensions ObjectUpdatePacket update = Movement.BuildFullUpdate(otherAgent.Avatar, NameValue.NameValuesToString(otherAgent.Avatar.NameValues), server.RegionHandle, otherAgent.State, otherAgent.Flags); - agent.SendPacket(update); + server.UDP.SendPacket(agent.AgentID, update, PacketCategory.State); // Send appearances for this avatar AvatarAppearancePacket appearance = AvatarManager.BuildAppearancePacket(otherAgent); - agent.SendPacket(appearance); + server.UDP.SendPacket(agent.AgentID, appearance, PacketCategory.State); } } @@ -136,11 +136,10 @@ namespace Simian.Extensions int[] patches = new int[1]; patches[0] = (y * 16) + x; LayerDataPacket layer = TerrainCompressor.CreateLandPacket(server.Heightmap, patches); - agent.SendPacket(layer); + server.UDP.SendPacket(agent.AgentID, layer, PacketCategory.Terrain); } } } } - } } diff --git a/Programs/Simian/Extensions/UDPServer.cs b/Programs/Simian/Extensions/UDPServer.cs new file mode 100644 index 00000000..ccc90dd1 --- /dev/null +++ b/Programs/Simian/Extensions/UDPServer.cs @@ -0,0 +1,551 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using OpenMetaverse; +using OpenMetaverse.Packets; + +namespace Simian +{ + public class UDPClient + { + /// + public Agent Agent; + /// + public IPEndPoint Address; + /// Sequence numbers of packets we've received (for duplicate checking) + public Queue PacketArchive = new Queue(); + /// Packets we have sent that need to be ACKed by the client + public Dictionary NeedAcks = new Dictionary(); + /// ACKs that are queued up, waiting to be sent to the client + public SortedList PendingAcks = new SortedList(); + /// Current packet sequence number + public int CurrentSequence = 0; + + Timer ackTimer; + UDPServer udpServer; + + public UDPClient(UDPServer server, Agent agent, IPEndPoint address) + { + udpServer = server; + Agent = agent; + Address = address; + ackTimer = new Timer(new TimerCallback(AckTimer_Elapsed), null, Settings.NETWORK_TICK_INTERVAL, + Settings.NETWORK_TICK_INTERVAL); + } + + public void Dispose() + { + ackTimer.Dispose(); + } + + private void AckTimer_Elapsed(object obj) + { + udpServer.SendAcks(this); + udpServer.ResendUnacked(this); + } + } + + public struct IncomingPacket + { + public UDPClient Client; + public Packet Packet; + } + + public class UDPManager : ISimianExtension, IUDPProvider + { + Simian Server; + UDPServer udpServer; + + public UDPManager(Simian server) + { + Server = server; + // Have to do this in the constructor, because we don't know that the + // UDP extension will be started before other extensions + udpServer = new UDPServer(Server.UDPPort, Server); + } + + public void Start() + { + } + + public void Stop() + { + udpServer.Stop(); + } + + public void AddClient(Agent agent, IPEndPoint endpoint) + { + udpServer.AddClient(agent, endpoint); + } + + public bool RemoveClient(Agent agent) + { + return udpServer.RemoveClient(agent); + } + + public bool RemoveClient(Agent agent, IPEndPoint endpoint) + { + return udpServer.RemoveClient(agent, endpoint); + } + + public void SendPacket(UUID agentID, Packet packet, PacketCategory category) + { + udpServer.SendPacket(agentID, packet, category); + } + + public void BroadcastPacket(Packet packet, PacketCategory category) + { + udpServer.BroadcastPacket(packet, category); + } + + public void RegisterPacketCallback(PacketType type, PacketCallback callback) + { + udpServer.RegisterPacketCallback(type, callback); + } + } + + public class UDPServer : UDPBase + { + /// This is only used to fetch unassociated agents, which will + /// be exposed through a login interface at some point + Simian server; + /// Handlers for incoming packets + PacketEventDictionary packetEvents = new PacketEventDictionary(); + /// Incoming packets that are awaiting handling + BlockingQueue packetInbox = new BlockingQueue(Settings.PACKET_INBOX_SIZE); + /// + DoubleDictionary clients = new DoubleDictionary(); + + public UDPServer(int port, Simian server) + : base(port) + { + this.server = server; + + Start(); + + // Start the incoming packet processing thread + Thread incomingThread = new Thread(new ThreadStart(IncomingPacketHandler)); + incomingThread.Start(); + } + + public void RegisterPacketCallback(PacketType type, PacketCallback callback) + { + packetEvents.RegisterEvent(type, callback); + } + + public void AddClient(Agent agent, IPEndPoint endpoint) + { + UDPClient client = new UDPClient(this, agent, endpoint); + clients.Add(agent.AgentID, endpoint, client); + } + + public bool RemoveClient(Agent agent) + { + UDPClient client; + if (clients.TryGetValue(agent.AgentID, out client)) + return clients.Remove(agent.AgentID, client.Address); + else + return false; + } + + public bool RemoveClient(Agent agent, IPEndPoint endpoint) + { + return clients.Remove(agent.AgentID, endpoint); + } + + public void BroadcastPacket(Packet packet, PacketCategory category) + { + clients.ForEach( + delegate(UDPClient client) { SendPacket(client, packet, category, true); }); + } + + public void SendPacket(UUID agentID, Packet packet, PacketCategory category) + { + // Look up the UDPClient this is going to + UDPClient client; + if (!clients.TryGetValue(agentID, out client)) + { + Logger.Log("Attempted to send a packet to unknown UDP client " + + agentID.ToString(), Helpers.LogLevel.Warning); + return; + } + + SendPacket(client, packet, category, true); + } + + void SendPacket(UDPClient client, Packet packet, PacketCategory category, bool setSequence) + { + byte[] buffer; + int bytes; + + // Keep track of when this packet was sent out + packet.TickCount = Environment.TickCount; + + if (setSequence) + { + // Reset to zero if we've hit the upper sequence number limit + Interlocked.CompareExchange(ref client.CurrentSequence, 0, 0xFFFFFF); + // Increment and fetch the current sequence number + uint sequence = (uint)Interlocked.Increment(ref client.CurrentSequence); + packet.Header.Sequence = sequence; + + if (packet.Header.Reliable) + { + // Add this packet to the list of ACK responses we are waiting on from this client + lock (client.NeedAcks) + client.NeedAcks[sequence] = packet; + + if (packet.Header.Resent) + { + // This packet has already been sent out once, strip any appended ACKs + // off it and reinsert them into the outgoing ACK queue under the + // assumption that this packet will continually be rejected from the + // client or that the appended ACKs are possibly making the delivery fail + if (packet.Header.AckList.Length > 0) + { + Logger.DebugLog(String.Format("Purging ACKs from packet #{0} ({1}) which will be resent.", + packet.Header.Sequence, packet.GetType())); + + lock (client.PendingAcks) + { + foreach (uint ack in packet.Header.AckList) + { + if (!client.PendingAcks.ContainsKey(ack)) + client.PendingAcks[ack] = ack; + } + } + + packet.Header.AppendedAcks = false; + packet.Header.AckList = new uint[0]; + } + } + else + { + // This packet is not a resend, check if the conditions are favorable + // to ACK appending + if (packet.Type != PacketType.PacketAck) + { + lock (client.PendingAcks) + { + int count = client.PendingAcks.Count; + + if (count > 0 && count < 10) + { + // Append all of the queued up outgoing ACKs to this packet + packet.Header.AckList = new uint[count]; + + for (int i = 0; i < count; i++) + packet.Header.AckList[i] = client.PendingAcks.Values[i]; + + client.PendingAcks.Clear(); + packet.Header.AppendedAcks = true; + } + } + } + } + } + else if (packet.Header.AckList.Length > 0) + { + // Sanity check for ACKS appended on an unreliable packet, this is bad form + Logger.Log("Sending appended ACKs on an unreliable packet", Helpers.LogLevel.Warning); + } + } + + // Serialize the packet + buffer = packet.ToBytes(); + bytes = buffer.Length; + //Stats.SentBytes += (ulong)bytes; + //++Stats.SentPackets; + + UDPPacketBuffer buf = new UDPPacketBuffer(client.Address); + + // Zerocode if needed + if (packet.Header.Zerocoded) + bytes = Helpers.ZeroEncode(buffer, bytes, buf.Data); + else + Buffer.BlockCopy(buffer, 0, buf.Data, 0, bytes); + + buf.DataLength = bytes; + + AsyncBeginSend(buf); + } + + void QueueAck(UDPClient client, uint ack) + { + // Add this packet to the list of ACKs that need to be sent out + lock (client.PendingAcks) + client.PendingAcks[ack] = ack; + + // Send out ACKs if we have a lot of them + if (client.PendingAcks.Count >= 10) + SendAcks(client); + } + + void ProcessAcks(UDPClient client, List acks) + { + lock (client.NeedAcks) + { + foreach (uint ack in acks) + client.NeedAcks.Remove(ack); + } + } + + void SendAck(UDPClient client, uint ack) + { + PacketAckPacket acks = new PacketAckPacket(); + acks.Header.Reliable = false; + acks.Packets = new PacketAckPacket.PacketsBlock[1]; + acks.Packets[0] = new PacketAckPacket.PacketsBlock(); + acks.Packets[0].ID = ack; + + SendPacket(client, acks, PacketCategory.Overhead, true); + } + + public void SendAcks(UDPClient client) + { + PacketAckPacket acks = null; + + lock (client.PendingAcks) + { + int count = client.PendingAcks.Count; + + if (count > 250) + { + Logger.Log("Too many ACKs queued up!", Helpers.LogLevel.Error); + return; + } + else if (count > 0) + { + acks = new PacketAckPacket(); + acks.Header.Reliable = false; + acks.Packets = new PacketAckPacket.PacketsBlock[count]; + + for (int i = 0; i < count; i++) + { + acks.Packets[i] = new PacketAckPacket.PacketsBlock(); + acks.Packets[i].ID = client.PendingAcks.Values[i]; + } + + client.PendingAcks.Clear(); + } + } + + if (acks != null) + SendPacket(client, acks, PacketCategory.Overhead, true); + } + + public void ResendUnacked(UDPClient client) + { + lock (client.NeedAcks) + { + List dropAck = new List(); + int now = Environment.TickCount; + + // Resend packets + foreach (Packet packet in client.NeedAcks.Values) + { + if (packet.TickCount != 0 && now - packet.TickCount > 4000) + { + if (packet.ResendCount < 3) + { + Logger.DebugLog(String.Format("Resending packet #{0} ({1}), {2}ms have passed", + packet.Header.Sequence, packet.GetType(), now - packet.TickCount)); + + packet.TickCount = 0; + packet.Header.Resent = true; + //++Stats.ResentPackets; + ++packet.ResendCount; + + SendPacket(client, packet, PacketCategory.Overhead, false); + } + else + { + Logger.Log(String.Format("Dropping packet #{0} ({1}) after {2} failed attempts", + packet.Header.Sequence, packet.GetType(), packet.ResendCount), Helpers.LogLevel.Warning); + + dropAck.Add(packet.Header.Sequence); + } + } + } + + if (dropAck.Count != 0) + { + for (int i = 0; i < dropAck.Count; i++) + client.NeedAcks.Remove(dropAck[i]); + } + } + } + + protected override void PacketReceived(UDPPacketBuffer buffer) + { + UDPClient client = null; + Packet packet = null; + int packetEnd = buffer.DataLength - 1; + IPEndPoint address = (IPEndPoint)buffer.RemoteEndPoint; + + // Decoding + try + { + packet = Packet.BuildPacket(buffer.Data, ref packetEnd, buffer.ZeroData); + } + 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); + return; + } + + //Stats.RecvBytes += (ulong)buffer.DataLength; + //++Stats.RecvPackets; + + if (packet.Type == PacketType.UseCircuitCode) + { + UseCircuitCodePacket useCircuitCode = (UseCircuitCodePacket)packet; + + Agent agent; + if (server.CompleteAgentConnection(useCircuitCode.CircuitCode.Code, out agent)) + { + AddClient(agent, address); + if (clients.TryGetValue(agent.AgentID, out client)) + { + Logger.Log("Activated UDP circuit " + useCircuitCode.CircuitCode.Code, Helpers.LogLevel.Info); + } + else + { + Logger.Log("Failed to locate newly created UDPClient", Helpers.LogLevel.Error); + return; + } + } + else + { + Logger.Log("Received a UseCircuitCode packet for an unrecognized circuit: " + + useCircuitCode.CircuitCode.Code.ToString(), Helpers.LogLevel.Warning); + return; + } + } + else + { + // Determine which agent this packet came from + if (!clients.TryGetValue(address, out client)) + { + Logger.Log("Received UDP packet from an unrecognized source: " + address.ToString(), + Helpers.LogLevel.Warning); + return; + } + } + + // Reliable handling + if (packet.Header.Reliable) + { + // Queue up this sequence number for acknowledgement + QueueAck(client, (uint)packet.Header.Sequence); + //if (packet.Header.Resent) ++Stats.ReceivedResends; + } + + // Inbox insertion + IncomingPacket incomingPacket; + incomingPacket.Client = client; + incomingPacket.Packet = packet; + + // TODO: Prioritize the queue + packetInbox.Enqueue(incomingPacket); + } + + protected override void PacketSent(UDPPacketBuffer buffer, int bytesSent) + { + } + + private void IncomingPacketHandler() + { + IncomingPacket incomingPacket = new IncomingPacket(); + Packet packet = null; + UDPClient client = null; + + while (IsRunning) + { + // Reset packet to null for the check below + packet = null; + + if (packetInbox.Dequeue(100, ref incomingPacket)) + { + packet = incomingPacket.Packet; + client = incomingPacket.Client; + + if (packet != null) + { + #region ACK accounting + + // Check the archives to see whether we already received this packet + lock (client.PacketArchive) + { + if (client.PacketArchive.Contains(packet.Header.Sequence)) + { + if (packet.Header.Resent) + { + Logger.DebugLog("Received resent packet #" + packet.Header.Sequence); + } + else + { + Logger.Log(String.Format("Received a duplicate of packet #{0}, current type: {1}", + packet.Header.Sequence, packet.Type), Helpers.LogLevel.Warning); + } + + // Avoid firing a callback twice for the same packet + continue; + } + else + { + // Keep the PacketArchive size within a certain capacity + while (client.PacketArchive.Count >= Settings.PACKET_ARCHIVE_SIZE) + { + client.PacketArchive.Dequeue(); client.PacketArchive.Dequeue(); + client.PacketArchive.Dequeue(); client.PacketArchive.Dequeue(); + } + + client.PacketArchive.Enqueue(packet.Header.Sequence); + } + } + + #endregion ACK accounting + + #region ACK handling + + // Handle appended ACKs + if (packet.Header.AppendedAcks) + { + lock (client.NeedAcks) + { + for (int i = 0; i < packet.Header.AckList.Length; i++) + client.NeedAcks.Remove(packet.Header.AckList[i]); + } + } + + // Handle PacketAck packets + if (packet.Type == PacketType.PacketAck) + { + PacketAckPacket ackPacket = (PacketAckPacket)packet; + + lock (client.NeedAcks) + { + for (int i = 0; i < ackPacket.Packets.Length; i++) + client.NeedAcks.Remove(ackPacket.Packets[i].ID); + } + } + + #endregion ACK handling + + packetEvents.BeginRaiseEvent(packet.Type, packet, client.Agent); + } + } + } + } + } +} diff --git a/Programs/Simian/Interfaces/IUDPProvider.cs b/Programs/Simian/Interfaces/IUDPProvider.cs new file mode 100644 index 00000000..8ae50439 --- /dev/null +++ b/Programs/Simian/Interfaces/IUDPProvider.cs @@ -0,0 +1,47 @@ +using System; +using System.Net; +using OpenMetaverse; +using OpenMetaverse.Packets; + +namespace Simian +{ + public enum PacketCategory + { + /// Any sort of transactional message, such as + /// AgentMovementComplete + Transaction = 0, + /// State synchronization, such as animations or + /// object updates + State, + /// State synchronization of inventory + Inventory, + /// State synchronization of terrain, LayerData + /// packets + Terrain, + /// Asset transfer packets + Asset, + /// Texture transfer packets + Texture, + /// Protocol overhead such as PacketAck + Overhead, + } + + /// + /// Coupled with RegisterCallback(), this is triggered whenever a packet + /// of a registered type is received + /// + public delegate void PacketCallback(Packet packet, Agent agent); + + public interface IUDPProvider + { + void AddClient(Agent agent, IPEndPoint endpoint); + bool RemoveClient(Agent agent); + bool RemoveClient(Agent agent, IPEndPoint endpoint); + + void SendPacket(UUID agentID, Packet packet, PacketCategory category); + + void BroadcastPacket(Packet packet, PacketCategory category); + + void RegisterPacketCallback(PacketType type, PacketCallback callback); + } +} diff --git a/Programs/Simian/Simian.cs b/Programs/Simian/Simian.cs index 70684126..25f2d6a5 100644 --- a/Programs/Simian/Simian.cs +++ b/Programs/Simian/Simian.cs @@ -13,30 +13,30 @@ namespace Simian { public partial class Simian { + public int UDPPort = 9000; + public int HttpPort = 9000; public string DataDir = "SimianData/"; public HttpServer HttpServer; - public UDPServer UDPServer; public ulong RegionHandle; public float[] Heightmap = new float[65536]; public float WaterHeight = 35.0f; public Dictionary AssetStore = new Dictionary(); // Interfaces + public IUDPProvider UDP; public IAvatarProvider Avatars; public IInventoryProvider Inventory; public IMeshingProvider Mesher; /// All of the agents currently connected to this UDP server - public Dictionary Agents = new Dictionary(); + public Dictionary Agents = new Dictionary(); const uint regionX = 256000; const uint regionY = 256000; Dictionary unassociatedAgents; int currentCircuitCode; - int tcpPort; - int udpPort; public Simian() { @@ -46,12 +46,10 @@ namespace Simian public void Start(int port, bool ssl) { - // Put UDP listening on the same port number as the HTTP server for simplicity - tcpPort = port; - udpPort = port; + HttpPort = port; + UDPPort = port; - InitUDPServer(udpPort); - InitHttpServer(tcpPort, ssl); + InitHttpServer(HttpPort, ssl); RegionHandle = Helpers.UIntsToLong(regionX, regionY); @@ -60,13 +58,17 @@ namespace Simian foreach (ISimianExtension extension in ExtensionLoader.Extensions) { - Logger.DebugLog("Loading extension " + extension.GetType().Name); - extension.Start(); - // Assign to an interface if possible TryAssignToInterface(extension); } + foreach (ISimianExtension extension in ExtensionLoader.Extensions) + { + // Start the interfaces + Logger.DebugLog("Loading extension " + extension.GetType().Name); + extension.Start(); + } + if (!CheckInterfaces()) { Logger.Log("Missing interfaces, shutting down", Helpers.LogLevel.Error); @@ -79,10 +81,26 @@ namespace Simian foreach (ISimianExtension extension in ExtensionLoader.Extensions) extension.Stop(); - UDPServer.Stop(); HttpServer.Stop(); } + public bool CompleteAgentConnection(uint circuitCode, out Agent agent) + { + if (unassociatedAgents.TryGetValue(circuitCode, out agent)) + { + lock (Agents) + Agents[agent.AgentID] = agent; + lock (unassociatedAgents) + unassociatedAgents.Remove(circuitCode); + + return true; + } + else + { + return false; + } + } + public bool TryGetUnassociatedAgent(uint circuitCode, out Agent agent) { if (unassociatedAgents.TryGetValue(circuitCode, out agent)) @@ -100,7 +118,11 @@ namespace Simian void TryAssignToInterface(ISimianExtension extension) { - if (extension is IAvatarProvider) + if (extension is IUDPProvider) + { + UDP = (IUDPProvider)extension; + } + else if (extension is IAvatarProvider) { Avatars = (IAvatarProvider)extension; } @@ -116,7 +138,12 @@ namespace Simian bool CheckInterfaces() { - if (Avatars == null) + if (UDP == null) + { + Logger.Log("No IUDPProvider interface loaded", Helpers.LogLevel.Error); + return false; + } + else if (Avatars == null) { Logger.Log("No IAvatarProvider interface loaded", Helpers.LogLevel.Error); return false; @@ -135,14 +162,9 @@ namespace Simian return true; } - void InitUDPServer(int port) - { - UDPServer = new UDPServer(port, this); - } - void InitHttpServer(int port, bool ssl) { - HttpServer = new HttpServer(tcpPort, ssl); + HttpServer = new HttpServer(HttpPort, ssl); // Login webpage HEAD request, used to check if the login webpage is alive HttpRequestSignature signature = new HttpRequestSignature(); @@ -301,7 +323,7 @@ namespace Simian uint regionX = 256000; uint regionY = 256000; - Agent agent = new Agent(UDPServer); + Agent agent = new Agent(); agent.AgentID = UUID.Random(); agent.FirstName = firstName; agent.LastName = lastName; @@ -361,9 +383,9 @@ namespace Simian response.RegionY = regionY; response.SecondsSinceEpoch = DateTime.Now; // FIXME: Actually generate a seed capability - response.SeedCapability = String.Format("http://{0}:{1}/seed_caps", simIP, tcpPort); + response.SeedCapability = String.Format("http://{0}:{1}/seed_caps", simIP, HttpPort); response.SimIP = simIP; - response.SimPort = (ushort)udpPort; + response.SimPort = (ushort)UDPPort; response.StartLocation = "last"; response.Success = true; diff --git a/Programs/Simian/UDPServer.cs b/Programs/Simian/UDPServer.cs deleted file mode 100644 index c2a6a8a6..00000000 --- a/Programs/Simian/UDPServer.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using OpenMetaverse; -using OpenMetaverse.Packets; - -namespace Simian -{ - public struct IncomingPacket - { - public Agent Agent; - public Packet Packet; - } - - public class UDPServer : UDPBase - { - /// - /// Coupled with RegisterCallback(), this is triggered whenever a packet - /// of a registered type is received - /// - public delegate void PacketCallback(Packet packet, Agent agent); - - /// This is only used to fetch unassociated agents, which will - /// be exposed through a login interface at some point - Simian server; - /// Handlers for incoming packets - PacketEventDictionary packetEvents = new PacketEventDictionary(); - /// Incoming packets that are awaiting handling - BlockingQueue packetInbox = new BlockingQueue(Settings.PACKET_INBOX_SIZE); - - public UDPServer(int port, Simian server) - : base(port) - { - this.server = server; - - Start(); - - // Start the incoming packet processing thread - Thread incomingThread = new Thread(new ThreadStart(IncomingPacketHandler)); - incomingThread.Start(); - } - - public void RegisterPacketCallback(PacketType type, PacketCallback callback) - { - packetEvents.RegisterEvent(type, callback); - } - - protected override void PacketReceived(UDPPacketBuffer buffer) - { - Agent agent = null; - Packet packet = null; - int packetEnd = buffer.DataLength - 1; - - // Decoding - try - { - packet = Packet.BuildPacket(buffer.Data, ref packetEnd, buffer.ZeroData); - } - 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); - return; - } - - //Stats.RecvBytes += (ulong)buffer.DataLength; - //++Stats.RecvPackets; - - if (packet.Type == PacketType.UseCircuitCode) - { - UseCircuitCodePacket useCircuitCode = (UseCircuitCodePacket)packet; - - if (server.TryGetUnassociatedAgent(useCircuitCode.CircuitCode.Code, out agent)) - { - agent.Initialize((IPEndPoint)buffer.RemoteEndPoint); - - lock (server.Agents) - server.Agents[(IPEndPoint)buffer.RemoteEndPoint] = agent; - - Logger.Log("Activated UDP circuit " + useCircuitCode.CircuitCode.Code, Helpers.LogLevel.Info); - - //agent.SendAck(useCircuitCode.Header.Sequence); - } - else - { - Logger.Log("Received a UseCircuitCode packet for an unrecognized circuit: " + useCircuitCode.CircuitCode.Code.ToString(), - Helpers.LogLevel.Warning); - return; - } - } - else - { - // Determine which agent this packet came from - if (!server.Agents.TryGetValue((IPEndPoint)buffer.RemoteEndPoint, out agent)) - { - Logger.Log("Received UDP packet from an unrecognized source: " + ((IPEndPoint)buffer.RemoteEndPoint).ToString(), - Helpers.LogLevel.Warning); - return; - } - } - - // Reliable handling - if (packet.Header.Reliable) - { - // Queue up this sequence number for acknowledgement - agent.QueueAck((uint)packet.Header.Sequence); - - //if (packet.Header.Resent) ++Stats.ReceivedResends; - } - - // Inbox insertion - IncomingPacket incomingPacket; - incomingPacket.Agent = agent; - incomingPacket.Packet = packet; - - // TODO: Prioritize the queue - packetInbox.Enqueue(incomingPacket); - } - - protected override void PacketSent(UDPPacketBuffer buffer, int bytesSent) - { - } - - private void IncomingPacketHandler() - { - IncomingPacket incomingPacket = new IncomingPacket(); - Packet packet = null; - Agent agent = null; - - while (IsRunning) - { - // Reset packet to null for the check below - packet = null; - - if (packetInbox.Dequeue(100, ref incomingPacket)) - { - packet = incomingPacket.Packet; - agent = incomingPacket.Agent; - - if (packet != null) - { - #region ACK accounting - - // Check the archives to see whether we already received this packet - lock (agent.packetArchive) - { - if (agent.packetArchive.Contains(packet.Header.Sequence)) - { - if (packet.Header.Resent) - { - Logger.DebugLog("Received resent packet #" + packet.Header.Sequence); - } - else - { - Logger.Log(String.Format("Received a duplicate of packet #{0}, current type: {1}", - packet.Header.Sequence, packet.Type), Helpers.LogLevel.Warning); - } - - // Avoid firing a callback twice for the same packet - continue; - } - else - { - // Keep the PacketArchive size within a certain capacity - while (agent.packetArchive.Count >= Settings.PACKET_ARCHIVE_SIZE) - { - agent.packetArchive.Dequeue(); agent.packetArchive.Dequeue(); - agent.packetArchive.Dequeue(); agent.packetArchive.Dequeue(); - } - - agent.packetArchive.Enqueue(packet.Header.Sequence); - } - } - - #endregion ACK accounting - - #region ACK handling - - // Handle appended ACKs - if (packet.Header.AppendedAcks) - { - lock (agent.needAcks) - { - for (int i = 0; i < packet.Header.AckList.Length; i++) - agent.needAcks.Remove(packet.Header.AckList[i]); - } - } - - // Handle PacketAck packets - if (packet.Type == PacketType.PacketAck) - { - PacketAckPacket ackPacket = (PacketAckPacket)packet; - - lock (agent.needAcks) - { - for (int i = 0; i < ackPacket.Packets.Length; i++) - agent.needAcks.Remove(ackPacket.Packets[i].ID); - } - } - - #endregion ACK handling - - packetEvents.BeginRaiseEvent(packet.Type, packet, agent); - } - } - } - } - } -}