From 134f86e8d5c414409631b25b8c6f0ee45fbd8631 Mon Sep 17 00:00:00 2001 From: David Walter Seikel Date: Thu, 3 Nov 2016 21:44:39 +1000 Subject: Initial update to OpenSim 0.8.2.1 source code. --- .../Linden/Caps/AgentPreferencesModule.cs | 182 +++ .../Linden/Caps/AvatarPickerSearchModule.cs | 136 ++ .../Linden/Caps/BunchOfCaps/BunchOfCaps.cs | 175 ++- .../Linden/Caps/BunchOfCaps/BunchOfCapsModule.cs | 4 +- .../Linden/Caps/EventQueue/EventQueueGetModule.cs | 207 +-- .../Linden/Caps/EventQueue/EventQueueHelper.cs | 56 +- .../Caps/EventQueue/Tests/EventQueueTests.cs | 103 +- .../Linden/Caps/FetchInventory2Module.cs | 20 +- .../Linden/Caps/GetDisplayNamesModule.cs | 144 ++ .../ClientStack/Linden/Caps/GetMeshModule.cs | 58 +- .../ClientStack/Linden/Caps/GetTextureModule.cs | 13 +- .../Linden/Caps/MeshUploadFlagModule.cs | 12 +- .../NewFileAgentInventoryVariablePriceModule.cs | 1 + .../Linden/Caps/ObjectCaps/ObjectAdd.cs | 4 + .../Caps/ObjectCaps/UploadObjectAssetModule.cs | 1 + .../Linden/Caps/Properties/AssemblyInfo.cs | 4 +- .../ClientStack/Linden/Caps/RegionConsoleModule.cs | 10 +- .../Linden/Caps/SimulatorFeaturesModule.cs | 149 +- .../Caps/Tests/WebFetchInvDescModuleTests.cs | 159 +++ .../Linden/Caps/UploadBakedTextureModule.cs | 198 ++- .../Linden/Caps/WebFetchInvDescModule.cs | 350 ++++- OpenSim/Region/ClientStack/Linden/UDP/J2KImage.cs | 4 +- .../Region/ClientStack/Linden/UDP/LLClientView.cs | 1423 +++++++++++++++----- .../ClientStack/Linden/UDP/LLImageManager.cs | 7 + .../Region/ClientStack/Linden/UDP/LLUDPClient.cs | 243 +++- .../Region/ClientStack/Linden/UDP/LLUDPServer.cs | 974 ++++++++++---- .../ClientStack/Linden/UDP/LLUDPServerCommands.cs | 901 +++++++++++++ .../ClientStack/Linden/UDP/OpenSimUDPBase.cs | 206 ++- .../Region/ClientStack/Linden/UDP/PacketPool.cs | 25 +- .../Linden/UDP/Properties/AssemblyInfo.cs | 7 +- .../Linden/UDP/Tests/BasicCircuitTests.cs | 110 +- .../Linden/UDP/Tests/LLImageManagerTests.cs | 17 +- .../ClientStack/Linden/UDP/Tests/MockScene.cs | 78 -- .../Linden/UDP/Tests/PacketHandlerTests.cs | 1 - .../Linden/UDP/Tests/TestLLUDPServer.cs | 170 --- .../ClientStack/Linden/UDP/Tests/ThrottleTests.cs | 427 ++++++ .../Region/ClientStack/Linden/UDP/ThrottleRates.cs | 17 +- .../Region/ClientStack/Linden/UDP/TokenBucket.cs | 292 ++-- .../Linden/UDP/UnackedPacketCollection.cs | 19 +- 39 files changed, 5476 insertions(+), 1431 deletions(-) create mode 100644 OpenSim/Region/ClientStack/Linden/Caps/AgentPreferencesModule.cs create mode 100644 OpenSim/Region/ClientStack/Linden/Caps/AvatarPickerSearchModule.cs create mode 100644 OpenSim/Region/ClientStack/Linden/Caps/GetDisplayNamesModule.cs create mode 100644 OpenSim/Region/ClientStack/Linden/Caps/Tests/WebFetchInvDescModuleTests.cs create mode 100644 OpenSim/Region/ClientStack/Linden/UDP/LLUDPServerCommands.cs delete mode 100644 OpenSim/Region/ClientStack/Linden/UDP/Tests/MockScene.cs delete mode 100644 OpenSim/Region/ClientStack/Linden/UDP/Tests/TestLLUDPServer.cs create mode 100644 OpenSim/Region/ClientStack/Linden/UDP/Tests/ThrottleTests.cs (limited to 'OpenSim/Region/ClientStack/Linden') diff --git a/OpenSim/Region/ClientStack/Linden/Caps/AgentPreferencesModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/AgentPreferencesModule.cs new file mode 100644 index 0000000..aabdb51 --- /dev/null +++ b/OpenSim/Region/ClientStack/Linden/Caps/AgentPreferencesModule.cs @@ -0,0 +1,182 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.IO; +using log4net; +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework.Console; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using Caps = OpenSim.Framework.Capabilities.Caps; +using OpenSim.Capabilities.Handlers; + +namespace OpenSim.Region.ClientStack.LindenCaps +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AgentPreferencesModule")] + public class AgentPreferencesModule : ISharedRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private List m_scenes = new List(); + + public void Initialise(IConfigSource source) + { + + } + + #region Region module + + public void AddRegion(Scene scene) + { + lock (m_scenes) m_scenes.Add(scene); + } + + public void RemoveRegion(Scene scene) + { + lock (m_scenes) m_scenes.Remove(scene); + scene.EventManager.OnRegisterCaps -= RegisterCaps; + scene = null; + } + + public void RegionLoaded(Scene scene) + { + scene.EventManager.OnRegisterCaps += delegate(UUID agentID, OpenSim.Framework.Capabilities.Caps caps) + { + RegisterCaps(agentID, caps); + }; + } + + public void PostInitialise() {} + + public void Close() {} + + public string Name { get { return "AgentPreferencesModule"; } } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void RegisterCaps(UUID agent, Caps caps) + { + UUID capId = UUID.Random(); + caps.RegisterHandler("AgentPreferences", + new RestStreamHandler("POST", "/CAPS/" + capId, + delegate(string request, string path, string param, + IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + return UpdateAgentPreferences(request, path, param, agent); + })); + caps.RegisterHandler("UpdateAgentLanguage", + new RestStreamHandler("POST", "/CAPS/" + capId, + delegate(string request, string path, string param, + IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + return UpdateAgentPreferences(request, path, param, agent); + })); + caps.RegisterHandler("UpdateAgentInformation", + new RestStreamHandler("POST", "/CAPS/" + capId, + delegate(string request, string path, string param, + IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + return UpdateAgentPreferences(request, path, param, agent); + })); + } + + public string UpdateAgentPreferences(string request, string path, string param, UUID agent) + { + OSDMap resp = new OSDMap(); + // The viewer doesn't do much with the return value, so for now, if there is no preference service, + // we'll return a null llsd block for debugging purposes. This may change if someone knows what the + // correct server response would be here. + if (m_scenes[0].AgentPreferencesService == null) + { + return OSDParser.SerializeLLSDXmlString(resp); + } + m_log.DebugFormat("[AgentPrefs]: UpdateAgentPreferences for {0}", agent.ToString()); + OSDMap req = (OSDMap)OSDParser.DeserializeLLSDXml(request); + AgentPrefs data = m_scenes[0].AgentPreferencesService.GetAgentPreferences(agent); + if (data == null) + { + data = new AgentPrefs(agent); + } + + if (req.ContainsKey("access_prefs")) + { + OSDMap accessPrefs = (OSDMap)req["access_prefs"]; // We could check with ContainsKey... + data.AccessPrefs = accessPrefs["max"].AsString(); + } + if (req.ContainsKey("default_object_perm_masks")) + { + OSDMap permsMap = (OSDMap)req["default_object_perm_masks"]; + data.PermEveryone = permsMap["Everyone"].AsInteger(); + data.PermGroup = permsMap["Group"].AsInteger(); + data.PermNextOwner = permsMap["NextOwner"].AsInteger(); + } + if (req.ContainsKey("hover_height")) + { + data.HoverHeight = req["hover_height"].AsReal(); + } + if (req.ContainsKey("language")) + { + data.Language = req["language"].AsString(); + } + if (req.ContainsKey("language_is_public")) + { + data.LanguageIsPublic = req["language_is_public"].AsBoolean(); + } + m_scenes[0].AgentPreferencesService.StoreAgentPreferences(data); + OSDMap respAccessPrefs = new OSDMap(); + respAccessPrefs["max"] = data.AccessPrefs; + resp["access_prefs"] = respAccessPrefs; + OSDMap respDefaultPerms = new OSDMap(); + respDefaultPerms["Everyone"] = data.PermEveryone; + respDefaultPerms["Group"] = data.PermGroup; + respDefaultPerms["NextOwner"] = data.PermNextOwner; + resp["default_object_perm_masks"] = respDefaultPerms; + resp["god_level"] = 0; // *TODO: Add this + resp["hover_height"] = data.HoverHeight; + resp["language"] = data.Language; + resp["language_is_public"] = data.LanguageIsPublic; + + string response = OSDParser.SerializeLLSDXmlString(resp); + return response; + } + + #endregion Region module + } +} + diff --git a/OpenSim/Region/ClientStack/Linden/Caps/AvatarPickerSearchModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/AvatarPickerSearchModule.cs new file mode 100644 index 0000000..bbadc55 --- /dev/null +++ b/OpenSim/Region/ClientStack/Linden/Caps/AvatarPickerSearchModule.cs @@ -0,0 +1,136 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Specialized; +using System.Drawing; +using System.Drawing.Imaging; +using System.Reflection; +using System.IO; +using System.Web; +using log4net; +using Nini.Config; +using Mono.Addins; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using Caps = OpenSim.Framework.Capabilities.Caps; +using OpenSim.Capabilities.Handlers; + +namespace OpenSim.Region.ClientStack.Linden +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AvatarPickerSearchModule")] + public class AvatarPickerSearchModule : INonSharedRegionModule + { +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + private IPeople m_People; + private bool m_Enabled = false; + + private string m_URL; + + #region ISharedRegionModule Members + + public void Initialise(IConfigSource source) + { + IConfig config = source.Configs["ClientStack.LindenCaps"]; + if (config == null) + return; + + m_URL = config.GetString("Cap_AvatarPickerSearch", string.Empty); + // Cap doesn't exist + if (m_URL != string.Empty) + m_Enabled = true; + } + + public void AddRegion(Scene s) + { + if (!m_Enabled) + return; + + m_scene = s; + } + + public void RemoveRegion(Scene s) + { + if (!m_Enabled) + return; + + m_scene.EventManager.OnRegisterCaps -= RegisterCaps; + m_scene = null; + } + + public void RegionLoaded(Scene s) + { + if (!m_Enabled) + return; + + m_People = m_scene.RequestModuleInterface(); + m_scene.EventManager.OnRegisterCaps += RegisterCaps; + } + + public void PostInitialise() + { + } + + public void Close() { } + + public string Name { get { return "AvatarPickerSearchModule"; } } + + public Type ReplaceableInterface + { + get { return null; } + } + + #endregion + + public void RegisterCaps(UUID agentID, Caps caps) + { + UUID capID = UUID.Random(); + + if (m_URL == "localhost") + { +// m_log.DebugFormat("[AVATAR PICKER SEARCH]: /CAPS/{0} in region {1}", capID, m_scene.RegionInfo.RegionName); + caps.RegisterHandler( + "AvatarPickerSearch", + new AvatarPickerSearchHandler("/CAPS/" + capID + "/", m_People, "AvatarPickerSearch", "Search for avatars by name")); + } + else + { + // m_log.DebugFormat("[AVATAR PICKER SEARCH]: {0} in region {1}", m_URL, m_scene.RegionInfo.RegionName); + caps.RegisterHandler("AvatarPickerSearch", m_URL); + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs b/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs index 568e216..774202e 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs @@ -44,11 +44,13 @@ using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Scenes.Serialization; using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Framework.Client; using OpenSim.Services.Interfaces; using Caps = OpenSim.Framework.Capabilities.Caps; using OSDArray = OpenMetaverse.StructuredData.OSDArray; using OSDMap = OpenMetaverse.StructuredData.OSDMap; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.ClientStack.Linden { @@ -96,6 +98,8 @@ namespace OpenSim.Region.ClientStack.Linden // private static readonly string m_fetchInventoryPath = "0006/"; private static readonly string m_copyFromNotecardPath = "0007/"; // private static readonly string m_remoteParcelRequestPath = "0009/";// This is in the LandManagementModule. + private static readonly string m_getObjectPhysicsDataPath = "0101/"; + /* 0102 - 0103 RESERVED */ private static readonly string m_UpdateAgentInformationPath = "0500/"; // These are callbacks which will be setup by the scene so that we can update scene data when we @@ -204,7 +208,15 @@ namespace OpenSim.Region.ClientStack.Linden m_HostCapsObj.RegisterHandler("UpdateNotecardAgentInventory", req); m_HostCapsObj.RegisterHandler("UpdateScriptAgentInventory", req); m_HostCapsObj.RegisterHandler("UpdateScriptAgent", req); - IRequestHandler UpdateAgentInformationHandler = new RestStreamHandler("POST", capsBase + m_UpdateAgentInformationPath, UpdateAgentInformation); + + IRequestHandler getObjectPhysicsDataHandler + = new RestStreamHandler( + "POST", capsBase + m_getObjectPhysicsDataPath, GetObjectPhysicsData, "GetObjectPhysicsData", null); + m_HostCapsObj.RegisterHandler("GetObjectPhysicsData", getObjectPhysicsDataHandler); + + IRequestHandler UpdateAgentInformationHandler + = new RestStreamHandler( + "POST", capsBase + m_UpdateAgentInformationPath, UpdateAgentInformation, "UpdateAgentInformation", null); m_HostCapsObj.RegisterHandler("UpdateAgentInformation", UpdateAgentInformationHandler); m_HostCapsObj.RegisterHandler( @@ -256,8 +268,8 @@ namespace OpenSim.Region.ClientStack.Linden public string SeedCapRequest(string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { - m_log.DebugFormat( - "[CAPS]: Received SEED caps request in {0} for agent {1}", m_regionName, m_HostCapsObj.AgentID); +// m_log.DebugFormat( +// "[CAPS]: Received SEED caps request in {0} for agent {1}", m_regionName, m_HostCapsObj.AgentID); if (!m_Scene.CheckClient(m_HostCapsObj.AgentID, httpRequest.RemoteIPEndPoint)) { @@ -268,13 +280,13 @@ namespace OpenSim.Region.ClientStack.Linden return string.Empty; } - Hashtable caps = m_HostCapsObj.CapsHandlers.GetCapsDetails(true); + OSDArray capsRequested = (OSDArray)OSDParser.DeserializeLLSDXml(request); + List validCaps = new List(); - // Add the external too - foreach (KeyValuePair kvp in m_HostCapsObj.ExternalCapsHandlers) - caps[kvp.Key] = kvp.Value; + foreach (OSD c in capsRequested) + validCaps.Add(c.AsString()); - string result = LLSDHelpers.SerialiseLLSDReply(caps); + string result = LLSDHelpers.SerialiseLLSDReply(m_HostCapsObj.GetCapsDetails(true, validCaps)); //m_log.DebugFormat("[CAPS] CapsRequest {0}", result); @@ -487,24 +499,28 @@ namespace OpenSim.Region.ClientStack.Linden if (inventoryType == "sound") { - inType = 1; - assType = 1; + inType = (sbyte)InventoryType.Sound; + assType = (sbyte)AssetType.Sound; + } + else if (inventoryType == "snapshot") + { + inType = (sbyte)InventoryType.Snapshot; } else if (inventoryType == "animation") { - inType = 19; - assType = 20; + inType = (sbyte)InventoryType.Animation; + assType = (sbyte)AssetType.Animation; } else if (inventoryType == "wearable") { - inType = 18; + inType = (sbyte)InventoryType.Wearable; switch (assetType) { case "bodypart": - assType = 13; + assType = (sbyte)AssetType.Bodypart; break; case "clothing": - assType = 5; + assType = (sbyte)AssetType.Clothing; break; } } @@ -521,6 +537,41 @@ namespace OpenSim.Region.ClientStack.Linden OSDArray texture_list = (OSDArray)request["texture_list"]; SceneObjectGroup grp = null; + InventoryFolderBase textureUploadFolder = null; + + List foldersToUpdate = new List(); + List itemsToUpdate = new List(); + IClientInventory clientInv = null; + + if (texture_list.Count > 0) + { + ScenePresence avatar = null; + m_Scene.TryGetScenePresence(m_HostCapsObj.AgentID, out avatar); + + if (avatar != null) + { + IClientCore core = (IClientCore)avatar.ControllingClient; + + if (core.TryGet(out clientInv)) + { + var systemTextureFolder = m_Scene.InventoryService.GetFolderForType(m_HostCapsObj.AgentID, FolderType.Texture); + textureUploadFolder = new InventoryFolderBase(UUID.Random(), assetName, m_HostCapsObj.AgentID, (short)FolderType.None, systemTextureFolder.ID, 1); + if (m_Scene.InventoryService.AddFolder(textureUploadFolder)) + { + foldersToUpdate.Add(textureUploadFolder); + + m_log.DebugFormat( + "[BUNCH OF CAPS]: Created new folder '{0}' ({1}) for textures uploaded with mesh object {2}", + textureUploadFolder.Name, textureUploadFolder.ID, assetName); + } + else + { + textureUploadFolder = null; + } + } + } + } + List textures = new List(); for (int i = 0; i < texture_list.Count; i++) { @@ -528,6 +579,38 @@ namespace OpenSim.Region.ClientStack.Linden textureAsset.Data = texture_list[i].AsBinary(); m_assetService.Store(textureAsset); textures.Add(textureAsset.FullID); + + if (textureUploadFolder != null) + { + InventoryItemBase textureItem = new InventoryItemBase(); + textureItem.Owner = m_HostCapsObj.AgentID; + textureItem.CreatorId = m_HostCapsObj.AgentID.ToString(); + textureItem.CreatorData = String.Empty; + textureItem.ID = UUID.Random(); + textureItem.AssetID = textureAsset.FullID; + textureItem.Description = assetDescription; + textureItem.Name = assetName + " - Texture " + (i + 1).ToString(); + textureItem.AssetType = (int)AssetType.Texture; + textureItem.InvType = (int)InventoryType.Texture; + textureItem.Folder = textureUploadFolder.ID; + textureItem.CurrentPermissions + = (uint)(PermissionMask.Move | PermissionMask.Copy | PermissionMask.Modify | PermissionMask.Transfer | PermissionMask.Export); + textureItem.BasePermissions = (uint)PermissionMask.All | (uint)PermissionMask.Export; + textureItem.EveryOnePermissions = 0; + textureItem.NextPermissions = (uint)PermissionMask.All; + textureItem.CreationDate = Util.UnixTimeSinceEpoch(); + m_Scene.InventoryService.AddItem(textureItem); + itemsToUpdate.Add(textureItem); + + m_log.DebugFormat( + "[BUNCH OF CAPS]: Created new inventory item '{0}' ({1}) for texture uploaded with mesh object {2}", + textureItem.Name, textureItem.ID, assetName); + } + } + + if (clientInv != null && (foldersToUpdate.Count > 0 || itemsToUpdate.Count > 0)) + { + clientInv.SendBulkUpdateInventory(foldersToUpdate.ToArray(), itemsToUpdate.ToArray()); } for (int i = 0; i < mesh_list.Count; i++) @@ -701,9 +784,9 @@ namespace OpenSim.Region.ClientStack.Linden // If we set PermissionMask.All then when we rez the item the next permissions will replace the current // (owner) permissions. This becomes a problem if next permissions are changed. item.CurrentPermissions - = (uint)(PermissionMask.Move | PermissionMask.Copy | PermissionMask.Modify | PermissionMask.Transfer); + = (uint)(PermissionMask.Move | PermissionMask.Copy | PermissionMask.Modify | PermissionMask.Transfer | PermissionMask.Export); - item.BasePermissions = (uint)PermissionMask.All; + item.BasePermissions = (uint)PermissionMask.All | (uint)PermissionMask.Export; item.EveryOnePermissions = 0; item.NextPermissions = (uint)PermissionMask.All; item.CreationDate = Util.UnixTimeSinceEpoch(); @@ -850,18 +933,26 @@ namespace OpenSim.Region.ClientStack.Linden item = m_Scene.InventoryService.GetItem(new InventoryItemBase(itemID)); if (item != null) { - copyItem = m_Scene.GiveInventoryItem(m_HostCapsObj.AgentID, item.Owner, itemID, folderID); - if (copyItem != null && client != null) + string message; + copyItem = m_Scene.GiveInventoryItem(m_HostCapsObj.AgentID, item.Owner, itemID, folderID, out message); + if (client != null) { - m_log.InfoFormat("[CAPS]: CopyInventoryFromNotecard, ItemID:{0}, FolderID:{1}", copyItem.ID, copyItem.Folder); - client.SendBulkUpdateInventory(copyItem); + if (copyItem != null) + { + m_log.InfoFormat("[CAPS]: CopyInventoryFromNotecard, ItemID:{0}, FolderID:{1}", copyItem.ID, copyItem.Folder); + client.SendBulkUpdateInventory(copyItem); + } + else + { + client.SendAgentAlertMessage(message, false); + } } } else { m_log.ErrorFormat("[CAPS]: CopyInventoryFromNotecard - Failed to retrieve item {0} from notecard {1}", itemID, notecardID); if (client != null) - client.SendAlertMessage("Failed to retrieve item"); + client.SendAgentAlertMessage("Failed to retrieve item", false); } } catch (Exception e) @@ -873,17 +964,49 @@ namespace OpenSim.Region.ClientStack.Linden return LLSDHelpers.SerialiseLLSDReply(response); } - public string UpdateAgentInformation(string request, string path, + public string GetObjectPhysicsData(string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { OSDMap req = (OSDMap)OSDParser.DeserializeLLSDXml(request); OSDMap resp = new OSDMap(); + OSDArray object_ids = (OSDArray)req["object_ids"]; - OSDMap accessPrefs = new OSDMap(); - accessPrefs["max"] = "A"; + for (int i = 0 ; i < object_ids.Count ; i++) + { + UUID uuid = object_ids[i].AsUUID(); + + SceneObjectPart obj = m_Scene.GetSceneObjectPart(uuid); + if (obj != null) + { + OSDMap object_data = new OSDMap(); - resp["access_prefs"] = accessPrefs; + object_data["PhysicsShapeType"] = obj.PhysicsShapeType; + object_data["Density"] = obj.Density; + object_data["Friction"] = obj.Friction; + object_data["Restitution"] = obj.Restitution; + object_data["GravityMultiplier"] = obj.GravityModifier; + + resp[uuid.ToString()] = object_data; + } + } + + string response = OSDParser.SerializeLLSDXmlString(resp); + return response; + } + + public string UpdateAgentInformation(string request, string path, + string param, IOSHttpRequest httpRequest, + IOSHttpResponse httpResponse) + { + OSDMap req = (OSDMap)OSDParser.DeserializeLLSDXml(request); + OSDMap accessPrefs = (OSDMap)req["access_prefs"]; + string desiredMaturity = accessPrefs["max"]; + + OSDMap resp = new OSDMap(); + OSDMap respAccessPrefs = new OSDMap(); + respAccessPrefs["max"] = desiredMaturity; // echoing the maturity back means success + resp["access_prefs"] = respAccessPrefs; string response = OSDParser.SerializeLLSDXmlString(resp); return response; diff --git a/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCapsModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCapsModule.cs index b735dfa..c241075 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCapsModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCapsModule.cs @@ -40,8 +40,8 @@ using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using Caps = OpenSim.Framework.Capabilities.Caps; -[assembly: Addin("LindenCaps", "0.1")] -[assembly: AddinDependency("OpenSim", "0.5")] +[assembly: Addin("LindenCaps", OpenSim.VersionInfo.VersionNumber)] +[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)] namespace OpenSim.Region.ClientStack.Linden { diff --git a/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/EventQueueGetModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/EventQueueGetModule.cs index 4d2c0f2..9b9f6a7 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/EventQueueGetModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/EventQueueGetModule.cs @@ -59,12 +59,20 @@ namespace OpenSim.Region.ClientStack.Linden public class EventQueueGetModule : IEventQueue, INonSharedRegionModule { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static string LogHeader = "[EVENT QUEUE GET MODULE]"; /// /// Debug level. /// public int DebugLevel { get; set; } + // Viewer post requests timeout in 60 secs + // https://bitbucket.org/lindenlab/viewer-release/src/421c20423df93d650cc305dc115922bb30040999/indra/llmessage/llhttpclient.cpp?at=default#cl-44 + // + private const int VIEWER_TIMEOUT = 60 * 1000; + // Just to be safe, we work on a 10 sec shorter cycle + private const int SERVER_EQ_TIME_NO_EVENTS = VIEWER_TIMEOUT - (10 * 1000); + protected Scene m_scene; private Dictionary m_ids = new Dictionary(); @@ -84,7 +92,6 @@ namespace OpenSim.Region.ClientStack.Linden scene.RegisterModuleInterface(this); scene.EventManager.OnClientClosed += ClientClosed; - scene.EventManager.OnMakeChildAgent += MakeChildAgent; scene.EventManager.OnRegisterCaps += OnRegisterCaps; MainConsole.Instance.Commands.AddCommand( @@ -94,9 +101,17 @@ namespace OpenSim.Region.ClientStack.Linden "debug eq [0|1|2]", "Turn on event queue debugging\n" + " <= 0 - turns off all event queue logging\n" - + " >= 1 - turns on outgoing event logging\n" + + " >= 1 - turns on event queue setup and outgoing event logging\n" + " >= 2 - turns on poll notification", HandleDebugEq); + + MainConsole.Instance.Commands.AddCommand( + "Debug", + false, + "show eq", + "show eq", + "Show contents of event queues for logged in avatars. Used for debugging.", + HandleShowEq); } public void RemoveRegion(Scene scene) @@ -105,7 +120,6 @@ namespace OpenSim.Region.ClientStack.Linden return; scene.EventManager.OnClientClosed -= ClientClosed; - scene.EventManager.OnMakeChildAgent -= MakeChildAgent; scene.EventManager.OnRegisterCaps -= OnRegisterCaps; scene.UnregisterModuleInterface(this); @@ -138,7 +152,7 @@ namespace OpenSim.Region.ClientStack.Linden if (!(args.Length == 3 && int.TryParse(args[2], out debugLevel))) { - MainConsole.Instance.OutputFormat("Usage: debug eq [0|1]"); + MainConsole.Instance.OutputFormat("Usage: debug eq [0|1|2]"); } else { @@ -148,6 +162,21 @@ namespace OpenSim.Region.ClientStack.Linden } } + protected void HandleShowEq(string module, string[] args) + { + MainConsole.Instance.OutputFormat("For scene {0}", m_scene.Name); + + lock (queues) + { + foreach (KeyValuePair> kvp in queues) + { + MainConsole.Instance.OutputFormat( + "For agent {0} there are {1} messages queued for send.", + kvp.Key, kvp.Value.Count); + } + } + } + /// /// Always returns a valid queue /// @@ -159,14 +188,14 @@ namespace OpenSim.Region.ClientStack.Linden { if (!queues.ContainsKey(agentId)) { - /* - m_log.DebugFormat( - "[EVENTQUEUE]: Adding new queue for agent {0} in region {1}", - agentId, m_scene.RegionInfo.RegionName); - */ + if (DebugLevel > 0) + m_log.DebugFormat( + "[EVENTQUEUE]: Adding new queue for agent {0} in region {1}", + agentId, m_scene.RegionInfo.RegionName); + queues[agentId] = new Queue(); } - + return queues[agentId]; } } @@ -198,8 +227,23 @@ namespace OpenSim.Region.ClientStack.Linden { Queue queue = GetQueue(avatarID); if (queue != null) + { lock (queue) queue.Enqueue(ev); + } + else if (DebugLevel > 0) + { + ScenePresence sp = m_scene.GetScenePresence(avatarID); + + // This assumes that an NPC should never have a queue. + if (sp != null && sp.PresenceType != PresenceType.Npc) + { + OSDMap evMap = (OSDMap)ev; + m_log.WarnFormat( + "[EVENTQUEUE]: (Enqueue) No queue found for agent {0} {1} when placing message {2} in region {3}", + sp.Name, sp.UUID, evMap["message"], m_scene.Name); + } + } } catch (NullReferenceException e) { @@ -214,44 +258,14 @@ namespace OpenSim.Region.ClientStack.Linden private void ClientClosed(UUID agentID, Scene scene) { -// m_log.DebugFormat("[EVENTQUEUE]: Closed client {0} in region {1}", agentID, m_scene.RegionInfo.RegionName); - - int count = 0; - while (queues.ContainsKey(agentID) && queues[agentID].Count > 0 && count++ < 5) - { - Thread.Sleep(1000); - } + //m_log.DebugFormat("[EVENTQUEUE]: Closed client {0} in region {1}", agentID, m_scene.RegionInfo.RegionName); lock (queues) - { queues.Remove(agentID); - } List removeitems = new List(); lock (m_AvatarQueueUUIDMapping) - { - foreach (UUID ky in m_AvatarQueueUUIDMapping.Keys) - { -// m_log.DebugFormat("[EVENTQUEUE]: Found key {0} in m_AvatarQueueUUIDMapping while looking for {1}", ky, AgentID); - if (ky == agentID) - { - removeitems.Add(ky); - } - } - - foreach (UUID ky in removeitems) - { - UUID eventQueueGetUuid = m_AvatarQueueUUIDMapping[ky]; - m_AvatarQueueUUIDMapping.Remove(ky); - - string eqgPath = GenerateEqgCapPath(eventQueueGetUuid); - MainServer.Instance.RemovePollServiceHTTPHandler("", eqgPath); - -// m_log.DebugFormat( -// "[EVENT QUEUE GET MODULE]: Removed EQG handler {0} for {1} in {2}", -// eqgPath, agentID, m_scene.RegionInfo.RegionName); - } - } + m_AvatarQueueUUIDMapping.Remove(agentID); UUID searchval = UUID.Zero; @@ -272,19 +286,9 @@ namespace OpenSim.Region.ClientStack.Linden foreach (UUID ky in removeitems) m_QueueUUIDAvatarMapping.Remove(ky); } - } - private void MakeChildAgent(ScenePresence avatar) - { - //m_log.DebugFormat("[EVENTQUEUE]: Make Child agent {0} in region {1}.", avatar.UUID, m_scene.RegionInfo.RegionName); - //lock (m_ids) - // { - //if (m_ids.ContainsKey(avatar.UUID)) - //{ - // close the event queue. - //m_ids[avatar.UUID] = -1; - //} - //} + // m_log.DebugFormat("[EVENTQUEUE]: Deleted queues for {0} in region {1}", agentID, m_scene.RegionInfo.RegionName); + } /// @@ -300,9 +304,10 @@ namespace OpenSim.Region.ClientStack.Linden { // Register an event queue for the client - //m_log.DebugFormat( - // "[EVENTQUEUE]: OnRegisterCaps: agentID {0} caps {1} region {2}", - // agentID, caps, m_scene.RegionInfo.RegionName); + if (DebugLevel > 0) + m_log.DebugFormat( + "[EVENTQUEUE]: OnRegisterCaps: agentID {0} caps {1} region {2}", + agentID, caps, m_scene.RegionInfo.RegionName); // Let's instantiate a Queue for this agent right now TryGetQueue(agentID); @@ -336,28 +341,9 @@ namespace OpenSim.Region.ClientStack.Linden m_AvatarQueueUUIDMapping.Add(agentID, eventQueueGetUUID); } - string eventQueueGetPath = GenerateEqgCapPath(eventQueueGetUUID); - - // Register this as a caps handler - // FIXME: Confusingly, we need to register separate as a capability so that the client is told about - // EventQueueGet when it receive capability information, but then we replace the rest handler immediately - // afterwards with the poll service. So for now, we'll pass a null instead to simplify code reading, but - // really it should be possible to directly register the poll handler as a capability. - caps.RegisterHandler("EventQueueGet", new RestHTTPHandler("POST", eventQueueGetPath, null)); -// delegate(Hashtable m_dhttpMethod) -// { -// return ProcessQueue(m_dhttpMethod, agentID, caps); -// })); - - // This will persist this beyond the expiry of the caps handlers - // TODO: Add EventQueueGet name/description for diagnostics - MainServer.Instance.AddPollServiceHTTPHandler( - eventQueueGetPath, - new PollServiceEventArgs(null, HasEvents, GetEvents, NoEvents, agentID)); - -// m_log.DebugFormat( -// "[EVENT QUEUE GET MODULE]: Registered EQG handler {0} for {1} in {2}", -// eventQueueGetPath, agentID, m_scene.RegionInfo.RegionName); + caps.RegisterPollHandler( + "EventQueueGet", + new PollServiceEventArgs(null, GenerateEqgCapPath(eventQueueGetUUID), HasEvents, GetEvents, NoEvents, agentID, SERVER_EQ_TIME_NO_EVENTS)); Random rnd = new Random(Environment.TickCount); lock (m_ids) @@ -375,7 +361,10 @@ namespace OpenSim.Region.ClientStack.Linden Queue queue = GetQueue(agentID); if (queue != null) lock (queue) + { + //m_log.WarnFormat("POLLED FOR EVENTS BY {0} in {1} -- {2}", agentID, m_scene.RegionInfo.RegionName, queue.Count); return queue.Count > 0; + } return false; } @@ -391,16 +380,21 @@ namespace OpenSim.Region.ClientStack.Linden OSDMap ev = (OSDMap)element; m_log.DebugFormat( "Eq OUT {0,-30} to {1,-20} {2,-20}", - ev["message"], m_scene.GetScenePresence(agentId).Name, m_scene.RegionInfo.RegionName); + ev["message"], m_scene.GetScenePresence(agentId).Name, m_scene.Name); } } - public Hashtable GetEvents(UUID requestID, UUID pAgentId, string request) + public Hashtable GetEvents(UUID requestID, UUID pAgentId) { if (DebugLevel >= 2) - m_log.DebugFormat("POLLED FOR EQ MESSAGES BY {0} in {1}", pAgentId, m_scene.RegionInfo.RegionName); + m_log.WarnFormat("POLLED FOR EQ MESSAGES BY {0} in {1}", pAgentId, m_scene.Name); + + Queue queue = GetQueue(pAgentId); + if (queue == null) + { + return NoEvents(requestID, pAgentId); + } - Queue queue = TryGetQueue(pAgentId); OSD element; lock (queue) { @@ -727,34 +721,51 @@ namespace OpenSim.Region.ClientStack.Linden Enqueue(item, avatarID); } - public virtual void EnableSimulator(ulong handle, IPEndPoint endPoint, UUID avatarID) + public virtual void EnableSimulator(ulong handle, IPEndPoint endPoint, UUID avatarID, int regionSizeX, int regionSizeY) { - OSD item = EventQueueHelper.EnableSimulator(handle, endPoint); + if (DebugLevel > 0) + m_log.DebugFormat("{0} EnableSimulator. handle={1}, endPoint={2}, avatarID={3}", + LogHeader, handle, endPoint, avatarID, regionSizeX, regionSizeY); + + OSD item = EventQueueHelper.EnableSimulator(handle, endPoint, regionSizeX, regionSizeY); Enqueue(item, avatarID); } - public virtual void EstablishAgentCommunication(UUID avatarID, IPEndPoint endPoint, string capsPath) + public virtual void EstablishAgentCommunication(UUID avatarID, IPEndPoint endPoint, string capsPath, + ulong regionHandle, int regionSizeX, int regionSizeY) { - OSD item = EventQueueHelper.EstablishAgentCommunication(avatarID, endPoint.ToString(), capsPath); + if (DebugLevel > 0) + m_log.DebugFormat("{0} EstablishAgentCommunication. handle={1}, endPoint={2}, avatarID={3}", + LogHeader, regionHandle, endPoint, avatarID, regionSizeX, regionSizeY); + + OSD item = EventQueueHelper.EstablishAgentCommunication(avatarID, endPoint.ToString(), capsPath, regionHandle, regionSizeX, regionSizeY); Enqueue(item, avatarID); } public virtual void TeleportFinishEvent(ulong regionHandle, byte simAccess, IPEndPoint regionExternalEndPoint, uint locationID, uint flags, string capsURL, - UUID avatarID) + UUID avatarID, int regionSizeX, int regionSizeY) { + if (DebugLevel > 0) + m_log.DebugFormat("{0} TeleportFinishEvent. handle={1}, endPoint={2}, avatarID={3}", + LogHeader, regionHandle, regionExternalEndPoint, avatarID, regionSizeX, regionSizeY); + OSD item = EventQueueHelper.TeleportFinishEvent(regionHandle, simAccess, regionExternalEndPoint, - locationID, flags, capsURL, avatarID); + locationID, flags, capsURL, avatarID, regionSizeX, regionSizeY); Enqueue(item, avatarID); } public virtual void CrossRegion(ulong handle, Vector3 pos, Vector3 lookAt, IPEndPoint newRegionExternalEndPoint, - string capsURL, UUID avatarID, UUID sessionID) + string capsURL, UUID avatarID, UUID sessionID, int regionSizeX, int regionSizeY) { + if (DebugLevel > 0) + m_log.DebugFormat("{0} CrossRegion. handle={1}, avatarID={2}, regionSize={3},{4}>", + LogHeader, handle, avatarID, regionSizeX, regionSizeY); + OSD item = EventQueueHelper.CrossRegion(handle, pos, lookAt, newRegionExternalEndPoint, - capsURL, avatarID, sessionID); + capsURL, avatarID, sessionID, regionSizeX, regionSizeY); Enqueue(item, avatarID); } @@ -771,12 +782,12 @@ namespace OpenSim.Region.ClientStack.Linden } - public void ChatterBoxSessionAgentListUpdates(UUID sessionID, UUID fromAgent, UUID toAgent, bool canVoiceChat, + public void ChatterBoxSessionAgentListUpdates(UUID sessionID, UUID fromAgent, UUID anotherAgent, bool canVoiceChat, bool isModerator, bool textMute) { OSD item = EventQueueHelper.ChatterBoxSessionAgentListUpdates(sessionID, fromAgent, canVoiceChat, isModerator, textMute); - Enqueue(item, toAgent); + Enqueue(item, fromAgent); //m_log.InfoFormat("########### eq ChatterBoxSessionAgentListUpdates #############\n{0}", item); } @@ -807,5 +818,13 @@ namespace OpenSim.Region.ClientStack.Linden { return EventQueueHelper.BuildEvent(eventName, eventBody); } + + public void partPhysicsProperties(uint localID, byte physhapetype, + float density, float friction, float bounce, float gravmod,UUID avatarID) + { + OSD item = EventQueueHelper.partPhysicsProperties(localID, physhapetype, + density, friction, bounce, gravmod); + Enqueue(item, avatarID); + } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/EventQueueHelper.cs b/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/EventQueueHelper.cs index 3f49aba..384af74 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/EventQueueHelper.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/EventQueueHelper.cs @@ -70,13 +70,15 @@ namespace OpenSim.Region.ClientStack.Linden return llsdEvent; } - public static OSD EnableSimulator(ulong handle, IPEndPoint endPoint) + public static OSD EnableSimulator(ulong handle, IPEndPoint endPoint, int regionSizeX, int regionSizeY) { - OSDMap llsdSimInfo = new OSDMap(3); + OSDMap llsdSimInfo = new OSDMap(5); llsdSimInfo.Add("Handle", new OSDBinary(ulongToByteArray(handle))); llsdSimInfo.Add("IP", new OSDBinary(endPoint.Address.GetAddressBytes())); llsdSimInfo.Add("Port", new OSDInteger(endPoint.Port)); + llsdSimInfo.Add("RegionSizeX", OSD.FromUInteger((uint) regionSizeX)); + llsdSimInfo.Add("RegionSizeY", OSD.FromUInteger((uint) regionSizeY)); OSDArray arr = new OSDArray(1); arr.Add(llsdSimInfo); @@ -104,7 +106,8 @@ namespace OpenSim.Region.ClientStack.Linden public static OSD CrossRegion(ulong handle, Vector3 pos, Vector3 lookAt, IPEndPoint newRegionExternalEndPoint, - string capsURL, UUID agentID, UUID sessionID) + string capsURL, UUID agentID, UUID sessionID, + int regionSizeX, int regionSizeY) { OSDArray lookAtArr = new OSDArray(3); lookAtArr.Add(OSD.FromReal(lookAt.X)); @@ -130,11 +133,13 @@ namespace OpenSim.Region.ClientStack.Linden OSDArray agentDataArr = new OSDArray(1); agentDataArr.Add(agentDataMap); - OSDMap regionDataMap = new OSDMap(4); + OSDMap regionDataMap = new OSDMap(6); regionDataMap.Add("RegionHandle", OSD.FromBinary(ulongToByteArray(handle))); regionDataMap.Add("SeedCapability", OSD.FromString(capsURL)); regionDataMap.Add("SimIP", OSD.FromBinary(newRegionExternalEndPoint.Address.GetAddressBytes())); regionDataMap.Add("SimPort", OSD.FromInteger(newRegionExternalEndPoint.Port)); + regionDataMap.Add("RegionSizeX", OSD.FromUInteger((uint)regionSizeX)); + regionDataMap.Add("RegionSizeY", OSD.FromUInteger((uint)regionSizeY)); OSDArray regionDataArr = new OSDArray(1); regionDataArr.Add(regionDataMap); @@ -148,8 +153,9 @@ namespace OpenSim.Region.ClientStack.Linden } public static OSD TeleportFinishEvent( - ulong regionHandle, byte simAccess, IPEndPoint regionExternalEndPoint, - uint locationID, uint flags, string capsURL, UUID agentID) + ulong regionHandle, byte simAccess, IPEndPoint regionExternalEndPoint, + uint locationID, uint flags, string capsURL, UUID agentID, + int regionSizeX, int regionSizeY) { OSDMap info = new OSDMap(); info.Add("AgentID", OSD.FromUUID(agentID)); @@ -160,6 +166,8 @@ namespace OpenSim.Region.ClientStack.Linden info.Add("SimIP", OSD.FromBinary(regionExternalEndPoint.Address.GetAddressBytes())); info.Add("SimPort", OSD.FromInteger(regionExternalEndPoint.Port)); info.Add("TeleportFlags", OSD.FromULong(1L << 4)); // AgentManager.TeleportFlags.ViaLocation + info.Add("RegionSizeX", OSD.FromUInteger((uint)regionSizeX)); + info.Add("RegionSizeY", OSD.FromUInteger((uint)regionSizeY)); OSDArray infoArr = new OSDArray(); infoArr.Add(info); @@ -187,12 +195,18 @@ namespace OpenSim.Region.ClientStack.Linden return BuildEvent("ScriptRunningReply", body); } - public static OSD EstablishAgentCommunication(UUID agentID, string simIpAndPort, string seedcap) + public static OSD EstablishAgentCommunication(UUID agentID, string simIpAndPort, string seedcap, + ulong regionHandle, int regionSizeX, int regionSizeY) { - OSDMap body = new OSDMap(3); - body.Add("agent-id", new OSDUUID(agentID)); - body.Add("sim-ip-and-port", new OSDString(simIpAndPort)); - body.Add("seed-capability", new OSDString(seedcap)); + OSDMap body = new OSDMap(6) + { + {"agent-id", new OSDUUID(agentID)}, + {"sim-ip-and-port", new OSDString(simIpAndPort)}, + {"seed-capability", new OSDString(seedcap)}, + {"region-handle", OSD.FromULong(regionHandle)}, + {"region-size-x", OSD.FromInteger(regionSizeX)}, + {"region-size-y", OSD.FromInteger(regionSizeY)} + }; return BuildEvent("EstablishAgentCommunication", body); } @@ -395,5 +409,25 @@ namespace OpenSim.Region.ClientStack.Linden return message; } + public static OSD partPhysicsProperties(uint localID, byte physhapetype, + float density, float friction, float bounce, float gravmod) + { + + OSDMap physinfo = new OSDMap(6); + physinfo["LocalID"] = localID; + physinfo["Density"] = density; + physinfo["Friction"] = friction; + physinfo["GravityMultiplier"] = gravmod; + physinfo["Restitution"] = bounce; + physinfo["PhysicsShapeType"] = (int)physhapetype; + + OSDArray array = new OSDArray(1); + array.Add(physinfo); + + OSDMap llsdBody = new OSDMap(1); + llsdBody.Add("ObjectData", array); + + return BuildEvent("ObjectPhysicsProperties", llsdBody); + } } } diff --git a/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/Tests/EventQueueTests.cs b/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/Tests/EventQueueTests.cs index ed8ec16..16a902d 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/Tests/EventQueueTests.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/Tests/EventQueueTests.cs @@ -26,6 +26,7 @@ */ using System; +using System.Collections; using System.Collections.Generic; using System.Net; using log4net.Config; @@ -33,13 +34,15 @@ using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.ClientStack.Linden; using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.OptionalModules.World.NPC; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.ClientStack.Linden.Tests { @@ -47,10 +50,14 @@ namespace OpenSim.Region.ClientStack.Linden.Tests public class EventQueueTests : OpenSimTestCase { private TestScene m_scene; + private EventQueueGetModule m_eqgMod; + private NPCModule m_npcMod; [SetUp] - public void SetUp() + public override void SetUp() { + base.SetUp(); + uint port = 9999; uint sslPort = 9998; @@ -67,14 +74,19 @@ namespace OpenSim.Region.ClientStack.Linden.Tests config.Configs["Startup"].Set("EventQueue", "true"); CapabilitiesModule capsModule = new CapabilitiesModule(); - EventQueueGetModule eqgModule = new EventQueueGetModule(); + m_eqgMod = new EventQueueGetModule(); + + // For NPC test support + config.AddConfig("NPC"); + config.Configs["NPC"].Set("Enabled", "true"); + m_npcMod = new NPCModule(); m_scene = new SceneHelpers().SetupScene(); - SceneHelpers.SetupSceneModules(m_scene, config, capsModule, eqgModule); + SceneHelpers.SetupSceneModules(m_scene, config, capsModule, m_eqgMod, m_npcMod); } [Test] - public void AddForClient() + public void TestAddForClient() { TestHelpers.InMethod(); // log4net.Config.XmlConfigurator.Configure(); @@ -86,18 +98,93 @@ namespace OpenSim.Region.ClientStack.Linden.Tests } [Test] - public void RemoveForClient() + public void TestRemoveForClient() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); +// TestHelpers.EnableLogging(); UUID spId = TestHelpers.ParseTail(0x1); SceneHelpers.AddScenePresence(m_scene, spId); - m_scene.IncomingCloseAgent(spId, false); + m_scene.CloseAgent(spId, false); // TODO: Add more assertions for the other aspects of event queues Assert.That(MainServer.Instance.GetPollServiceHandlerKeys().Count, Is.EqualTo(0)); } + + [Test] + public void TestEnqueueMessage() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); + + string messageName = "TestMessage"; + + m_eqgMod.Enqueue(m_eqgMod.BuildEvent(messageName, new OSDMap()), sp.UUID); + + Hashtable eventsResponse = m_eqgMod.GetEvents(UUID.Zero, sp.UUID); + + Assert.That((int)eventsResponse["int_response_code"], Is.EqualTo((int)HttpStatusCode.OK)); + +// Console.WriteLine("Response [{0}]", (string)eventsResponse["str_response_string"]); + + OSDMap rawOsd = (OSDMap)OSDParser.DeserializeLLSDXml((string)eventsResponse["str_response_string"]); + OSDArray eventsOsd = (OSDArray)rawOsd["events"]; + + bool foundUpdate = false; + foreach (OSD osd in eventsOsd) + { + OSDMap eventOsd = (OSDMap)osd; + + if (eventOsd["message"] == messageName) + foundUpdate = true; + } + + Assert.That(foundUpdate, Is.True, string.Format("Did not find {0} in response", messageName)); + } + + /// + /// Test an attempt to put a message on the queue of a user that is not in the region. + /// + [Test] + public void TestEnqueueMessageNoUser() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + string messageName = "TestMessage"; + + m_eqgMod.Enqueue(m_eqgMod.BuildEvent(messageName, new OSDMap()), TestHelpers.ParseTail(0x1)); + + Hashtable eventsResponse = m_eqgMod.GetEvents(UUID.Zero, TestHelpers.ParseTail(0x1)); + + Assert.That((int)eventsResponse["int_response_code"], Is.EqualTo((int)HttpStatusCode.BadGateway)); + } + + /// + /// NPCs do not currently have an event queue but a caller may try to send a message anyway, so check behaviour. + /// + [Test] + public void TestEnqueueMessageToNpc() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID npcId + = m_npcMod.CreateNPC( + "John", "Smith", new Vector3(128, 128, 30), UUID.Zero, true, m_scene, new AvatarAppearance()); + + ScenePresence npc = m_scene.GetScenePresence(npcId); + + string messageName = "TestMessage"; + + m_eqgMod.Enqueue(m_eqgMod.BuildEvent(messageName, new OSDMap()), npc.UUID); + + Hashtable eventsResponse = m_eqgMod.GetEvents(UUID.Zero, npc.UUID); + + Assert.That((int)eventsResponse["int_response_code"], Is.EqualTo((int)HttpStatusCode.BadGateway)); + } } } \ No newline at end of file diff --git a/OpenSim/Region/ClientStack/Linden/Caps/FetchInventory2Module.cs b/OpenSim/Region/ClientStack/Linden/Caps/FetchInventory2Module.cs index 87d3d1c..e0a11cc 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/FetchInventory2Module.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/FetchInventory2Module.cs @@ -25,20 +25,16 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -using System; -using System.Collections; -using System.Reflection; -using log4net; -using Nini.Config; using Mono.Addins; +using Nini.Config; using OpenMetaverse; -using OpenSim.Framework; +using OpenSim.Capabilities.Handlers; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; +using System; using Caps = OpenSim.Framework.Capabilities.Caps; -using OpenSim.Capabilities.Handlers; namespace OpenSim.Region.ClientStack.Linden { @@ -58,8 +54,6 @@ namespace OpenSim.Region.ClientStack.Linden private string m_fetchInventory2Url; - private FetchInventory2Handler m_fetchHandler; - #region ISharedRegionModule Members public void Initialise(IConfigSource source) @@ -98,10 +92,6 @@ namespace OpenSim.Region.ClientStack.Linden m_inventoryService = m_scene.InventoryService; - // We'll reuse the same handler for all requests. - if (m_fetchInventory2Url == "localhost") - m_fetchHandler = new FetchInventory2Handler(m_inventoryService); - m_scene.EventManager.OnRegisterCaps += RegisterCaps; } @@ -131,9 +121,11 @@ namespace OpenSim.Region.ClientStack.Linden { capUrl = "/CAPS/" + UUID.Random(); + FetchInventory2Handler fetchHandler = new FetchInventory2Handler(m_inventoryService, agentID); + IRequestHandler reqHandler = new RestStreamHandler( - "POST", capUrl, m_fetchHandler.FetchInventoryRequest, capName, agentID.ToString()); + "POST", capUrl, fetchHandler.FetchInventoryRequest, capName, agentID.ToString()); caps.RegisterHandler(capName, reqHandler); } diff --git a/OpenSim/Region/ClientStack/Linden/Caps/GetDisplayNamesModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/GetDisplayNamesModule.cs new file mode 100644 index 0000000..6617bbc --- /dev/null +++ b/OpenSim/Region/ClientStack/Linden/Caps/GetDisplayNamesModule.cs @@ -0,0 +1,144 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Specialized; +using System.Drawing; +using System.Drawing.Imaging; +using System.Reflection; +using System.IO; +using System.Web; +using log4net; +using Nini.Config; +using Mono.Addins; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenMetaverse.Imaging; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using Caps = OpenSim.Framework.Capabilities.Caps; +using OpenSim.Capabilities.Handlers; + +namespace OpenSim.Region.ClientStack.Linden +{ + + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GetDisplayNamesModule")] + public class GetDisplayNamesModule : INonSharedRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + private IUserManagement m_UserManager; + + private bool m_Enabled = false; + + private string m_URL; + + #region ISharedRegionModule Members + + public void Initialise(IConfigSource source) + { + IConfig config = source.Configs["ClientStack.LindenCaps"]; + if (config == null) + return; + + m_URL = config.GetString("Cap_GetDisplayNames", string.Empty); + if (m_URL != string.Empty) + m_Enabled = true; + } + + public void AddRegion(Scene s) + { + if (!m_Enabled) + return; + + m_scene = s; + } + + public void RemoveRegion(Scene s) + { + if (!m_Enabled) + return; + + m_scene.EventManager.OnRegisterCaps -= RegisterCaps; + m_scene = null; + } + + public void RegionLoaded(Scene s) + { + if (!m_Enabled) + return; + + m_UserManager = m_scene.RequestModuleInterface(); + m_scene.EventManager.OnRegisterCaps += RegisterCaps; + } + + public void PostInitialise() + { + } + + public void Close() { } + + public string Name { get { return "GetDisplayNamesModule"; } } + + public Type ReplaceableInterface + { + get { return null; } + } + + #endregion + + public void RegisterCaps(UUID agentID, Caps caps) + { + UUID capID = UUID.Random(); + + if (m_URL == "localhost") + { + m_log.DebugFormat("[GET_DISPLAY_NAMES]: /CAPS/agents/{0} in region {1}", capID, m_scene.RegionInfo.RegionName); + caps.RegisterHandler( + "GetDisplayNames", + new GetDisplayNamesHandler("/CAPS/agents" + capID + "/", m_UserManager, "GetDisplayNames", agentID.ToString())); + } + else + { +// m_log.DebugFormat("[GETTEXTURE]: {0} in region {1}", m_URL, m_scene.RegionInfo.RegionName); + IExternalCapsModule handler = m_scene.RequestModuleInterface(); + if (handler != null) + handler.RegisterExternalUserCapsHandler(agentID,caps,"GetDisplayNames", m_URL); + else + caps.RegisterHandler("GetDisplayNames", m_URL); + } + } + + } +} diff --git a/OpenSim/Region/ClientStack/Linden/Caps/GetMeshModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/GetMeshModule.cs index 8e1f63a..f57d857 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/GetMeshModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/GetMeshModule.cs @@ -57,6 +57,9 @@ namespace OpenSim.Region.ClientStack.Linden private IAssetService m_AssetService; private bool m_Enabled = true; private string m_URL; + private string m_URL2; + private string m_RedirectURL = null; + private string m_RedirectURL2 = null; #region Region Module interfaceBase Members @@ -74,7 +77,18 @@ namespace OpenSim.Region.ClientStack.Linden m_URL = config.GetString("Cap_GetMesh", string.Empty); // Cap doesn't exist if (m_URL != string.Empty) + { + m_Enabled = true; + m_RedirectURL = config.GetString("GetMeshRedirectURL"); + } + + m_URL2 = config.GetString("Cap_GetMesh2", string.Empty); + // Cap doesn't exist + if (m_URL2 != string.Empty) + { m_Enabled = true; + m_RedirectURL2 = config.GetString("GetMesh2RedirectURL"); + } } public void AddRegion(Scene pScene) @@ -113,28 +127,42 @@ namespace OpenSim.Region.ClientStack.Linden public void RegisterCaps(UUID agentID, Caps caps) { -// UUID capID = UUID.Random(); + UUID capID = UUID.Random(); + bool getMeshRegistered = false; - //caps.RegisterHandler("GetTexture", new StreamHandler("GET", "/CAPS/" + capID, ProcessGetTexture)); - if (m_URL == "localhost") + if (m_URL == string.Empty) + { + + } + else if (m_URL == "localhost") { -// m_log.DebugFormat("[GETMESH]: /CAPS/{0} in region {1}", capID, m_scene.RegionInfo.RegionName); - GetMeshHandler gmeshHandler = new GetMeshHandler(m_AssetService); - IRequestHandler reqHandler - = new RestHTTPHandler( - "GET", - "/CAPS/" + UUID.Random(), - httpMethod => gmeshHandler.ProcessGetMesh(httpMethod, UUID.Zero, null), - "GetMesh", - agentID.ToString()); - - caps.RegisterHandler("GetMesh", reqHandler); + getMeshRegistered = true; + caps.RegisterHandler( + "GetMesh", + new GetMeshHandler("/CAPS/" + capID + "/", m_AssetService, "GetMesh", agentID.ToString(), m_RedirectURL)); } else { -// m_log.DebugFormat("[GETMESH]: {0} in region {1}", m_URL, m_scene.RegionInfo.RegionName); caps.RegisterHandler("GetMesh", m_URL); } + + if(m_URL2 == string.Empty) + { + + } + else if (m_URL2 == "localhost") + { + if (!getMeshRegistered) + { + caps.RegisterHandler( + "GetMesh2", + new GetMeshHandler("/CAPS/" + capID + "/", m_AssetService, "GetMesh2", agentID.ToString(), m_RedirectURL2)); + } + } + else + { + caps.RegisterHandler("GetMesh2", m_URL2); + } } } diff --git a/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs index 13415f8..bb932f2 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs @@ -63,7 +63,7 @@ namespace OpenSim.Region.ClientStack.Linden private bool m_Enabled = false; // TODO: Change this to a config option - const string REDIRECT_URL = null; + private string m_RedirectURL = null; private string m_URL; @@ -78,7 +78,10 @@ namespace OpenSim.Region.ClientStack.Linden m_URL = config.GetString("Cap_GetTexture", string.Empty); // Cap doesn't exist if (m_URL != string.Empty) + { m_Enabled = true; + m_RedirectURL = config.GetString("GetTextureRedirectURL"); + } } public void AddRegion(Scene s) @@ -132,12 +135,16 @@ namespace OpenSim.Region.ClientStack.Linden // m_log.DebugFormat("[GETTEXTURE]: /CAPS/{0} in region {1}", capID, m_scene.RegionInfo.RegionName); caps.RegisterHandler( "GetTexture", - new GetTextureHandler("/CAPS/" + capID + "/", m_assetService, "GetTexture", agentID.ToString())); + new GetTextureHandler("/CAPS/" + capID + "/", m_assetService, "GetTexture", agentID.ToString(), m_RedirectURL)); } else { // m_log.DebugFormat("[GETTEXTURE]: {0} in region {1}", m_URL, m_scene.RegionInfo.RegionName); - caps.RegisterHandler("GetTexture", m_URL); + IExternalCapsModule handler = m_scene.RequestModuleInterface(); + if (handler != null) + handler.RegisterExternalUserCapsHandler(agentID,caps,"GetTexture", m_URL); + else + caps.RegisterHandler("GetTexture", m_URL); } } diff --git a/OpenSim/Region/ClientStack/Linden/Caps/MeshUploadFlagModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/MeshUploadFlagModule.cs index 33b1f77..45d33cd 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/MeshUploadFlagModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/MeshUploadFlagModule.cs @@ -57,7 +57,6 @@ namespace OpenSim.Region.ClientStack.Linden public bool Enabled { get; private set; } private Scene m_scene; - private UUID m_agentID; #region ISharedRegionModule Members @@ -118,25 +117,26 @@ namespace OpenSim.Region.ClientStack.Linden public void RegisterCaps(UUID agentID, Caps caps) { IRequestHandler reqHandler - = new RestHTTPHandler("GET", "/CAPS/" + UUID.Random(), MeshUploadFlag, "MeshUploadFlag", agentID.ToString()); + = new RestHTTPHandler( + "GET", "/CAPS/" + UUID.Random(), ht => MeshUploadFlag(ht, agentID), "MeshUploadFlag", agentID.ToString()); caps.RegisterHandler("MeshUploadFlag", reqHandler); - m_agentID = agentID; + } - private Hashtable MeshUploadFlag(Hashtable mDhttpMethod) + private Hashtable MeshUploadFlag(Hashtable mDhttpMethod, UUID agentID) { // m_log.DebugFormat("[MESH UPLOAD FLAG MODULE]: MeshUploadFlag request"); OSDMap data = new OSDMap(); - ScenePresence sp = m_scene.GetScenePresence(m_agentID); + ScenePresence sp = m_scene.GetScenePresence(agentID); data["username"] = sp.Firstname + "." + sp.Lastname; data["display_name_next_update"] = new OSDDate(DateTime.Now); data["legacy_first_name"] = sp.Firstname; data["mesh_upload_status"] = "valid"; data["display_name"] = sp.Firstname + " " + sp.Lastname; data["legacy_last_name"] = sp.Lastname; - data["id"] = m_agentID; + data["id"] = agentID; data["is_display_name_default"] = true; //Send back data diff --git a/OpenSim/Region/ClientStack/Linden/Caps/NewFileAgentInventoryVariablePriceModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/NewFileAgentInventoryVariablePriceModule.cs index 5529550..f69a0bb 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/NewFileAgentInventoryVariablePriceModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/NewFileAgentInventoryVariablePriceModule.cs @@ -44,6 +44,7 @@ using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using Caps = OpenSim.Framework.Capabilities.Caps; using OpenSim.Framework.Capabilities; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.ClientStack.Linden { diff --git a/OpenSim/Region/ClientStack/Linden/Caps/ObjectCaps/ObjectAdd.cs b/OpenSim/Region/ClientStack/Linden/Caps/ObjectCaps/ObjectAdd.cs index 92805e2..94f8bc1 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/ObjectCaps/ObjectAdd.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/ObjectCaps/ObjectAdd.cs @@ -155,6 +155,7 @@ namespace OpenSim.Region.ClientStack.Linden Quaternion rotation = Quaternion.Identity; Vector3 scale = Vector3.Zero; int state = 0; + int lastattach = 0; if (r.Type != OSDType.Map) // not a proper req return responsedata; @@ -224,6 +225,7 @@ namespace OpenSim.Region.ClientStack.Linden ray_target_id = ObjMap["RayTargetId"].AsUUID(); state = ObjMap["State"].AsInteger(); + lastattach = ObjMap["LastAttachPoint"].AsInteger(); try { ray_end = ((OSDArray)ObjMap["RayEnd"]).AsVector3(); @@ -290,6 +292,7 @@ namespace OpenSim.Region.ClientStack.Linden //session_id = rm["session_id"].AsUUID(); state = rm["state"].AsInteger(); + lastattach = rm["last_attach_point"].AsInteger(); try { ray_end = ((OSDArray)rm["ray_end"]).AsVector3(); @@ -331,6 +334,7 @@ namespace OpenSim.Region.ClientStack.Linden pbs.ProfileEnd = (ushort)profile_end; pbs.Scale = scale; pbs.State = (byte)state; + pbs.LastAttachPoint = (byte)lastattach; SceneObjectGroup obj = null; ; diff --git a/OpenSim/Region/ClientStack/Linden/Caps/ObjectCaps/UploadObjectAssetModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/ObjectCaps/UploadObjectAssetModule.cs index 55a503e..769fe28 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/ObjectCaps/UploadObjectAssetModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/ObjectCaps/UploadObjectAssetModule.cs @@ -277,6 +277,7 @@ namespace OpenSim.Region.ClientStack.Linden pbs.ProfileEnd = (ushort) obj.ProfileEnd; pbs.Scale = obj.Scale; pbs.State = (byte) 0; + pbs.LastAttachPoint = (byte) 0; SceneObjectPart prim = new SceneObjectPart(); prim.UUID = UUID.Random(); prim.CreatorID = AgentId; diff --git a/OpenSim/Region/ClientStack/Linden/Caps/Properties/AssemblyInfo.cs b/OpenSim/Region/ClientStack/Linden/Caps/Properties/AssemblyInfo.cs index 060a61c..0adfa1a 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/Properties/AssemblyInfo.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.8.3.*")] + diff --git a/OpenSim/Region/ClientStack/Linden/Caps/RegionConsoleModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/RegionConsoleModule.cs index 17c7270..a133a69 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/RegionConsoleModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/RegionConsoleModule.cs @@ -56,8 +56,8 @@ namespace OpenSim.Region.ClientStack.Linden [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "RegionConsoleModule")] public class RegionConsoleModule : INonSharedRegionModule, IRegionConsole { - private static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); +// private static readonly ILog m_log = +// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private Scene m_scene; private IEventQueue m_eventQueue; @@ -157,8 +157,8 @@ namespace OpenSim.Region.ClientStack.Linden public class ConsoleHandler : BaseStreamHandler { - private static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); +// private static readonly ILog m_log = +// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private RegionConsoleModule m_consoleModule; private UUID m_agentID; @@ -176,7 +176,7 @@ namespace OpenSim.Region.ClientStack.Linden m_isGod = m_scene.Permissions.IsGod(agentID); } - public override byte[] Handle(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + protected override byte[] ProcessRequest(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { StreamReader reader = new StreamReader(request); string message = reader.ReadToEnd(); diff --git a/OpenSim/Region/ClientStack/Linden/Caps/SimulatorFeaturesModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/SimulatorFeaturesModule.cs index 191bccf..e258bcb 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/SimulatorFeaturesModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/SimulatorFeaturesModule.cs @@ -27,6 +27,7 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Reflection; using log4net; using Nini.Config; @@ -37,7 +38,7 @@ using OpenSim.Framework; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; -using OpenSim.Services.Interfaces; +// using OpenSim.Services.Interfaces; using Caps = OpenSim.Framework.Capabilities.Caps; namespace OpenSim.Region.ClientStack.Linden @@ -56,8 +57,10 @@ namespace OpenSim.Region.ClientStack.Linden [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "SimulatorFeaturesModule")] public class SimulatorFeaturesModule : ISharedRegionModule, ISimulatorFeaturesModule { -// private static readonly ILog m_log = -// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public event SimulatorFeaturesRequestDelegate OnSimulatorFeaturesRequest; private Scene m_scene; @@ -66,25 +69,40 @@ namespace OpenSim.Region.ClientStack.Linden /// private OSDMap m_features = new OSDMap(); - private string m_MapImageServerURL = string.Empty; private string m_SearchURL = string.Empty; + private string m_DestinationGuideURL = string.Empty; + private bool m_ExportSupported = false; + private string m_GridName = string.Empty; + private string m_GridURL = string.Empty; #region ISharedRegionModule Members public void Initialise(IConfigSource source) { IConfig config = source.Configs["SimulatorFeatures"]; + if (config != null) - { - m_MapImageServerURL = config.GetString("MapImageServerURI", string.Empty); - if (m_MapImageServerURL != string.Empty) - { - m_MapImageServerURL = m_MapImageServerURL.Trim(); - if (!m_MapImageServerURL.EndsWith("/")) - m_MapImageServerURL = m_MapImageServerURL + "/"; - } - - m_SearchURL = config.GetString("SearchServerURI", string.Empty); + { + // + // All this is obsolete since getting these features from the grid service!! + // Will be removed after the next release + // + m_SearchURL = config.GetString("SearchServerURI", m_SearchURL); + + m_DestinationGuideURL = config.GetString ("DestinationGuideURI", m_DestinationGuideURL); + + if (m_DestinationGuideURL == string.Empty) // Make this consistent with the variable in the LoginService config + m_DestinationGuideURL = config.GetString("DestinationGuide", m_DestinationGuideURL); + + m_ExportSupported = config.GetBoolean("ExportSupported", m_ExportSupported); + + m_GridURL = Util.GetConfigVarFromSections( + source, "GatekeeperURI", new string[] { "Startup", "Hypergrid", "SimulatorFeatures" }, String.Empty); + + m_GridName = config.GetString("GridName", string.Empty); + if (m_GridName == string.Empty) + m_GridName = Util.GetConfigVarFromSections( + source, "gridname", new string[] { "GridInfo", "SimulatorFeatures" }, String.Empty); } AddDefaultFeatures(); @@ -94,6 +112,8 @@ namespace OpenSim.Region.ClientStack.Linden { m_scene = s; m_scene.EventManager.OnRegisterCaps += RegisterCaps; + + m_scene.RegisterModuleInterface(this); } public void RemoveRegion(Scene s) @@ -103,6 +123,7 @@ namespace OpenSim.Region.ClientStack.Linden public void RegionLoaded(Scene s) { + GetGridExtraFeatures(s); } public void PostInitialise() @@ -128,26 +149,43 @@ namespace OpenSim.Region.ClientStack.Linden /// private void AddDefaultFeatures() { + lock (m_features) { m_features["MeshRezEnabled"] = true; m_features["MeshUploadEnabled"] = true; m_features["MeshXferEnabled"] = true; m_features["PhysicsMaterialsEnabled"] = true; - + OSDMap typesMap = new OSDMap(); typesMap["convex"] = true; typesMap["none"] = true; typesMap["prim"] = true; m_features["PhysicsShapeTypes"] = typesMap; - + // Extra information for viewers that want to use it - OSDMap gridServicesMap = new OSDMap(); - if (m_MapImageServerURL != string.Empty) - gridServicesMap["map-server-url"] = m_MapImageServerURL; + // TODO: Take these out of here into their respective modules, like map-server-url + OSDMap extrasMap; + if(m_features.ContainsKey("OpenSimExtras")) + { + extrasMap = (OSDMap)m_features["OpenSimExtras"]; + } + else + extrasMap = new OSDMap(); + if (m_SearchURL != string.Empty) - gridServicesMap["search"] = m_SearchURL; - m_features["GridServices"] = gridServicesMap; + extrasMap["search-server-url"] = m_SearchURL; + if (!string.IsNullOrEmpty(m_DestinationGuideURL)) + extrasMap["destination-guide-url"] = m_DestinationGuideURL; + if (m_ExportSupported) + extrasMap["ExportSupported"] = true; + if (m_GridURL != string.Empty) + extrasMap["GridURL"] = m_GridURL; + if (m_GridName != string.Empty) + extrasMap["GridName"] = m_GridName; + + if (extrasMap.Count > 0) + m_features["OpenSimExtras"] = extrasMap; } } @@ -156,7 +194,7 @@ namespace OpenSim.Region.ClientStack.Linden IRequestHandler reqHandler = new RestHTTPHandler( "GET", "/CAPS/" + UUID.Random(), - HandleSimulatorFeaturesRequest, "SimulatorFeatures", agentID.ToString()); + x => { return HandleSimulatorFeaturesRequest(x, agentID); }, "SimulatorFeatures", agentID.ToString()); caps.RegisterHandler("SimulatorFeatures", reqHandler); } @@ -185,20 +223,81 @@ namespace OpenSim.Region.ClientStack.Linden return new OSDMap(m_features); } - private Hashtable HandleSimulatorFeaturesRequest(Hashtable mDhttpMethod) + private OSDMap DeepCopy() + { + // This isn't the cheapest way of doing this but the rate + // of occurrence is low (on sim entry only) and it's a sure + // way to get a true deep copy. + OSD copy = OSDParser.DeserializeLLSDXml(OSDParser.SerializeLLSDXmlString(m_features)); + + return (OSDMap)copy; + } + + private Hashtable HandleSimulatorFeaturesRequest(Hashtable mDhttpMethod, UUID agentID) { // m_log.DebugFormat("[SIMULATOR FEATURES MODULE]: SimulatorFeatures request"); + OSDMap copy = DeepCopy(); + + // Let's add the agentID to the destination guide, if it is expecting that. + if (copy.ContainsKey("OpenSimExtras") && ((OSDMap)(copy["OpenSimExtras"])).ContainsKey("destination-guide-url")) + ((OSDMap)copy["OpenSimExtras"])["destination-guide-url"] = Replace(((OSDMap)copy["OpenSimExtras"])["destination-guide-url"], "[USERID]", agentID.ToString()); + + SimulatorFeaturesRequestDelegate handlerOnSimulatorFeaturesRequest = OnSimulatorFeaturesRequest; + + if (handlerOnSimulatorFeaturesRequest != null) + handlerOnSimulatorFeaturesRequest(agentID, ref copy); + //Send back data Hashtable responsedata = new Hashtable(); responsedata["int_response_code"] = 200; responsedata["content_type"] = "text/plain"; responsedata["keepalive"] = false; - lock (m_features) - responsedata["str_response_string"] = OSDParser.SerializeLLSDXmlString(m_features); + responsedata["str_response_string"] = OSDParser.SerializeLLSDXmlString(copy); return responsedata; } + + /// + /// Gets the grid extra features. + /// + /// + /// The URI Robust uses to handle the get_extra_features request + /// + private void GetGridExtraFeatures(Scene scene) + { + Dictionary extraFeatures = scene.GridService.GetExtraFeatures(); + if (extraFeatures.ContainsKey("Result") && extraFeatures["Result"] != null && extraFeatures["Result"].ToString() == "Failure") + { + m_log.WarnFormat("[SIMULATOR FEATURES MODULE]: Unable to retrieve grid-wide features"); + return; + } + + lock (m_features) + { + OSDMap extrasMap = new OSDMap(); + + foreach(string key in extraFeatures.Keys) + { + extrasMap[key] = (string)extraFeatures[key]; + + if (key == "ExportSupported") + { + bool.TryParse(extraFeatures[key].ToString(), out m_ExportSupported); + } + } + m_features["OpenSimExtras"] = extrasMap; + + } + } + + private string Replace(string url, string substring, string replacement) + { + if (!String.IsNullOrEmpty(url) && url.Contains(substring)) + return url.Replace(substring, replacement); + + return url; + } } } diff --git a/OpenSim/Region/ClientStack/Linden/Caps/Tests/WebFetchInvDescModuleTests.cs b/OpenSim/Region/ClientStack/Linden/Caps/Tests/WebFetchInvDescModuleTests.cs new file mode 100644 index 0000000..dd4a691 --- /dev/null +++ b/OpenSim/Region/ClientStack/Linden/Caps/Tests/WebFetchInvDescModuleTests.cs @@ -0,0 +1,159 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using HttpServer; +using log4net.Config; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Framework.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.ClientStack.Linden; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; +using OSDArray = OpenMetaverse.StructuredData.OSDArray; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; + +namespace OpenSim.Region.ClientStack.Linden.Caps.Tests +{ + [TestFixture] + public class WebFetchInvDescModuleTests : OpenSimTestCase + { + [TestFixtureSetUp] + public void TestFixtureSetUp() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TestFixureTearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // This is an unfortunate bit of clean up we have to do because MainServer manages things through static + // variables and the VM is not restarted between tests. + uint port = 9999; + MainServer.RemoveHttpServer(port); + + BaseHttpServer server = new BaseHttpServer(port, false, 0, ""); + MainServer.AddHttpServer(server); + MainServer.Instance = server; + + server.Start(false); + } + + [Test] + public void TestInventoryDescendentsFetch() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + BaseHttpServer httpServer = MainServer.Instance; + Scene scene = new SceneHelpers().SetupScene(); + + CapabilitiesModule capsModule = new CapabilitiesModule(); + WebFetchInvDescModule wfidModule = new WebFetchInvDescModule(false); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("ClientStack.LindenCaps"); + config.Configs["ClientStack.LindenCaps"].Set("Cap_FetchInventoryDescendents2", "localhost"); + + SceneHelpers.SetupSceneModules(scene, config, capsModule, wfidModule); + + UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1)); + + // We need a user present to have any capabilities set up + SceneHelpers.AddScenePresence(scene, ua.PrincipalID); + + TestHttpRequest req = new TestHttpRequest(); + OpenSim.Framework.Capabilities.Caps userCaps = capsModule.GetCapsForUser(ua.PrincipalID); + PollServiceEventArgs pseArgs; + userCaps.TryGetPollHandler("FetchInventoryDescendents2", out pseArgs); + req.UriPath = pseArgs.Url; + req.Uri = new Uri("file://" + req.UriPath); + + // Retrieve root folder details directly so that we can request + InventoryFolderBase folder = scene.InventoryService.GetRootFolder(ua.PrincipalID); + + OSDMap osdFolder = new OSDMap(); + osdFolder["folder_id"] = folder.ID; + osdFolder["owner_id"] = ua.PrincipalID; + osdFolder["fetch_folders"] = true; + osdFolder["fetch_items"] = true; + osdFolder["sort_order"] = 0; + + OSDArray osdFoldersArray = new OSDArray(); + osdFoldersArray.Add(osdFolder); + + OSDMap osdReqMap = new OSDMap(); + osdReqMap["folders"] = osdFoldersArray; + + req.Body = new MemoryStream(OSDParser.SerializeLLSDXmlBytes(osdReqMap)); + + TestHttpClientContext context = new TestHttpClientContext(false); + + // WARNING: This results in a caught exception, because queryString is null + MainServer.Instance.OnRequest(context, new RequestEventArgs(req)); + + // Drive processing of the queued inventory request synchronously. + wfidModule.WaitProcessQueuedInventoryRequest(); + MainServer.Instance.PollServiceRequestManager.WaitPerformResponse(); + +// System.Threading.Thread.Sleep(10000); + + OSDMap responseOsd = (OSDMap)OSDParser.DeserializeLLSDXml(context.ResponseBody); + OSDArray foldersOsd = (OSDArray)responseOsd["folders"]; + OSDMap folderOsd = (OSDMap)foldersOsd[0]; + + // A sanity check that the response has the expected number of descendents for a default inventory + // TODO: Need a more thorough check. + Assert.That((int)folderOsd["descendents"], Is.EqualTo(16)); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/ClientStack/Linden/Caps/UploadBakedTextureModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/UploadBakedTextureModule.cs index 3b0ccd7..8cdebcd 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/UploadBakedTextureModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/UploadBakedTextureModule.cs @@ -27,6 +27,7 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; using System.Drawing; using System.Drawing.Imaging; @@ -53,8 +54,8 @@ namespace OpenSim.Region.ClientStack.Linden [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "UploadBakedTextureModule")] public class UploadBakedTextureModule : INonSharedRegionModule { -// private static readonly ILog m_log = -// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); /// /// For historical reasons this is fixed, but there @@ -64,31 +65,210 @@ namespace OpenSim.Region.ClientStack.Linden private Scene m_scene; private bool m_persistBakedTextures; + private IBakedTextureModule m_BakedTextureModule; + public void Initialise(IConfigSource source) { IConfig appearanceConfig = source.Configs["Appearance"]; if (appearanceConfig != null) m_persistBakedTextures = appearanceConfig.GetBoolean("PersistBakedTextures", m_persistBakedTextures); + + } public void AddRegion(Scene s) { m_scene = s; + } public void RemoveRegion(Scene s) { + s.EventManager.OnRegisterCaps -= RegisterCaps; + s.EventManager.OnNewPresence -= RegisterNewPresence; + s.EventManager.OnRemovePresence -= DeRegisterPresence; + m_BakedTextureModule = null; + m_scene = null; } + + public void RegionLoaded(Scene s) { m_scene.EventManager.OnRegisterCaps += RegisterCaps; + m_scene.EventManager.OnNewPresence += RegisterNewPresence; + m_scene.EventManager.OnRemovePresence += DeRegisterPresence; + + } + + private void DeRegisterPresence(UUID agentId) + { + ScenePresence presence = null; + if (m_scene.TryGetScenePresence(agentId, out presence)) + { + presence.ControllingClient.OnSetAppearance -= CaptureAppearanceSettings; + } + + } + + private void RegisterNewPresence(ScenePresence presence) + { + presence.ControllingClient.OnSetAppearance += CaptureAppearanceSettings; + + } + + private void CaptureAppearanceSettings(IClientAPI remoteClient, Primitive.TextureEntry textureEntry, byte[] visualParams, Vector3 avSize, WearableCacheItem[] cacheItems) + { + int maxCacheitemsLoop = cacheItems.Length; + if (maxCacheitemsLoop > AvatarWearable.MAX_WEARABLES) + { + maxCacheitemsLoop = AvatarWearable.MAX_WEARABLES; + m_log.WarnFormat("[CACHEDBAKES]: Too Many Cache items Provided {0}, the max is {1}. Truncating!", cacheItems.Length, AvatarWearable.MAX_WEARABLES); + } + + m_BakedTextureModule = m_scene.RequestModuleInterface(); + if (cacheItems.Length > 0) + { +// m_log.Debug("[Cacheitems]: " + cacheItems.Length); +// for (int iter = 0; iter < maxCacheitemsLoop; iter++) +// { +// m_log.Debug("[Cacheitems] {" + iter + "/" + cacheItems[iter].TextureIndex + "}: c-" + cacheItems[iter].CacheId + ", t-" + +// cacheItems[iter].TextureID); +// } + + ScenePresence p = null; + if (m_scene.TryGetScenePresence(remoteClient.AgentId, out p)) + { + + WearableCacheItem[] existingitems = p.Appearance.WearableCacheItems; + if (existingitems == null) + { + if (m_BakedTextureModule != null) + { + WearableCacheItem[] savedcache = null; + try + { + if (p.Appearance.WearableCacheItemsDirty) + { + savedcache = m_BakedTextureModule.Get(p.UUID); + p.Appearance.WearableCacheItems = savedcache; + p.Appearance.WearableCacheItemsDirty = false; + } + + } + /* + * The following Catch types DO NOT WORK with m_BakedTextureModule.Get + * it jumps to the General Packet Exception Handler if you don't catch Exception! + * + catch (System.Net.Sockets.SocketException) + { + cacheItems = null; + } + catch (WebException) + { + cacheItems = null; + } + catch (InvalidOperationException) + { + cacheItems = null; + } */ + catch (Exception) + { + // The service logs a sufficient error message. + } + + + if (savedcache != null) + existingitems = savedcache; + } + } + // Existing items null means it's a fully new appearance + if (existingitems == null) + { + + for (int i = 0; i < maxCacheitemsLoop; i++) + { + if (textureEntry.FaceTextures.Length > cacheItems[i].TextureIndex) + { + Primitive.TextureEntryFace face = textureEntry.FaceTextures[cacheItems[i].TextureIndex]; + if (face == null) + { + textureEntry.CreateFace(cacheItems[i].TextureIndex); + textureEntry.FaceTextures[cacheItems[i].TextureIndex].TextureID = + AppearanceManager.DEFAULT_AVATAR_TEXTURE; + continue; + } + cacheItems[i].TextureID =face.TextureID; + if (m_scene.AssetService != null) + cacheItems[i].TextureAsset = + m_scene.AssetService.GetCached(cacheItems[i].TextureID.ToString()); + } + else + { + m_log.WarnFormat("[CACHEDBAKES]: Invalid Texture Index Provided, Texture doesn't exist or hasn't been uploaded yet {0}, the max is {1}. Skipping!", cacheItems[i].TextureIndex, textureEntry.FaceTextures.Length); + } + + + } + } + else + + + { + // for each uploaded baked texture + for (int i = 0; i < maxCacheitemsLoop; i++) + { + if (textureEntry.FaceTextures.Length > cacheItems[i].TextureIndex) + { + Primitive.TextureEntryFace face = textureEntry.FaceTextures[cacheItems[i].TextureIndex]; + if (face == null) + { + textureEntry.CreateFace(cacheItems[i].TextureIndex); + textureEntry.FaceTextures[cacheItems[i].TextureIndex].TextureID = + AppearanceManager.DEFAULT_AVATAR_TEXTURE; + continue; + } + cacheItems[i].TextureID = + face.TextureID; + } + else + { + m_log.WarnFormat("[CACHEDBAKES]: Invalid Texture Index Provided, Texture doesn't exist or hasn't been uploaded yet {0}, the max is {1}. Skipping!", cacheItems[i].TextureIndex, textureEntry.FaceTextures.Length); + } + } + + for (int i = 0; i < maxCacheitemsLoop; i++) + { + if (cacheItems[i].TextureAsset == null) + { + cacheItems[i].TextureAsset = + m_scene.AssetService.GetCached(cacheItems[i].TextureID.ToString()); + } + } + } + + + + p.Appearance.WearableCacheItems = cacheItems; + + + + if (m_BakedTextureModule != null) + { + m_BakedTextureModule.Store(remoteClient.AgentId, cacheItems); + p.Appearance.WearableCacheItemsDirty = true; + + } + } + } } public void PostInitialise() { } + + public void Close() { } public string Name { get { return "UploadBakedTextureModule"; } } @@ -100,15 +280,23 @@ namespace OpenSim.Region.ClientStack.Linden public void RegisterCaps(UUID agentID, Caps caps) { + UploadBakedTextureHandler avatarhandler = new UploadBakedTextureHandler( + caps, m_scene.AssetService, m_persistBakedTextures); + + + caps.RegisterHandler( "UploadBakedTexture", new RestStreamHandler( "POST", "/CAPS/" + caps.CapsObjectPath + m_uploadBakedTexturePath, - new UploadBakedTextureHandler( - caps, m_scene.AssetService, m_persistBakedTextures).UploadBakedTexture, + avatarhandler.UploadBakedTexture, "UploadBakedTexture", agentID.ToString())); + + + + } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs index 6890f4a..025ffea 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs @@ -27,15 +27,21 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Reflection; +using System.Threading; using log4net; using Nini.Config; using Mono.Addins; using OpenMetaverse; +using OpenMetaverse.StructuredData; using OpenSim.Framework; +using OpenSim.Framework.Monitoring; +using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework.Capabilities; using OpenSim.Services.Interfaces; using Caps = OpenSim.Framework.Capabilities.Caps; using OpenSim.Capabilities.Handlers; @@ -48,9 +54,37 @@ namespace OpenSim.Region.ClientStack.Linden [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "WebFetchInvDescModule")] public class WebFetchInvDescModule : INonSharedRegionModule { -// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + class aPollRequest + { + public PollServiceInventoryEventArgs thepoll; + public UUID reqID; + public Hashtable request; + public ScenePresence presence; + public List folders; + } + + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Control whether requests will be processed asynchronously. + /// + /// + /// Defaults to true. Can currently not be changed once a region has been added to the module. + /// + public bool ProcessQueuedRequestsAsync { get; private set; } - private Scene m_scene; + /// + /// Number of inventory requests processed by this module. + /// + /// + /// It's the PollServiceRequestManager that actually sends completed requests back to the requester. + /// + public static int ProcessedRequestsCount { get; set; } + + private static Stat s_queuedRequestsStat; + private static Stat s_processedRequestsStat; + + public Scene Scene { get; private set; } private IInventoryService m_InventoryService; private ILibraryService m_LibraryService; @@ -60,10 +94,22 @@ namespace OpenSim.Region.ClientStack.Linden private string m_fetchInventoryDescendents2Url; private string m_webFetchInventoryDescendentsUrl; - private WebFetchInvDescHandler m_webFetchHandler; + private static FetchInvDescHandler m_webFetchHandler; + + private static Thread[] m_workerThreads = null; + + private static DoubleQueue m_queue = + new DoubleQueue(); #region ISharedRegionModule Members + public WebFetchInvDescModule() : this(true) {} + + public WebFetchInvDescModule(bool processQueuedResultsAsync) + { + ProcessQueuedRequestsAsync = processQueuedResultsAsync; + } + public void Initialise(IConfigSource source) { IConfig config = source.Configs["ClientStack.LindenCaps"]; @@ -84,7 +130,7 @@ namespace OpenSim.Region.ClientStack.Linden if (!m_Enabled) return; - m_scene = s; + Scene = s; } public void RemoveRegion(Scene s) @@ -92,8 +138,23 @@ namespace OpenSim.Region.ClientStack.Linden if (!m_Enabled) return; - m_scene.EventManager.OnRegisterCaps -= RegisterCaps; - m_scene = null; + Scene.EventManager.OnRegisterCaps -= RegisterCaps; + + StatsManager.DeregisterStat(s_processedRequestsStat); + StatsManager.DeregisterStat(s_queuedRequestsStat); + + if (ProcessQueuedRequestsAsync) + { + if (m_workerThreads != null) + { + foreach (Thread t in m_workerThreads) + Watchdog.AbortThread(t.ManagedThreadId); + + m_workerThreads = null; + } + } + + Scene = null; } public void RegionLoaded(Scene s) @@ -101,14 +162,61 @@ namespace OpenSim.Region.ClientStack.Linden if (!m_Enabled) return; - m_InventoryService = m_scene.InventoryService; - m_LibraryService = m_scene.LibraryService; + if (s_processedRequestsStat == null) + s_processedRequestsStat = + new Stat( + "ProcessedFetchInventoryRequests", + "Number of processed fetch inventory requests", + "These have not necessarily yet been dispatched back to the requester.", + "", + "inventory", + "httpfetch", + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => { stat.Value = ProcessedRequestsCount; }, + StatVerbosity.Debug); + + if (s_queuedRequestsStat == null) + s_queuedRequestsStat = + new Stat( + "QueuedFetchInventoryRequests", + "Number of fetch inventory requests queued for processing", + "", + "", + "inventory", + "httpfetch", + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => { stat.Value = m_queue.Count; }, + StatVerbosity.Debug); + + StatsManager.RegisterStat(s_processedRequestsStat); + StatsManager.RegisterStat(s_queuedRequestsStat); + + m_InventoryService = Scene.InventoryService; + m_LibraryService = Scene.LibraryService; // We'll reuse the same handler for all requests. - if (m_fetchInventoryDescendents2Url == "localhost" || m_webFetchInventoryDescendentsUrl == "localhost") - m_webFetchHandler = new WebFetchInvDescHandler(m_InventoryService, m_LibraryService); + m_webFetchHandler = new FetchInvDescHandler(m_InventoryService, m_LibraryService, Scene); - m_scene.EventManager.OnRegisterCaps += RegisterCaps; + Scene.EventManager.OnRegisterCaps += RegisterCaps; + + int nworkers = 2; // was 2 + if (ProcessQueuedRequestsAsync && m_workerThreads == null) + { + m_workerThreads = new Thread[nworkers]; + + for (uint i = 0; i < nworkers; i++) + { + m_workerThreads[i] = WorkManager.StartThread(DoInventoryRequests, + String.Format("InventoryWorkerThread{0}", i), + ThreadPriority.Normal, + false, + true, + null, + int.MaxValue); + } + } } public void PostInitialise() @@ -126,43 +234,221 @@ namespace OpenSim.Region.ClientStack.Linden #endregion - private void RegisterCaps(UUID agentID, Caps caps) + private class PollServiceInventoryEventArgs : PollServiceEventArgs { - if (m_webFetchInventoryDescendentsUrl != "") - RegisterFetchCap(agentID, caps, "WebFetchInventoryDescendents", m_webFetchInventoryDescendentsUrl); + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Dictionary responses = + new Dictionary(); + + private WebFetchInvDescModule m_module; - if (m_fetchInventoryDescendents2Url != "") - RegisterFetchCap(agentID, caps, "FetchInventoryDescendents2", m_fetchInventoryDescendents2Url); + public PollServiceInventoryEventArgs(WebFetchInvDescModule module, string url, UUID pId) : + base(null, url, null, null, null, pId, int.MaxValue) + { + m_module = module; + + HasEvents = (x, y) => { lock (responses) return responses.ContainsKey(x); }; + GetEvents = (x, y) => + { + lock (responses) + { + try + { + return responses[x]; + } + finally + { + responses.Remove(x); + } + } + }; + + Request = (x, y) => + { + ScenePresence sp = m_module.Scene.GetScenePresence(Id); + + aPollRequest reqinfo = new aPollRequest(); + reqinfo.thepoll = this; + reqinfo.reqID = x; + reqinfo.request = y; + reqinfo.presence = sp; + reqinfo.folders = new List(); + + // Decode the request here + string request = y["body"].ToString(); + + request = request.Replace("00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000000"); + + request = request.Replace("fetch_folders0", "fetch_folders0"); + request = request.Replace("fetch_folders1", "fetch_folders1"); + + Hashtable hash = new Hashtable(); + try + { + hash = (Hashtable)LLSD.LLSDDeserialize(Utils.StringToBytes(request)); + } + catch (LLSD.LLSDParseException e) + { + m_log.ErrorFormat("[INVENTORY]: Fetch error: {0}{1}" + e.Message, e.StackTrace); + m_log.Error("Request: " + request); + return; + } + catch (System.Xml.XmlException) + { + m_log.ErrorFormat("[INVENTORY]: XML Format error"); + } + + ArrayList foldersrequested = (ArrayList)hash["folders"]; + + bool highPriority = false; + + for (int i = 0; i < foldersrequested.Count; i++) + { + Hashtable inventoryhash = (Hashtable)foldersrequested[i]; + string folder = inventoryhash["folder_id"].ToString(); + UUID folderID; + if (UUID.TryParse(folder, out folderID)) + { + if (!reqinfo.folders.Contains(folderID)) + { + //TODO: Port COF handling from Avination + reqinfo.folders.Add(folderID); + } + } + } + + if (highPriority) + m_queue.EnqueueHigh(reqinfo); + else + m_queue.EnqueueLow(reqinfo); + }; + + NoEvents = (x, y) => + { +/* + lock (requests) + { + Hashtable request = requests.Find(id => id["RequestID"].ToString() == x.ToString()); + requests.Remove(request); + } +*/ + Hashtable response = new Hashtable(); + + response["int_response_code"] = 500; + response["str_response_string"] = "Script timeout"; + response["content_type"] = "text/plain"; + response["keepalive"] = false; + response["reusecontext"] = false; + + return response; + }; + } + + public void Process(aPollRequest requestinfo) + { + UUID requestID = requestinfo.reqID; + + Hashtable response = new Hashtable(); + + response["int_response_code"] = 200; + response["content_type"] = "text/plain"; + response["keepalive"] = false; + response["reusecontext"] = false; + + response["str_response_string"] = m_webFetchHandler.FetchInventoryDescendentsRequest( + requestinfo.request["body"].ToString(), String.Empty, String.Empty, null, null); + + lock (responses) + { + if (responses.ContainsKey(requestID)) + m_log.WarnFormat("[FETCH INVENTORY DESCENDENTS2 MODULE]: Caught in the act of loosing responses! Please report this on mantis #7054"); + responses[requestID] = response; + } + + WebFetchInvDescModule.ProcessedRequestsCount++; + } + } + + private void RegisterCaps(UUID agentID, Caps caps) + { + RegisterFetchDescendentsCap(agentID, caps, "FetchInventoryDescendents2", m_fetchInventoryDescendents2Url); } - private void RegisterFetchCap(UUID agentID, Caps caps, string capName, string url) + private void RegisterFetchDescendentsCap(UUID agentID, Caps caps, string capName, string url) { string capUrl; - if (url == "localhost") + // disable the cap clause + if (url == "") + { + return; + } + // handled by the simulator + else if (url == "localhost") { - capUrl = "/CAPS/" + UUID.Random(); + capUrl = "/CAPS/" + UUID.Random() + "/"; - IRequestHandler reqHandler - = new RestStreamHandler( - "POST", - capUrl, - m_webFetchHandler.FetchInventoryDescendentsRequest, - "FetchInventoryDescendents2", - agentID.ToString()); + // Register this as a poll service + PollServiceInventoryEventArgs args = new PollServiceInventoryEventArgs(this, capUrl, agentID); + args.Type = PollServiceEventArgs.EventType.Inventory; - caps.RegisterHandler(capName, reqHandler); + caps.RegisterPollHandler(capName, args); } + // external handler else { capUrl = url; + IExternalCapsModule handler = Scene.RequestModuleInterface(); + if (handler != null) + handler.RegisterExternalUserCapsHandler(agentID,caps,capName,capUrl); + else + caps.RegisterHandler(capName, capUrl); + } - caps.RegisterHandler(capName, capUrl); + // m_log.DebugFormat( + // "[FETCH INVENTORY DESCENDENTS2 MODULE]: Registered capability {0} at {1} in region {2} for {3}", + // capName, capUrl, m_scene.RegionInfo.RegionName, agentID); + } + +// private void DeregisterCaps(UUID agentID, Caps caps) +// { +// string capUrl; +// +// if (m_capsDict.TryGetValue(agentID, out capUrl)) +// { +// MainServer.Instance.RemoveHTTPHandler("", capUrl); +// m_capsDict.Remove(agentID); +// } +// } + + private void DoInventoryRequests() + { + while (true) + { + Watchdog.UpdateThread(); + + WaitProcessQueuedInventoryRequest(); } + } + + public void WaitProcessQueuedInventoryRequest() + { + aPollRequest poolreq = m_queue.Dequeue(); -// m_log.DebugFormat( -// "[WEB FETCH INV DESC MODULE]: Registered capability {0} at {1} in region {2} for {3}", -// capName, capUrl, m_scene.RegionInfo.RegionName, agentID); + if (poolreq != null && poolreq.thepoll != null) + { + try + { + poolreq.thepoll.Process(poolreq); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[INVENTORY]: Failed to process queued inventory request {0} for {1} in {2}. Exception {3}", + poolreq.reqID, poolreq.presence != null ? poolreq.presence.Name : "unknown", Scene.Name, e); + } + } } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/ClientStack/Linden/UDP/J2KImage.cs b/OpenSim/Region/ClientStack/Linden/UDP/J2KImage.cs index afbe56b..4d0568d 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/J2KImage.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/J2KImage.cs @@ -421,12 +421,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP // foreign user is visiting, we need to try again after the first fail to the local // asset service. string assetServerURL = string.Empty; - if (InventoryAccessModule.IsForeignUser(AgentID, out assetServerURL)) + if (InventoryAccessModule.IsForeignUser(AgentID, out assetServerURL) && !string.IsNullOrEmpty(assetServerURL)) { if (!assetServerURL.EndsWith("/") && !assetServerURL.EndsWith("=")) assetServerURL = assetServerURL + "/"; - m_log.DebugFormat("[J2KIMAGE]: texture {0} not found in local asset storage. Trying user's storage.", assetServerURL + id); +// m_log.DebugFormat("[J2KIMAGE]: texture {0} not found in local asset storage. Trying user's storage.", assetServerURL + id); AssetService.Get(assetServerURL + id, InventoryAccessModule, AssetReceived); return; } diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs index 967fa44..b17b822 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs @@ -34,11 +34,13 @@ using System.Text; using System.Threading; using System.Timers; using System.Xml; + using log4net; using OpenMetaverse; using OpenMetaverse.Packets; using OpenMetaverse.Messages.Linden; using OpenMetaverse.StructuredData; + using OpenSim.Framework; using OpenSim.Framework.Client; using OpenSim.Framework.Monitoring; @@ -48,9 +50,9 @@ using OpenSim.Services.Interfaces; using Timer = System.Timers.Timer; using AssetLandmark = OpenSim.Framework.AssetLandmark; using RegionFlags = OpenMetaverse.RegionFlags; -using Nini.Config; using System.IO; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.ClientStack.LindenUDP { @@ -69,7 +71,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP #region Events - public event GenericMessage OnGenericMessage; public event BinaryGenericMessage OnBinaryGenericMessage; public event Action OnLogout; public event ObjectPermissions OnObjectPermissions; @@ -77,7 +78,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP public event ViewerEffectEventHandler OnViewerEffect; public event ImprovedInstantMessage OnInstantMessage; public event ChatMessage OnChatFromClient; - public event TextureRequest OnRequestTexture; public event RezObject OnRezObject; public event DeRezObject OnDeRezObject; public event ModifyTerrain OnModifyTerrain; @@ -94,6 +94,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP public event Action OnCompleteMovementToRegion; public event UpdateAgent OnPreAgentUpdate; public event UpdateAgent OnAgentUpdate; + public event UpdateAgent OnAgentCameraUpdate; public event AgentRequestSit OnAgentRequestSit; public event AgentSit OnAgentSit; public event AvatarPickerRequest OnAvatarPickerRequest; @@ -134,15 +135,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP public event UpdatePrimGroupRotation OnUpdatePrimGroupMouseRotation; public event UpdateVector OnUpdatePrimScale; public event UpdateVector OnUpdatePrimGroupScale; - public event StatusChange OnChildAgentStatus; - public event GenericCall2 OnStopMovement; - public event Action OnRemoveAvatar; public event RequestMapBlocks OnRequestMapBlocks; public event RequestMapName OnMapNameRequest; public event TeleportLocationRequest OnTeleportLocationRequest; public event TeleportLandmarkRequest OnTeleportLandmarkRequest; public event TeleportCancel OnTeleportCancel; - public event DisconnectUser OnDisconnectUser; public event RequestAvatarProperties OnRequestAvatarProperties; public event SetAlwaysRun OnSetAlwaysRun; public event FetchInventory OnAgentDataUpdateRequest; @@ -172,7 +169,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP public event UpdateTaskInventory OnUpdateTaskInventory; public event MoveTaskInventory OnMoveTaskItem; public event RemoveTaskInventory OnRemoveTaskItem; - public event RequestAsset OnRequestAsset; public event UUIDNameRequest OnNameFromUUIDRequest; public event ParcelAccessListRequest OnParcelAccessListRequest; public event ParcelAccessListUpdateRequest OnParcelAccessListUpdateRequest; @@ -203,7 +199,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP public event RequestPayPrice OnRequestPayPrice; public event ObjectSaleInfo OnObjectSaleInfo; public event ObjectBuy OnObjectBuy; - public event BuyObjectInventory OnBuyObjectInventory; public event AgentSit OnUndo; public event AgentSit OnRedo; public event LandUndo OnLandUndo; @@ -212,7 +207,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP public event RequestObjectPropertiesFamily OnObjectGroupRequest; public event DetailedEstateDataRequest OnDetailedEstateDataRequest; public event SetEstateFlagsRequest OnSetEstateFlagsRequest; - public event SetEstateTerrainBaseTexture OnSetEstateTerrainBaseTexture; public event SetEstateTerrainDetailTexture OnSetEstateTerrainDetailTexture; public event SetEstateTerrainTextureHeights OnSetEstateTerrainTextureHeights; public event CommitEstateTerrainTextureRequest OnCommitEstateTerrainTextureRequest; @@ -235,7 +229,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP public event GetScriptRunning OnGetScriptRunning; public event SetScriptRunning OnSetScriptRunning; public event Action OnAutoPilotGo; - public event TerrainUnacked OnUnackedTerrain; public event ActivateGesture OnActivateGesture; public event DeactivateGesture OnDeactivateGesture; public event ObjectOwner OnObjectOwner; @@ -293,6 +286,20 @@ namespace OpenSim.Region.ClientStack.LindenUDP public event GodlikeMessage onGodlikeMessage; public event GodUpdateRegionInfoUpdate OnGodUpdateRegionInfoUpdate; +#pragma warning disable 0067 + public event GenericMessage OnGenericMessage; + public event TextureRequest OnRequestTexture; + public event StatusChange OnChildAgentStatus; + public event GenericCall2 OnStopMovement; + public event Action OnRemoveAvatar; + public event DisconnectUser OnDisconnectUser; + public event RequestAsset OnRequestAsset; + public event BuyObjectInventory OnBuyObjectInventory; + public event SetEstateTerrainBaseTexture OnSetEstateTerrainBaseTexture; + public event TerrainUnacked OnUnackedTerrain; + public event CachedTextureRequest OnCachedTextureRequest; +#pragma warning restore 0067 + #endregion Events #region Class Members @@ -304,6 +311,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP private const float m_sunPainDaHalfOrbitalCutoff = 4.712388980384689858f; private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static string LogHeader = "[LLCLIENTVIEW]"; protected static Dictionary PacketHandlers = new Dictionary(); //Global/static handlers for all clients /// @@ -325,7 +333,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP private PriorityQueue m_entityProps; private Prioritizer m_prioritizer; private bool m_disableFacelights = false; - + private volatile bool m_justEditedTerrain = false; /// /// List used in construction of data blocks for an object update packet. This is to stop us having to /// continually recreate it. @@ -344,7 +352,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP // protected HashSet m_attachmentsSent; - private int m_moneyBalance; private int m_animationSequenceNumber = 1; private bool m_SendLogoutPacketWhenClosing = true; @@ -357,7 +364,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// This does mean that agent updates must be processed synchronously, at least for each client, and called methods /// cannot retain a reference to it outside of that method. /// - private AgentUpdateArgs m_lastAgentUpdateArgs; + private AgentUpdateArgs m_thisAgentUpdateArgs = new AgentUpdateArgs(); protected Dictionary m_packetHandlers = new Dictionary(); protected Dictionary m_genericPacketHandlers = new Dictionary(); //PauPaw:Local Generic Message handlers @@ -393,9 +400,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP } public UUID AgentId { get { return m_agentId; } } public ISceneAgent SceneAgent { get; set; } - public UUID ActiveGroupId { get { return m_activeGroupID; } } - public string ActiveGroupName { get { return m_activeGroupName; } } - public ulong ActiveGroupPowers { get { return m_activeGroupPowers; } } + public UUID ActiveGroupId { get { return m_activeGroupID; } private set { m_activeGroupID = value; } } + public string ActiveGroupName { get { return m_activeGroupName; } private set { m_activeGroupName = value; } } + public ulong ActiveGroupPowers { get { return m_activeGroupPowers; } private set { m_activeGroupPowers = value; } } public bool IsGroupMember(UUID groupID) { return m_groupPowers.ContainsKey(groupID); } /// @@ -419,7 +426,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP public string Name { get { return FirstName + " " + LastName; } } public uint CircuitCode { get { return m_circuitCode; } } - public int MoneyBalance { get { return m_moneyBalance; } } public int NextAnimationSequenceNumber { get { return m_animationSequenceNumber++; } } /// @@ -447,7 +453,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP // ~LLClientView() // { -// m_log.DebugFormat("[LLCLIENTVIEW]: Destructor called for {0}, circuit code {1}", Name, CircuitCode); +// m_log.DebugFormat("{0} Destructor called for {1}, circuit code {2}", LogHeader, Name, CircuitCode); // } /// @@ -482,11 +488,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_firstName = sessionInfo.LoginInfo.First; m_lastName = sessionInfo.LoginInfo.Last; m_startpos = sessionInfo.LoginInfo.StartPos; - m_moneyBalance = 1000; m_udpServer = udpServer; m_udpClient = udpClient; m_udpClient.OnQueueEmpty += HandleQueueEmpty; + m_udpClient.HasUpdates += HandleHasUpdates; m_udpClient.OnPacketStats += PopulateStats; m_prioritizer = new Prioritizer(m_scene); @@ -512,7 +518,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP // We still perform a force close inside the sync lock since this is intended to attempt close where // there is some unidentified connection problem, not where we have issues due to deadlock if (!IsActive && !force) + { + m_log.DebugFormat( "{0} Not attempting to close inactive client {1} in {2} since force flag is not set", + LogHeader, Name, m_scene.Name); + return; + } IsActive = false; CloseWithoutChecks(); @@ -637,12 +648,36 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// true if the handler was added. This is currently always the case. public bool AddLocalPacketHandler(PacketType packetType, PacketMethod handler, bool doAsync) { + return AddLocalPacketHandler(packetType, handler, doAsync, false); + } + + /// + /// Add a handler for the given packet type. + /// + /// + /// + /// + /// If true, when the packet is received handle it on a different thread. Whether this is given direct to + /// a threadpool thread or placed in a queue depends on the inEngine parameter. + /// + /// + /// If async is false then this parameter is ignored. + /// If async is true and inEngine is false, then the packet is sent directly to a + /// threadpool thread. + /// If async is true and inEngine is true, then the packet is sent to the IncomingPacketAsyncHandlingEngine. + /// This may result in slower handling but reduces the risk of overloading the simulator when there are many + /// simultaneous async requests. + /// + /// true if the handler was added. This is currently always the case. + public bool AddLocalPacketHandler(PacketType packetType, PacketMethod handler, bool doAsync, bool inEngine) + { bool result = false; lock (m_packetHandlers) { if (!m_packetHandlers.ContainsKey(packetType)) { - m_packetHandlers.Add(packetType, new PacketProcessor() { method = handler, Async = doAsync }); + m_packetHandlers.Add( + packetType, new PacketProcessor() { method = handler, Async = doAsync, InEngine = inEngine }); result = true; } } @@ -677,15 +712,30 @@ namespace OpenSim.Region.ClientStack.LindenUDP PacketProcessor pprocessor; if (m_packetHandlers.TryGetValue(packet.Type, out pprocessor)) { + ClientInfo cinfo = UDPClient.GetClientInfo(); + //there is a local handler for this packet type if (pprocessor.Async) { + if (!cinfo.AsyncRequests.ContainsKey(packet.Type.ToString())) + cinfo.AsyncRequests[packet.Type.ToString()] = 0; + cinfo.AsyncRequests[packet.Type.ToString()]++; + object obj = new AsyncPacketProcess(this, pprocessor.method, packet); - Util.FireAndForget(ProcessSpecificPacketAsync, obj); + + if (pprocessor.InEngine) + m_udpServer.IpahEngine.QueueJob(packet.Type.ToString(), () => ProcessSpecificPacketAsync(obj)); + else + Util.FireAndForget(ProcessSpecificPacketAsync, obj, packet.Type.ToString()); + result = true; } else { + if (!cinfo.SyncRequests.ContainsKey(packet.Type.ToString())) + cinfo.SyncRequests[packet.Type.ToString()] = 0; + cinfo.SyncRequests[packet.Type.ToString()]++; + result = pprocessor.method(this, packet); } } @@ -700,6 +750,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP } if (found) { + ClientInfo cinfo = UDPClient.GetClientInfo(); + if (!cinfo.GenericRequests.ContainsKey(packet.Type.ToString())) + cinfo.GenericRequests[packet.Type.ToString()] = 0; + cinfo.GenericRequests[packet.Type.ToString()]++; + result = method(this, packet); } } @@ -717,9 +772,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP catch (Exception e) { // Make sure that we see any exception caused by the asynchronous operation. - m_log.ErrorFormat( - "[LLCLIENTVIEW]: Caught exception while processing {0} for {1}, {2} {3}", - packetObject.Pack, Name, e.Message, e.StackTrace); + m_log.Error( + string.Format( + "[LLCLIENTVIEW]: Caught exception while processing {0} for {1} ", packetObject.Pack, Name), + e); } } @@ -729,7 +785,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP public virtual void Start() { - m_scene.AddNewClient(this, PresenceType.User); + m_scene.AddNewAgent(this, PresenceType.User); RefreshGroupMembership(); } @@ -791,9 +847,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP handshake.RegionInfo3.ProductName = Util.StringToBytes256(regionInfo.RegionType); handshake.RegionInfo3.ProductSKU = Utils.EmptyBytes; - OutPacket(handshake, ThrottleOutPacketType.Task); + handshake.RegionInfo4 = new RegionHandshakePacket.RegionInfo4Block[1]; + handshake.RegionInfo4[0] = new RegionHandshakePacket.RegionInfo4Block(); + handshake.RegionInfo4[0].RegionFlagsExtended = args.regionFlags; + handshake.RegionInfo4[0].RegionProtocols = 0; // 1 here would indicate that SSB is supported + + OutPacket(handshake, ThrottleOutPacketType.Unknown); } + public void MoveAgentIntoRegion(RegionInfo regInfo, Vector3 pos, Vector3 look) { AgentMovementCompletePacket mov = (AgentMovementCompletePacket)PacketPool.Instance.GetPacket(PacketType.AgentMovementComplete); @@ -893,9 +955,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } - public void SendGenericMessage(string method, List message) + public void SendGenericMessage(string method, UUID invoice, List message) { GenericMessagePacket gmp = new GenericMessagePacket(); + + gmp.AgentData.AgentID = AgentId; + gmp.AgentData.SessionID = m_sessionId; + gmp.AgentData.TransactionID = invoice; + gmp.MethodData.Method = Util.StringToBytes256(method); gmp.ParamList = new GenericMessagePacket.ParamListBlock[message.Count]; int i = 0; @@ -908,9 +975,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP OutPacket(gmp, ThrottleOutPacketType.Task); } - public void SendGenericMessage(string method, List message) + public void SendGenericMessage(string method, UUID invoice, List message) { GenericMessagePacket gmp = new GenericMessagePacket(); + + gmp.AgentData.AgentID = AgentId; + gmp.AgentData.SessionID = m_sessionId; + gmp.AgentData.TransactionID = invoice; + gmp.MethodData.Method = Util.StringToBytes256(method); gmp.ParamList = new GenericMessagePacket.ParamListBlock[message.Count]; int i = 0; @@ -1112,11 +1184,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// /// Send the region heightmap to the client + /// This method is only called when not doing intellegent terrain patch sending and + /// is only called when the scene presence is initially created and sends all of the + /// region's patches to the client. /// /// heightmap public virtual void SendLayerData(float[] map) { - Util.FireAndForget(DoSendLayerData, map); + Util.FireAndForget(DoSendLayerData, m_scene.Heightmap.GetTerrainData(), "LLClientView.DoSendLayerData"); } /// @@ -1125,10 +1200,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// private void DoSendLayerData(object o) { - float[] map = LLHeightFieldMoronize((float[])o); + TerrainData map = (TerrainData)o; try { + // Send LayerData in typerwriter pattern //for (int y = 0; y < 16; y++) //{ // for (int x = 0; x < 16; x++) @@ -1138,7 +1214,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP //} // Send LayerData in a spiral pattern. Fun! - SendLayerTopRight(map, 0, 0, 15, 15); + SendLayerTopRight(map, 0, 0, map.SizeX/Constants.TerrainPatchSize-1, map.SizeY/Constants.TerrainPatchSize-1); } catch (Exception e) { @@ -1146,7 +1222,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } - private void SendLayerTopRight(float[] map, int x1, int y1, int x2, int y2) + private void SendLayerTopRight(TerrainData map, int x1, int y1, int x2, int y2) { // Row for (int i = x1; i <= x2; i++) @@ -1156,11 +1232,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP for (int j = y1 + 1; j <= y2; j++) SendLayerData(x2, j, map); - if (x2 - x1 > 0) + if (x2 - x1 > 0 && y2 - y1 > 0) SendLayerBottomLeft(map, x1, y1 + 1, x2 - 1, y2); } - void SendLayerBottomLeft(float[] map, int x1, int y1, int x2, int y2) + void SendLayerBottomLeft(TerrainData map, int x1, int y1, int x2, int y2) { // Row in reverse for (int i = x2; i >= x1; i--) @@ -1170,7 +1246,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP for (int j = y2 - 1; j >= y1; j--) SendLayerData(x1, j, map); - if (x2 - x1 > 0) + if (x2 - x1 > 0 && y2 - y1 > 0) SendLayerTopRight(map, x1 + 1, y1, x2, y2 - 1); } @@ -1192,25 +1268,98 @@ namespace OpenSim.Region.ClientStack.LindenUDP // OutPacket(layerpack, ThrottleOutPacketType.Land); // } + // Legacy form of invocation that passes around a bare data array. + // Just ignore what was passed and use the real terrain info that is part of the scene. + // As a HORRIBLE kludge in an attempt to not change the definition of IClientAPI, + // there is a special form for specifying multiple terrain patches to send. + // The form is to pass 'px' as negative the number of patches to send and to + // pass the float array as pairs of patch X and Y coordinates. So, passing 'px' + // as -2 and map= [3, 5, 8, 4] would mean to send two terrain heightmap patches + // and the patches to send are <3,5> and <8,4>. + public void SendLayerData(int px, int py, float[] map) + { + if (px >= 0) + { + SendLayerData(px, py, m_scene.Heightmap.GetTerrainData()); + } + else + { + int numPatches = -px; + int[] xPatches = new int[numPatches]; + int[] yPatches = new int[numPatches]; + for (int pp = 0; pp < numPatches; pp++) + { + xPatches[pp] = (int)map[pp * 2]; + yPatches[pp] = (int)map[pp * 2 + 1]; + } + + // DebugSendingPatches("SendLayerData", xPatches, yPatches); + + SendLayerData(xPatches, yPatches, m_scene.Heightmap.GetTerrainData()); + } + } + + private void DebugSendingPatches(string pWho, int[] pX, int[] pY) + { + if (m_log.IsDebugEnabled) + { + int numPatches = pX.Length; + string Xs = ""; + string Ys = ""; + for (int pp = 0; pp < numPatches; pp++) + { + Xs += String.Format("{0}", (int)pX[pp]) + ","; + Ys += String.Format("{0}", (int)pY[pp]) + ","; + } + m_log.DebugFormat("{0} {1}: numPatches={2}, X={3}, Y={4}", LogHeader, pWho, numPatches, Xs, Ys); + } + } + /// - /// Sends a specified patch to a client + /// Sends a terrain packet for the point specified. + /// This is a legacy call that has refarbed the terrain into a flat map of floats. + /// We just use the terrain from the region we know about. /// /// Patch coordinate (x) 0..15 /// Patch coordinate (y) 0..15 /// heightmap - public void SendLayerData(int px, int py, float[] map) + public void SendLayerData(int px, int py, TerrainData terrData) + { + int[] xPatches = new[] { px }; + int[] yPatches = new[] { py }; + SendLayerData(xPatches, yPatches, terrData); + } + + private void SendLayerData(int[] px, int[] py, TerrainData terrData) { try { - int[] patches = new int[] { py * 16 + px }; - float[] heightmap = (map.Length == 65536) ? - map : - LLHeightFieldMoronize(map); + /* test code using the terrain compressor in libOpenMetaverse + int[] patchInd = new int[1]; + patchInd[0] = px + (py * Constants.TerrainPatchSize); + LayerDataPacket layerpack = TerrainCompressor.CreateLandPacket(terrData.GetFloatsSerialized(), patchInd); + */ + // Many, many patches could have been passed to us. Since the patches will be compressed + // into variable sized blocks, we cannot pre-compute how many will fit into one + // packet. While some fancy packing algorithm is possible, 4 seems to always fit. + int PatchesAssumedToFit = 4; + for (int pcnt = 0; pcnt < px.Length; pcnt += PatchesAssumedToFit) + { + int remaining = Math.Min(px.Length - pcnt, PatchesAssumedToFit); + int[] xPatches = new int[remaining]; + int[] yPatches = new int[remaining]; + for (int ii = 0; ii < remaining; ii++) + { + xPatches[ii] = px[pcnt + ii]; + yPatches[ii] = py[pcnt + ii]; + } + LayerDataPacket layerpack = OpenSimTerrainCompressor.CreateLandPacket(terrData, xPatches, yPatches); + // DebugSendingPatches("SendLayerDataInternal", xPatches, yPatches); - LayerDataPacket layerpack = TerrainCompressor.CreateLandPacket(heightmap, patches); - layerpack.Header.Reliable = true; + SendTheLayerPacket(layerpack); + } + // LayerDataPacket layerpack = OpenSimTerrainCompressor.CreateLandPacket(terrData, px, py); - OutPacket(layerpack, ThrottleOutPacketType.Land); } catch (Exception e) { @@ -1218,36 +1367,34 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } - /// - /// Munges heightfield into the LLUDP backed in restricted heightfield. - /// - /// float array in the base; Constants.RegionSize - /// float array in the base 256 - internal float[] LLHeightFieldMoronize(float[] map) + // When a user edits the terrain, so much data is sent, the data queues up fast and presents a + // sub optimal editing experience. To alleviate this issue, when the user edits the terrain, we + // start skipping the queues until they're done editing the terrain. We also make them + // unreliable because it's extremely likely that multiple packets will be sent for a terrain patch + // area invalidating previous packets for that area. + + // It's possible for an editing user to flood themselves with edited packets but the majority + // of use cases are such that only a tiny percentage of users will be editing the terrain. + // Other, non-editing users will see the edits much slower. + + // One last note on this topic, by the time users are going to be editing the terrain, it's + // extremely likely that the sim will have rezzed already and therefore this is not likely going + // to cause any additional issues with lost packets, objects or terrain patches. + + // m_justEditedTerrain is volatile, so test once and duplicate two affected statements so we + // only have one cache miss. + private void SendTheLayerPacket(LayerDataPacket layerpack) { - if (map.Length == 65536) - return map; + if (m_justEditedTerrain) + { + layerpack.Header.Reliable = false; + OutPacket(layerpack, ThrottleOutPacketType.Unknown ); + } else { - float[] returnmap = new float[65536]; - - if (map.Length < 65535) - { - // rebase the vector stride to 256 - for (int i = 0; i < Constants.RegionSize; i++) - Array.Copy(map, i * (int)Constants.RegionSize, returnmap, i * 256, (int)Constants.RegionSize); - } - else - { - for (int i = 0; i < 256; i++) - Array.Copy(map, i * (int)Constants.RegionSize, returnmap, i * 256, 256); - } - - //Array.Copy(map,0,returnmap,0,(map.Length < 65536)? map.Length : 65536); - - return returnmap; + layerpack.Header.Reliable = true; + OutPacket(layerpack, ThrottleOutPacketType.Land); } - } /// @@ -1256,7 +1403,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// 16x16 array of wind speeds public virtual void SendWindData(Vector2[] windSpeeds) { - Util.FireAndForget(DoSendWindData, windSpeeds); + Util.FireAndForget(DoSendWindData, windSpeeds, "LLClientView.SendWindData"); } /// @@ -1265,7 +1412,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// 16x16 array of cloud densities public virtual void SendCloudData(float[] cloudDensity) { - Util.FireAndForget(DoSendCloudData, cloudDensity); + Util.FireAndForget(DoSendCloudData, cloudDensity, "LLClientView.SendCloudData"); } /// @@ -1276,21 +1423,22 @@ namespace OpenSim.Region.ClientStack.LindenUDP { Vector2[] windSpeeds = (Vector2[])o; TerrainPatch[] patches = new TerrainPatch[2]; - patches[0] = new TerrainPatch(); - patches[0].Data = new float[16 * 16]; - patches[1] = new TerrainPatch(); - patches[1].Data = new float[16 * 16]; + patches[0] = new TerrainPatch { Data = new float[16 * 16] }; + patches[1] = new TerrainPatch { Data = new float[16 * 16] }; - for (int y = 0; y < 16; y++) + for (int x = 0; x < 16 * 16; x++) { - for (int x = 0; x < 16; x++) - { - patches[0].Data[y * 16 + x] = windSpeeds[y * 16 + x].X; - patches[1].Data[y * 16 + x] = windSpeeds[y * 16 + x].Y; - } + patches[0].Data[x] = windSpeeds[x].X; + patches[1].Data[x] = windSpeeds[x].Y; } - LayerDataPacket layerpack = TerrainCompressor.CreateLayerDataPacket(patches, TerrainPatch.LayerType.Wind); + byte layerType = (byte)TerrainPatch.LayerType.Wind; + if (m_scene.RegionInfo.RegionSizeX > Constants.RegionSize || m_scene.RegionInfo.RegionSizeY > Constants.RegionSize) + layerType = (byte)TerrainPatch.LayerType.WindExtended; + + // LayerDataPacket layerpack = TerrainCompressor.CreateLayerDataPacket(patches, (TerrainPatch.LayerType)layerType); + LayerDataPacket layerpack = OpenSimTerrainCompressor.CreateLayerDataPacket(patches, layerType, + (int)m_scene.RegionInfo.RegionSizeX, (int)m_scene.RegionInfo.RegionSizeY); layerpack.Header.Zerocoded = true; OutPacket(layerpack, ThrottleOutPacketType.Wind); } @@ -1314,7 +1462,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } - LayerDataPacket layerpack = TerrainCompressor.CreateLayerDataPacket(patches, TerrainPatch.LayerType.Cloud); + byte layerType = (byte)TerrainPatch.LayerType.Cloud; + if (m_scene.RegionInfo.RegionSizeX > Constants.RegionSize || m_scene.RegionInfo.RegionSizeY > Constants.RegionSize) + layerType = (byte)TerrainPatch.LayerType.CloudExtended; + + // LayerDataPacket layerpack = TerrainCompressor.CreateLayerDataPacket(patches, (TerrainPatch.LayerType)layerType); + LayerDataPacket layerpack = OpenSimTerrainCompressor.CreateLayerDataPacket(patches, layerType, + (int)m_scene.RegionInfo.RegionSizeX, (int)m_scene.RegionInfo.RegionSizeY); layerpack.Header.Zerocoded = true; OutPacket(layerpack, ThrottleOutPacketType.Cloud); } @@ -1403,6 +1557,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP mapReply.AgentData.AgentID = AgentId; mapReply.Data = new MapBlockReplyPacket.DataBlock[mapBlocks2.Length]; + mapReply.Size = new MapBlockReplyPacket.SizeBlock[mapBlocks2.Length]; mapReply.AgentData.Flags = flag; for (int i = 0; i < mapBlocks2.Length; i++) @@ -1417,6 +1572,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP mapReply.Data[i].RegionFlags = mapBlocks2[i].RegionFlags; mapReply.Data[i].Access = mapBlocks2[i].Access; mapReply.Data[i].Agents = mapBlocks2[i].Agents; + + mapReply.Size[i] = new MapBlockReplyPacket.SizeBlock(); + mapReply.Size[i].SizeX = mapBlocks2[i].SizeX; + mapReply.Size[i].SizeY = mapBlocks2[i].SizeY; } OutPacket(mapReply, ThrottleOutPacketType.Land); } @@ -1521,7 +1680,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP OutPacket(tpProgress, ThrottleOutPacketType.Unknown); } - public void SendMoneyBalance(UUID transaction, bool success, byte[] description, int balance) + public void SendMoneyBalance(UUID transaction, bool success, byte[] description, int balance, int transactionType, UUID sourceID, bool sourceIsGroup, UUID destID, bool destIsGroup, int amount, string item) { MoneyBalanceReplyPacket money = (MoneyBalanceReplyPacket)PacketPool.Instance.GetPacket(PacketType.MoneyBalanceReply); money.MoneyData.AgentID = AgentId; @@ -1529,7 +1688,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP money.MoneyData.TransactionSuccess = success; money.MoneyData.Description = description; money.MoneyData.MoneyBalance = balance; - money.TransactionInfo.ItemDescription = Util.StringToBytes256("NONE"); + money.TransactionInfo.TransactionType = transactionType; + money.TransactionInfo.SourceID = sourceID; + money.TransactionInfo.IsSourceGroup = sourceIsGroup; + money.TransactionInfo.DestID = destID; + money.TransactionInfo.IsDestGroup = destIsGroup; + money.TransactionInfo.Amount = amount; + money.TransactionInfo.ItemDescription = Util.StringToBytes256(item); + OutPacket(money, ThrottleOutPacketType.Task); } @@ -1571,7 +1737,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP OutPacket(pc, ThrottleOutPacketType.Unknown); } - public void SendKillObject(ulong regionHandle, List localIDs) + public void SendKillObject(List localIDs) { // m_log.DebugFormat("[CLIENT]: Sending KillObjectPacket to {0} for {1} in {2}", Name, localID, regionHandle); @@ -1588,7 +1754,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (localIDs.Count == 1 && m_scene.GetScenePresence(localIDs[0]) != null) { - OutPacket(kill, ThrottleOutPacketType.State); + OutPacket(kill, ThrottleOutPacketType.Task); } else { @@ -1700,6 +1866,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP newBlock.Name = Util.StringToBytes256(folder.Name); newBlock.ParentID = folder.ParentID; newBlock.Type = (sbyte)folder.Type; + //if (newBlock.Type == InventoryItemBase.SUITCASE_FOLDER_TYPE) + // newBlock.Type = InventoryItemBase.SUITCASE_FOLDER_FAKE_TYPE; return newBlock; } @@ -1807,7 +1975,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void SendInventoryItemDetails(UUID ownerID, InventoryItemBase item) { - const uint FULL_MASK_PERMISSIONS = (uint)PermissionMask.All; + // Fudge this value. It's only needed to make the CRC anyway + const uint FULL_MASK_PERMISSIONS = (uint)0x7fffffff; FetchInventoryReplyPacket inventoryReply = (FetchInventoryReplyPacket)PacketPool.Instance.GetPacket(PacketType.FetchInventoryReply); // TODO: don't create new blocks if recycling an old packet @@ -1948,8 +2117,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP folderBlock.FolderID = folder.ID; folderBlock.ParentID = folder.ParentID; - //folderBlock.Type = -1; folderBlock.Type = (sbyte)folder.Type; + // Leaving this here for now, just in case we need to do this for a while + //if (folderBlock.Type == InventoryItemBase.SUITCASE_FOLDER_TYPE) + // folderBlock.Type = InventoryItemBase.SUITCASE_FOLDER_FAKE_TYPE; folderBlock.Name = Util.StringToBytes256(folder.Name); return folderBlock; @@ -2012,7 +2183,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP protected void SendBulkUpdateInventoryItem(InventoryItemBase item) { - const uint FULL_MASK_PERMISSIONS = (uint)PermissionMask.All; + const uint FULL_MASK_PERMISSIONS = (uint)0x7ffffff; BulkUpdateInventoryPacket bulkUpdate = (BulkUpdateInventoryPacket)PacketPool.Instance.GetPacket(PacketType.BulkUpdateInventory); @@ -2066,7 +2237,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// IClientAPI.SendInventoryItemCreateUpdate(InventoryItemBase) public void SendInventoryItemCreateUpdate(InventoryItemBase Item, uint callbackId) { - const uint FULL_MASK_PERMISSIONS = (uint)PermissionMask.All; + const uint FULL_MASK_PERMISSIONS = (uint)0x7fffffff; UpdateCreateInventoryItemPacket InventoryReply = (UpdateCreateInventoryItemPacket)PacketPool.Instance.GetPacket( @@ -2212,9 +2383,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void SendAgentDataUpdate(UUID agentid, UUID activegroupid, string firstname, string lastname, ulong grouppowers, string groupname, string grouptitle) { - m_activeGroupID = activegroupid; - m_activeGroupName = groupname; - m_activeGroupPowers = grouppowers; + if (agentid == AgentId) + { + ActiveGroupId = activegroupid; + ActiveGroupName = groupname; + ActiveGroupPowers = grouppowers; + } AgentDataUpdatePacket sendAgentDataUpdate = (AgentDataUpdatePacket)PacketPool.Instance.GetPacket(PacketType.AgentDataUpdate); sendAgentDataUpdate.AgentData.ActiveGroupID = activegroupid; @@ -2261,6 +2435,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// public AgentAlertMessagePacket BuildAgentAlertPacket(string message, bool modal) { + // Prepend a slash to make the message come up in the top right + // again. + // Allow special formats to be sent from aware modules. + if (!modal && !message.StartsWith("ALERT: ") && !message.StartsWith("NOTIFY: ") && message != "Home position set." && message != "You died and have been teleported to your home location") + message = "/" + message; AgentAlertMessagePacket alertPack = (AgentAlertMessagePacket)PacketPool.Instance.GetPacket(PacketType.AgentAlertMessage); alertPack.AgentData.AgentID = AgentId; alertPack.AlertData.Message = Util.StringToBytes256(message); @@ -2556,11 +2735,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP { AvatarSitResponsePacket avatarSitResponse = new AvatarSitResponsePacket(); avatarSitResponse.SitObject.ID = TargetID; - if (CameraAtOffset != Vector3.Zero) - { - avatarSitResponse.SitTransform.CameraAtOffset = CameraAtOffset; - avatarSitResponse.SitTransform.CameraEyeOffset = CameraEyeOffset; - } + avatarSitResponse.SitTransform.CameraAtOffset = CameraAtOffset; + avatarSitResponse.SitTransform.CameraEyeOffset = CameraEyeOffset; avatarSitResponse.SitTransform.ForceMouselook = ForceMouseLook; avatarSitResponse.SitTransform.AutoPilot = autopilot; avatarSitResponse.SitTransform.SitPosition = OffsetPos; @@ -2627,6 +2803,34 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } + public void SendPartPhysicsProprieties(ISceneEntity entity) + { + SceneObjectPart part = (SceneObjectPart)entity; + if (part != null && AgentId != UUID.Zero) + { + try + { + IEventQueue eq = Scene.RequestModuleInterface(); + if (eq != null) + { + uint localid = part.LocalId; + byte physshapetype = part.PhysicsShapeType; + float density = part.Density; + float friction = part.Friction; + float bounce = part.Restitution; + float gravmod = part.GravityModifier; + eq.partPhysicsProperties(localid, physshapetype, density, friction, bounce, gravmod,AgentId); + } + } + catch (Exception ex) + { + m_log.Error("Unable to send part Physics Proprieties - exception: " + ex.ToString()); + } + part.UpdatePhysRequired = false; + } + } + + public void SendGroupNameReply(UUID groupLLUID, string GroupName) { @@ -2681,8 +2885,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP { if (req.AssetInf.Data == null) { - m_log.ErrorFormat("Cannot send asset {0} ({1}), asset data is null", - req.AssetInf.ID, req.AssetInf.Metadata.ContentType); + m_log.ErrorFormat("{0} Cannot send asset {1} ({2}), asset data is null", + LogHeader, req.AssetInf.ID, req.AssetInf.Metadata.ContentType); return; } @@ -3530,7 +3734,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP AvatarAppearancePacket avp = (AvatarAppearancePacket)PacketPool.Instance.GetPacket(PacketType.AvatarAppearance); // TODO: don't create new blocks if recycling an old packet - avp.VisualParam = new AvatarAppearancePacket.VisualParamBlock[218]; + avp.VisualParam = new AvatarAppearancePacket.VisualParamBlock[visualParams.Length]; avp.ObjectData.TextureEntry = textureEntry; AvatarAppearancePacket.VisualParamBlock avblock = null; @@ -3543,6 +3747,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP avp.Sender.IsTrial = false; avp.Sender.ID = agentID; + avp.AppearanceData = new AvatarAppearancePacket.AppearanceDataBlock[0]; + avp.AppearanceHover = new AvatarAppearancePacket.AppearanceHoverBlock[0]; //m_log.DebugFormat("[CLIENT]: Sending appearance for {0} to {1}", agentID.ToString(), AgentId.ToString()); OutPacket(avp, ThrottleOutPacketType.Task); } @@ -3660,11 +3866,25 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// public void SendEntityUpdate(ISceneEntity entity, PrimUpdateFlags updateFlags) { - //double priority = m_prioritizer.GetUpdatePriority(this, entity); - uint priority = m_prioritizer.GetUpdatePriority(this, entity); + if (entity.UUID == m_agentId && !updateFlags.HasFlag(PrimUpdateFlags.FullUpdate)) + { + ImprovedTerseObjectUpdatePacket packet + = (ImprovedTerseObjectUpdatePacket)PacketPool.Instance.GetPacket(PacketType.ImprovedTerseObjectUpdate); - lock (m_entityUpdates.SyncRoot) - m_entityUpdates.Enqueue(priority, new EntityUpdate(entity, updateFlags, m_scene.TimeDilation)); + packet.RegionData.RegionHandle = m_scene.RegionInfo.RegionHandle; + packet.RegionData.TimeDilation = Utils.FloatToUInt16(1, 0.0f, 1.0f); + packet.ObjectData = new ImprovedTerseObjectUpdatePacket.ObjectDataBlock[1]; + packet.ObjectData[0] = CreateImprovedTerseBlock(entity, false); + OutPacket(packet, ThrottleOutPacketType.Unknown, true); + } + else + { + //double priority = m_prioritizer.GetUpdatePriority(this, entity); + uint priority = m_prioritizer.GetUpdatePriority(this, entity); + + lock (m_entityUpdates.SyncRoot) + m_entityUpdates.Enqueue(priority, new EntityUpdate(entity, updateFlags, m_scene.TimeDilation)); + } } /// @@ -3699,12 +3919,27 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_udpClient.NeedAcks.Remove(oPacket.SequenceNumber); // Count this as a resent packet since we are going to requeue all of the updates contained in it - Interlocked.Increment(ref m_udpClient.PacketsResent); + Interlocked.Increment(ref m_udpClient.PacketsResent); + + // We're not going to worry about interlock yet since its not currently critical that this total count + // is 100% correct + m_udpServer.PacketsResentCount++; foreach (EntityUpdate update in updates) ResendPrimUpdate(update); } +// OpenSim.Framework.Lazy> objectUpdateBlocks = new OpenSim.Framework.Lazy>(); +// OpenSim.Framework.Lazy> compressedUpdateBlocks = new OpenSim.Framework.Lazy>(); +// OpenSim.Framework.Lazy> terseUpdateBlocks = new OpenSim.Framework.Lazy>(); +// OpenSim.Framework.Lazy> terseAgentUpdateBlocks = new OpenSim.Framework.Lazy>(); +// +// OpenSim.Framework.Lazy> objectUpdates = new OpenSim.Framework.Lazy>(); +// OpenSim.Framework.Lazy> compressedUpdates = new OpenSim.Framework.Lazy>(); +// OpenSim.Framework.Lazy> terseUpdates = new OpenSim.Framework.Lazy>(); +// OpenSim.Framework.Lazy> terseAgentUpdates = new OpenSim.Framework.Lazy>(); + + private void ProcessEntityUpdates(int maxUpdates) { OpenSim.Framework.Lazy> objectUpdateBlocks = new OpenSim.Framework.Lazy>(); @@ -3717,6 +3952,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP OpenSim.Framework.Lazy> terseUpdates = new OpenSim.Framework.Lazy>(); OpenSim.Framework.Lazy> terseAgentUpdates = new OpenSim.Framework.Lazy>(); +// objectUpdateBlocks.Value.Clear(); +// compressedUpdateBlocks.Value.Clear(); +// terseUpdateBlocks.Value.Clear(); +// terseAgentUpdateBlocks.Value.Clear(); +// objectUpdates.Value.Clear(); +// compressedUpdates.Value.Clear(); +// terseUpdates.Value.Clear(); +// terseAgentUpdates.Value.Clear(); + // Check to see if this is a flush if (maxUpdates <= 0) { @@ -3774,6 +4018,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP part.Shape.LightEntry = false; } } + + if (part.Shape != null && (part.Shape.SculptType == (byte)SculptType.Mesh)) + { + // Ensure that mesh has at least 8 valid faces + part.Shape.ProfileBegin = 12500; + part.Shape.ProfileEnd = 0; + part.Shape.ProfileHollow = 27500; + } } #region UpdateFlags to packet type conversion @@ -3995,6 +4247,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } +// m_log.DebugFormat( +// "[LLCLIENTVIEW]: Sent {0} updates in ProcessEntityUpdates() for {1} {2} in {3}", +// updatesThisCall, Name, SceneAgent.IsChildAgent ? "child" : "root", Scene.Name); +// #endregion Packet Sending } @@ -4034,8 +4290,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP void HandleQueueEmpty(ThrottleOutPacketTypeFlags categories) { +// if (!m_udpServer.IsRunningOutbound) +// return; + if ((categories & ThrottleOutPacketTypeFlags.Task) != 0) { +// if (!m_udpServer.IsRunningOutbound) +// return; + if (m_maxUpdates == 0 || m_LastQueueFill == 0) { m_maxUpdates = m_udpServer.PrimUpdatesPerCallback; @@ -4061,6 +4323,27 @@ namespace OpenSim.Region.ClientStack.LindenUDP ImageManager.ProcessImageQueue(m_udpServer.TextureSendLimit); } + internal bool HandleHasUpdates(ThrottleOutPacketTypeFlags categories) + { + bool hasUpdates = false; + + if ((categories & ThrottleOutPacketTypeFlags.Task) != 0) + { + if (m_entityUpdates.Count > 0) + hasUpdates = true; + else if (m_entityProps.Count > 0) + hasUpdates = true; + } + + if ((categories & ThrottleOutPacketTypeFlags.Texture) != 0) + { + if (ImageManager.HasUpdates()) + hasUpdates = true; + } + + return hasUpdates; + } + public void SendAssetUploadCompleteMessage(sbyte AssetType, bool Success, UUID AssetFullID) { AssetUploadCompletePacket newPack = new AssetUploadCompletePacket(); @@ -4156,7 +4439,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP pack.Stat = stats.StatsBlock; pack.Header.Reliable = false; - + pack.RegionInfo = new SimStatsPacket.RegionInfoBlock[0]; OutPacket(pack, ThrottleOutPacketType.Task); } @@ -4206,6 +4489,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Count this as a resent packet since we are going to requeue all of the updates contained in it Interlocked.Increment(ref m_udpClient.PacketsResent); + // We're not going to worry about interlock yet since its not currently critical that this total count + // is 100% correct + m_udpServer.PacketsResentCount++; + foreach (ObjectPropertyUpdate update in updates) ResendPropertyUpdate(update); } @@ -4389,6 +4676,32 @@ namespace OpenSim.Region.ClientStack.LindenUDP SceneObjectPart root = sop.ParentGroup.RootPart; block.TouchName = Util.StringToBytes256(root.TouchName); + + // SL 3.3.4, at least, appears to read this information as a concatenated byte[] stream of UUIDs but + // it's not yet clear whether this is actually used. If this is done in the future then a pre-cached + // copy is really needed since it's less efficient to be constantly recreating this byte array. +// using (MemoryStream memStream = new MemoryStream()) +// { +// using (BinaryWriter binWriter = new BinaryWriter(memStream)) +// { +// for (int i = 0; i < sop.GetNumberOfSides(); i++) +// { +// Primitive.TextureEntryFace teFace = sop.Shape.Textures.FaceTextures[i]; +// +// UUID textureID; +// +// if (teFace != null) +// textureID = teFace.TextureID; +// else +// textureID = sop.Shape.Textures.DefaultTexture.TextureID; +// +// binWriter.Write(textureID.GetBytes()); +// } +// +// block.TextureID = memStream.ToArray(); +// } +// } + block.TextureID = new byte[0]; // TextureID ??? block.SitName = Util.StringToBytes256(root.SitName); block.OwnerMask = root.OwnerMask; @@ -4543,7 +4856,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP rinfopack.AgentData = new RegionInfoPacket.AgentDataBlock(); rinfopack.AgentData.AgentID = AgentId; rinfopack.AgentData.SessionID = SessionId; - + rinfopack.RegionInfo3 = new RegionInfoPacket.RegionInfo3Block[0]; OutPacket(rinfopack, ThrottleOutPacketType.Task); } @@ -4764,7 +5077,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void SendForceClientSelectObjects(List ObjectIDs) { - m_log.WarnFormat("[LLCLIENTVIEW] sending select with {0} objects", ObjectIDs.Count); +// m_log.DebugFormat("[LLCLIENTVIEW] sending select with {0} objects", ObjectIDs.Count); bool firstCall = true; const int MAX_OBJECTS_PER_PACKET = 251; @@ -4882,7 +5195,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP { ScenePresence presence = (ScenePresence)entity; - attachPoint = 0; +// m_log.DebugFormat( +// "[LLCLIENTVIEW]: Sending terse update to {0} with pos {1}, vel {2} in {3}", +// Name, presence.OffsetPosition, presence.Velocity, m_scene.Name); + + attachPoint = presence.State; collisionPlane = presence.CollisionPlane; position = presence.OffsetPosition; velocity = presence.Velocity; @@ -4893,9 +5210,24 @@ namespace OpenSim.Region.ClientStack.LindenUDP // may improve movement smoothness. // acceleration = new Vector3(1, 0, 0); - angularVelocity = Vector3.Zero; + angularVelocity = presence.AngularVelocity; + + // Whilst not in mouselook, an avatar will transmit only the Z rotation as this is the only axis + // it rotates around. + // In mouselook, X and Y co-ordinate will also be sent but when used in Rotation, these cause unwanted + // excessive up and down movements of the camera when looking up and down. + // See http://opensimulator.org/mantis/view.php?id=3274 + // This does not affect head movement, since this is controlled entirely by camera movement rather than + // body rotation. We still need to transmit X and Y for sitting avatars but mouselook does not change + // the rotation in this case. rotation = presence.Rotation; + if (!presence.IsSatOnObject) + { + rotation.X = 0; + rotation.Y = 0; + } + if (sendTexture) textureEntry = presence.Appearance.Texture.GetBytes(); else @@ -4906,7 +5238,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP SceneObjectPart part = (SceneObjectPart)entity; attachPoint = part.ParentGroup.AttachmentPoint; - + attachPoint = ((attachPoint % 16) * 16 + (attachPoint / 16)); // m_log.DebugFormat( // "[LLCLIENTVIEW]: Sending attachPoint {0} for {1} {2} to {3}", // attachPoint, part.Name, part.LocalId, Name); @@ -4934,7 +5266,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP pos += 4; // Avatar/CollisionPlane - data[pos++] = (byte)((attachPoint % 16) * 16 + (attachPoint / 16)); ; + data[pos++] = (byte) attachPoint; if (avatar) { data[pos++] = 1; @@ -5001,13 +5333,33 @@ namespace OpenSim.Region.ClientStack.LindenUDP protected ObjectUpdatePacket.ObjectDataBlock CreateAvatarUpdateBlock(ScenePresence data) { +// m_log.DebugFormat( +// "[LLCLIENTVIEW]: Sending full update to {0} with pos {1}, vel {2} in {3}", Name, data.OffsetPosition, data.Velocity, m_scene.Name); + byte[] objectData = new byte[76]; data.CollisionPlane.ToBytes(objectData, 0); data.OffsetPosition.ToBytes(objectData, 16); -// data.Velocity.ToBytes(objectData, 28); + data.Velocity.ToBytes(objectData, 28); // data.Acceleration.ToBytes(objectData, 40); - data.Rotation.ToBytes(objectData, 52); + + // Whilst not in mouselook, an avatar will transmit only the Z rotation as this is the only axis + // it rotates around. + // In mouselook, X and Y co-ordinate will also be sent but when used in Rotation, these cause unwanted + // excessive up and down movements of the camera when looking up and down. + // See http://opensimulator.org/mantis/view.php?id=3274 + // This does not affect head movement, since this is controlled entirely by camera movement rather than + // body rotation. We still need to transmit X and Y for sitting avatars but mouselook does not change + // the rotation in this case. + Quaternion rot = data.Rotation; + + if (!data.IsSatOnObject) + { + rot.X = 0; + rot.Y = 0; + } + + rot.ToBytes(objectData, 52); //data.AngularVelocity.ToBytes(objectData, 64); ObjectUpdatePacket.ObjectDataBlock update = new ObjectUpdatePacket.ObjectDataBlock(); @@ -5021,7 +5373,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP update.NameValue = Utils.StringToBytes("FirstName STRING RW SV " + data.Firstname + "\nLastName STRING RW SV " + data.Lastname + "\nTitle STRING RW SV " + data.Grouptitle); update.ObjectData = objectData; - update.ParentID = data.ParentID; + + SceneObjectPart parentPart = data.ParentPart; + if (parentPart != null) + update.ParentID = parentPart.ParentGroup.LocalId; + else + update.ParentID = 0; + update.PathCurve = 16; update.PathScaleX = 100; update.PathScaleY = 100; @@ -5075,10 +5433,22 @@ namespace OpenSim.Region.ClientStack.LindenUDP //update.JointType = 0; update.Material = data.Material; update.MediaURL = Utils.EmptyBytes; // FIXME: Support this in OpenSim + if (data.ParentGroup.IsAttachment) { - update.NameValue = Util.StringToBytes256("AttachItemID STRING RW SV " + data.ParentGroup.FromItemID); + update.NameValue + = Util.StringToBytes256( + string.Format("AttachItemID STRING RW SV {0}", data.ParentGroup.FromItemID)); + update.State = (byte)((data.ParentGroup.AttachmentPoint % 16) * 16 + (data.ParentGroup.AttachmentPoint / 16)); + +// m_log.DebugFormat( +// "[LLCLIENTVIEW]: Sending NameValue {0} for {1} {2} to {3}", +// Util.UTF8.GetString(update.NameValue), data.Name, data.LocalId, Name); +// +// m_log.DebugFormat( +// "[LLCLIENTVIEW]: Sending state {0} for {1} {2} to {3}", +// update.State, data.Name, data.LocalId, Name); } else { @@ -5089,10 +5459,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP update.State = data.ParentGroup.RootPart.Shape.State; } -// m_log.DebugFormat( -// "[LLCLIENTVIEW]: Sending state {0} for {1} {2} to {3}", -// update.State, data.Name, data.LocalId, Name); - update.ObjectData = objectData; update.ParentID = data.ParentID; update.PathBegin = data.Shape.PathBegin; @@ -5192,8 +5558,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP public ulong GetGroupPowers(UUID groupID) { - if (groupID == m_activeGroupID) - return m_activeGroupPowers; + if (groupID == ActiveGroupId) + return ActiveGroupPowers; if (m_groupPowers.ContainsKey(groupID)) return m_groupPowers[groupID]; @@ -5221,10 +5587,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP AddLocalPacketHandler(PacketType.ParcelBuy, HandleParcelBuyRequest, false); AddLocalPacketHandler(PacketType.UUIDGroupNameRequest, HandleUUIDGroupNameRequest); AddLocalPacketHandler(PacketType.ObjectGroup, HandleObjectGroupRequest); - AddLocalPacketHandler(PacketType.GenericMessage, HandleGenericMessage); - AddLocalPacketHandler(PacketType.AvatarPropertiesRequest, HandleAvatarPropertiesRequest); + AddLocalPacketHandler(PacketType.GenericMessage, HandleGenericMessage, true, true); + AddLocalPacketHandler(PacketType.AvatarPropertiesRequest, HandleAvatarPropertiesRequest, true, true); AddLocalPacketHandler(PacketType.ChatFromViewer, HandleChatFromViewer); - AddLocalPacketHandler(PacketType.AvatarPropertiesUpdate, HandlerAvatarPropertiesUpdate); + AddLocalPacketHandler(PacketType.AvatarPropertiesUpdate, HandlerAvatarPropertiesUpdate, true, true); AddLocalPacketHandler(PacketType.ScriptDialogReply, HandlerScriptDialogReply); AddLocalPacketHandler(PacketType.ImprovedInstantMessage, HandlerImprovedInstantMessage); AddLocalPacketHandler(PacketType.AcceptFriendship, HandlerAcceptFriendship); @@ -5233,7 +5599,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP AddLocalPacketHandler(PacketType.RezObject, HandlerRezObject); AddLocalPacketHandler(PacketType.DeRezObject, HandlerDeRezObject); AddLocalPacketHandler(PacketType.ModifyLand, HandlerModifyLand); - AddLocalPacketHandler(PacketType.RegionHandshakeReply, HandlerRegionHandshakeReply); + AddLocalPacketHandler(PacketType.RegionHandshakeReply, HandlerRegionHandshakeReply, false); AddLocalPacketHandler(PacketType.AgentWearablesRequest, HandlerAgentWearablesRequest); AddLocalPacketHandler(PacketType.AgentSetAppearance, HandlerAgentSetAppearance); AddLocalPacketHandler(PacketType.AgentIsNowWearing, HandlerAgentIsNowWearing); @@ -5294,8 +5660,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP AddLocalPacketHandler(PacketType.ScriptAnswerYes, HandleScriptAnswerYes, false); AddLocalPacketHandler(PacketType.ObjectClickAction, HandleObjectClickAction, false); AddLocalPacketHandler(PacketType.ObjectMaterial, HandleObjectMaterial, false); - AddLocalPacketHandler(PacketType.RequestImage, HandleRequestImage); - AddLocalPacketHandler(PacketType.TransferRequest, HandleTransferRequest); + AddLocalPacketHandler(PacketType.RequestImage, HandleRequestImage, false); + AddLocalPacketHandler(PacketType.TransferRequest, HandleTransferRequest, false); AddLocalPacketHandler(PacketType.AssetUploadRequest, HandleAssetUploadRequest); AddLocalPacketHandler(PacketType.RequestXfer, HandleRequestXfer); AddLocalPacketHandler(PacketType.SendXferPacket, HandleSendXferPacket); @@ -5327,7 +5693,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP AddLocalPacketHandler(PacketType.TeleportCancel, HandleTeleportCancel); AddLocalPacketHandler(PacketType.TeleportLocationRequest, HandleTeleportLocationRequest); AddLocalPacketHandler(PacketType.UUIDNameRequest, HandleUUIDNameRequest, false); - AddLocalPacketHandler(PacketType.RegionHandleRequest, HandleRegionHandleRequest); + AddLocalPacketHandler(PacketType.RegionHandleRequest, HandleRegionHandleRequest, false); AddLocalPacketHandler(PacketType.ParcelInfoRequest, HandleParcelInfoRequest); AddLocalPacketHandler(PacketType.ParcelAccessListRequest, HandleParcelAccessListRequest, false); AddLocalPacketHandler(PacketType.ParcelAccessListUpdate, HandleParcelAccessListUpdate, false); @@ -5409,8 +5775,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP AddLocalPacketHandler(PacketType.PickDelete, HandlePickDelete); AddLocalPacketHandler(PacketType.PickGodDelete, HandlePickGodDelete); AddLocalPacketHandler(PacketType.PickInfoUpdate, HandlePickInfoUpdate); - AddLocalPacketHandler(PacketType.AvatarNotesUpdate, HandleAvatarNotesUpdate); - AddLocalPacketHandler(PacketType.AvatarInterestsUpdate, HandleAvatarInterestsUpdate); + AddLocalPacketHandler(PacketType.AvatarNotesUpdate, HandleAvatarNotesUpdate, true, true); + AddLocalPacketHandler(PacketType.AvatarInterestsUpdate, HandleAvatarInterestsUpdate, true, true); AddLocalPacketHandler(PacketType.GrantUserRights, HandleGrantUserRights); AddLocalPacketHandler(PacketType.PlacesQuery, HandlePlacesQuery); AddLocalPacketHandler(PacketType.UpdateMuteListEntry, HandleUpdateMuteListEntry); @@ -5438,82 +5804,137 @@ namespace OpenSim.Region.ClientStack.LindenUDP #region Packet Handlers + public int TotalAgentUpdates { get; set; } + #region Scene/Avatar - private bool HandleAgentUpdate(IClientAPI sener, Packet packet) + // Threshold for body rotation to be a significant agent update + private const float QDELTA = 0.000001f; + // Threshold for camera rotation to be a significant agent update + private const float VDELTA = 0.01f; + + /// + /// This checks the update significance against the last update made. + /// + /// Can only be called by one thread at a time + /// + /// + public bool CheckAgentUpdateSignificance(AgentUpdatePacket.AgentDataBlock x) { - if (OnAgentUpdate != null) - { - AgentUpdatePacket agentUpdate = (AgentUpdatePacket)packet; + return CheckAgentMovementUpdateSignificance(x) || CheckAgentCameraUpdateSignificance(x); + } - #region Packet Session and User Check - if (agentUpdate.AgentData.SessionID != SessionId || agentUpdate.AgentData.AgentID != AgentId) - { - PacketPool.Instance.ReturnPacket(packet); - return false; - } - #endregion + /// + /// This checks the movement/state update significance against the last update made. + /// + /// Can only be called by one thread at a time + /// + /// + private bool CheckAgentMovementUpdateSignificance(AgentUpdatePacket.AgentDataBlock x) + { + float qdelta1 = 1 - (float)Math.Pow(Quaternion.Dot(x.BodyRotation, m_thisAgentUpdateArgs.BodyRotation), 2); + //qdelta2 = 1 - (float)Math.Pow(Quaternion.Dot(x.HeadRotation, m_thisAgentUpdateArgs.HeadRotation), 2); + + bool movementSignificant = + (qdelta1 > QDELTA) // significant if body rotation above threshold + // Ignoring head rotation altogether, because it's not being used for anything interesting up the stack + // || (qdelta2 > QDELTA * 10) // significant if head rotation above threshold + || (x.ControlFlags != m_thisAgentUpdateArgs.ControlFlags) // significant if control flags changed + || (x.ControlFlags != (byte)AgentManager.ControlFlags.NONE) // significant if user supplying any movement update commands + || (x.Far != m_thisAgentUpdateArgs.Far) // significant if far distance changed + || (x.Flags != m_thisAgentUpdateArgs.Flags) // significant if Flags changed + || (x.State != m_thisAgentUpdateArgs.State) // significant if Stats changed + ; + //if (movementSignificant) + //{ + //m_log.DebugFormat("[LLCLIENTVIEW]: Bod {0} {1}", + // qdelta1, qdelta2); + //m_log.DebugFormat("[LLCLIENTVIEW]: St {0} {1} {2} {3}", + // x.ControlFlags, x.Flags, x.Far, x.State); + //} + return movementSignificant; + } - bool update = false; - AgentUpdatePacket.AgentDataBlock x = agentUpdate.AgentData; - - if (m_lastAgentUpdateArgs != null) - { - // These should be ordered from most-likely to - // least likely to change. I've made an initial - // guess at that. - update = - ( - (x.BodyRotation != m_lastAgentUpdateArgs.BodyRotation) || - (x.CameraAtAxis != m_lastAgentUpdateArgs.CameraAtAxis) || - (x.CameraCenter != m_lastAgentUpdateArgs.CameraCenter) || - (x.CameraLeftAxis != m_lastAgentUpdateArgs.CameraLeftAxis) || - (x.CameraUpAxis != m_lastAgentUpdateArgs.CameraUpAxis) || - (x.ControlFlags != m_lastAgentUpdateArgs.ControlFlags) || - (x.Far != m_lastAgentUpdateArgs.Far) || - (x.Flags != m_lastAgentUpdateArgs.Flags) || - (x.State != m_lastAgentUpdateArgs.State) || - (x.HeadRotation != m_lastAgentUpdateArgs.HeadRotation) || - (x.SessionID != m_lastAgentUpdateArgs.SessionID) || - (x.AgentID != m_lastAgentUpdateArgs.AgentID) - ); - } - else - { - m_lastAgentUpdateArgs = new AgentUpdateArgs(); - update = true; - } + /// + /// This checks the camera update significance against the last update made. + /// + /// Can only be called by one thread at a time + /// + /// + private bool CheckAgentCameraUpdateSignificance(AgentUpdatePacket.AgentDataBlock x) + { + float vdelta1 = Vector3.Distance(x.CameraAtAxis, m_thisAgentUpdateArgs.CameraAtAxis); + float vdelta2 = Vector3.Distance(x.CameraCenter, m_thisAgentUpdateArgs.CameraCenter); + float vdelta3 = Vector3.Distance(x.CameraLeftAxis, m_thisAgentUpdateArgs.CameraLeftAxis); + float vdelta4 = Vector3.Distance(x.CameraUpAxis, m_thisAgentUpdateArgs.CameraUpAxis); - if (update) - { -// m_log.DebugFormat("[LLCLIENTVIEW]: Triggered AgentUpdate for {0}", sener.Name); + bool cameraSignificant = + (vdelta1 > VDELTA) || + (vdelta2 > VDELTA) || + (vdelta3 > VDELTA) || + (vdelta4 > VDELTA) + ; - m_lastAgentUpdateArgs.AgentID = x.AgentID; - m_lastAgentUpdateArgs.BodyRotation = x.BodyRotation; - m_lastAgentUpdateArgs.CameraAtAxis = x.CameraAtAxis; - m_lastAgentUpdateArgs.CameraCenter = x.CameraCenter; - m_lastAgentUpdateArgs.CameraLeftAxis = x.CameraLeftAxis; - m_lastAgentUpdateArgs.CameraUpAxis = x.CameraUpAxis; - m_lastAgentUpdateArgs.ControlFlags = x.ControlFlags; - m_lastAgentUpdateArgs.Far = x.Far; - m_lastAgentUpdateArgs.Flags = x.Flags; - m_lastAgentUpdateArgs.HeadRotation = x.HeadRotation; - m_lastAgentUpdateArgs.SessionID = x.SessionID; - m_lastAgentUpdateArgs.State = x.State; + //if (cameraSignificant) + //{ + //m_log.DebugFormat("[LLCLIENTVIEW]: Cam1 {0} {1}", + // x.CameraAtAxis, x.CameraCenter); + //m_log.DebugFormat("[LLCLIENTVIEW]: Cam2 {0} {1}", + // x.CameraLeftAxis, x.CameraUpAxis); + //} - UpdateAgent handlerAgentUpdate = OnAgentUpdate; - UpdateAgent handlerPreAgentUpdate = OnPreAgentUpdate; + return cameraSignificant; + } - if (handlerPreAgentUpdate != null) - OnPreAgentUpdate(this, m_lastAgentUpdateArgs); + private bool HandleAgentUpdate(IClientAPI sener, Packet packet) + { + // We got here, which means that something in agent update was significant - if (handlerAgentUpdate != null) - OnAgentUpdate(this, m_lastAgentUpdateArgs); + AgentUpdatePacket agentUpdate = (AgentUpdatePacket)packet; + AgentUpdatePacket.AgentDataBlock x = agentUpdate.AgentData; - handlerAgentUpdate = null; - handlerPreAgentUpdate = null; - } - } + if (x.AgentID != AgentId || x.SessionID != SessionId) + return false; + + // Before we update the current m_thisAgentUpdateArgs, let's check this again + // to see what exactly changed + bool movement = CheckAgentMovementUpdateSignificance(x); + bool camera = CheckAgentCameraUpdateSignificance(x); + + m_thisAgentUpdateArgs.AgentID = x.AgentID; + m_thisAgentUpdateArgs.BodyRotation = x.BodyRotation; + m_thisAgentUpdateArgs.CameraAtAxis = x.CameraAtAxis; + m_thisAgentUpdateArgs.CameraCenter = x.CameraCenter; + m_thisAgentUpdateArgs.CameraLeftAxis = x.CameraLeftAxis; + m_thisAgentUpdateArgs.CameraUpAxis = x.CameraUpAxis; + m_thisAgentUpdateArgs.ControlFlags = x.ControlFlags; + m_thisAgentUpdateArgs.Far = x.Far; + m_thisAgentUpdateArgs.Flags = x.Flags; + m_thisAgentUpdateArgs.HeadRotation = x.HeadRotation; + m_thisAgentUpdateArgs.SessionID = x.SessionID; + m_thisAgentUpdateArgs.State = x.State; + + UpdateAgent handlerAgentUpdate = OnAgentUpdate; + UpdateAgent handlerPreAgentUpdate = OnPreAgentUpdate; + UpdateAgent handlerAgentCameraUpdate = OnAgentCameraUpdate; + + // Was there a significant movement/state change? + if (movement) + { + if (handlerPreAgentUpdate != null) + OnPreAgentUpdate(this, m_thisAgentUpdateArgs); + + if (handlerAgentUpdate != null) + OnAgentUpdate(this, m_thisAgentUpdateArgs); + } + // Was there a significant camera(s) change? + if (camera) + if (handlerAgentCameraUpdate != null) + handlerAgentCameraUpdate(this, m_thisAgentUpdateArgs); + + handlerAgentUpdate = null; + handlerPreAgentUpdate = null; + handlerAgentCameraUpdate = null; PacketPool.Instance.ReturnPacket(packet); @@ -6080,6 +6501,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP //m_log.Info("[LAND]: LAND:" + modify.ToString()); if (modify.ParcelData.Length > 0) { + // Note: the ModifyTerrain event handler sends out updated packets before the end of this event. Therefore, + // a simple boolean value should work and perhaps queue up just a few terrain patch packets at the end of the edit. + m_justEditedTerrain = true; // Prevent terrain packet (Land layer) from being queued, make it unreliable if (OnModifyTerrain != null) { for (int i = 0; i < modify.ParcelData.Length; i++) @@ -6095,6 +6519,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } } + m_justEditedTerrain = false; // Queue terrain packet (Land layer) if necessary, make it reliable again } return true; @@ -6149,17 +6574,25 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Temporarily protect ourselves from the mantis #951 failure. // However, we could do this for several other handlers where a failure isn't terminal // for the client session anyway, in order to protect ourselves against bad code in plugins + Vector3 avSize = appear.AgentData.Size; try { byte[] visualparams = new byte[appear.VisualParam.Length]; for (int i = 0; i < appear.VisualParam.Length; i++) visualparams[i] = appear.VisualParam[i].ParamValue; + //var b = appear.WearableData[0]; Primitive.TextureEntry te = null; if (appear.ObjectData.TextureEntry.Length > 1) te = new Primitive.TextureEntry(appear.ObjectData.TextureEntry, 0, appear.ObjectData.TextureEntry.Length); - handlerSetAppearance(sender, te, visualparams); + WearableCacheItem[] cacheitems = new WearableCacheItem[appear.WearableData.Length]; + for (int i=0; i + /// Used when a child agent gets a sit response which should not be fulfilled. + /// + private void SendCantSitBecauseChildAgentResponse() + { + SendAlertMessage("Try moving closer. Can't sit on object because it is not in the same region as you."); + } + private bool HandleSoundTrigger(IClientAPI sender, Packet Pack) { SoundTriggerPacket soundTriggerPacket = (SoundTriggerPacket)Pack; @@ -6856,7 +7309,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP { handlerObjectDuplicate(dupe.ObjectData[i].ObjectLocalID, dupe.SharedData.Offset, dupe.SharedData.DuplicateFlags, AgentId, - m_activeGroupID); + ActiveGroupId); } } @@ -7031,14 +7484,37 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (handlerUpdatePrimFlags != null) { - byte[] data = Pack.ToBytes(); +// byte[] data = Pack.ToBytes(); // 46,47,48 are special positions within the packet // This may change so perhaps we need a better way // of storing this (OMV.FlagUpdatePacket.UsePhysics,etc?) - bool UsePhysics = (data[46] != 0) ? true : false; - bool IsTemporary = (data[47] != 0) ? true : false; - bool IsPhantom = (data[48] != 0) ? true : false; - handlerUpdatePrimFlags(flags.AgentData.ObjectLocalID, UsePhysics, IsTemporary, IsPhantom, this); + /* + bool UsePhysics = (data[46] != 0) ? true : false; + bool IsTemporary = (data[47] != 0) ? true : false; + bool IsPhantom = (data[48] != 0) ? true : false; + handlerUpdatePrimFlags(flags.AgentData.ObjectLocalID, UsePhysics, IsTemporary, IsPhantom, this); + */ + bool UsePhysics = flags.AgentData.UsePhysics; + bool IsPhantom = flags.AgentData.IsPhantom; + bool IsTemporary = flags.AgentData.IsTemporary; + ObjectFlagUpdatePacket.ExtraPhysicsBlock[] blocks = flags.ExtraPhysics; + ExtraPhysicsData physdata = new ExtraPhysicsData(); + + if (blocks == null || blocks.Length == 0) + { + physdata.PhysShapeType = PhysShapeType.invalid; + } + else + { + ObjectFlagUpdatePacket.ExtraPhysicsBlock phsblock = blocks[0]; + physdata.PhysShapeType = (PhysShapeType)phsblock.PhysicsShapeType; + physdata.Bounce = phsblock.Restitution; + physdata.Density = phsblock.Density; + physdata.Friction = phsblock.Friction; + physdata.GravitationModifier = phsblock.GravityMultiplier; + } + + handlerUpdatePrimFlags(flags.AgentData.ObjectLocalID, UsePhysics, IsTemporary, IsPhantom, physdata, this); } return true; } @@ -7446,7 +7922,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (handlerObjectDuplicateOnRay != null) { handlerObjectDuplicateOnRay(dupeOnRay.ObjectData[i].ObjectLocalID, dupeOnRay.AgentData.DuplicateFlags, - AgentId, m_activeGroupID, dupeOnRay.AgentData.RayTargetID, dupeOnRay.AgentData.RayEnd, + AgentId, ActiveGroupId, dupeOnRay.AgentData.RayTargetID, dupeOnRay.AgentData.RayEnd, dupeOnRay.AgentData.RayStart, dupeOnRay.AgentData.BypassRaycast, dupeOnRay.AgentData.RayEndIsIntersection, dupeOnRay.AgentData.CopyCenters, dupeOnRay.AgentData.CopyRotates); } @@ -7640,129 +8116,146 @@ namespace OpenSim.Region.ClientStack.LindenUDP //m_log.Debug("ClientView.ProcessPackets.cs:ProcessInPacket() - Got transfer request"); TransferRequestPacket transfer = (TransferRequestPacket)Pack; - //m_log.Debug("Transfer Request: " + transfer.ToString()); - // Validate inventory transfers - // Has to be done here, because AssetCache can't do it - // UUID taskID = UUID.Zero; if (transfer.TransferInfo.SourceType == (int)SourceType.SimInventoryItem) { - taskID = new UUID(transfer.TransferInfo.Params, 48); - UUID itemID = new UUID(transfer.TransferInfo.Params, 64); - UUID requestID = new UUID(transfer.TransferInfo.Params, 80); - -// m_log.DebugFormat( -// "[CLIENT]: Got request for asset {0} from item {1} in prim {2} by {3}", -// requestID, itemID, taskID, Name); - if (!(((Scene)m_scene).Permissions.BypassPermissions())) { - if (taskID != UUID.Zero) // Prim + // We're spawning a thread because the permissions check can block this thread + Util.FireAndForget(delegate { - SceneObjectPart part = ((Scene)m_scene).GetSceneObjectPart(taskID); + // This requests the asset if needed + HandleSimInventoryTransferRequestWithPermsCheck(sender, transfer); + }, null, "LLClientView.HandleTransferRequest"); - if (part == null) - { - m_log.WarnFormat( - "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but prim does not exist", - Name, requestID, itemID, taskID); - return true; - } + return true; + } + } + else if (transfer.TransferInfo.SourceType == (int)SourceType.SimEstate) + { + //TransferRequestPacket does not include covenant uuid? + //get scene covenant uuid + taskID = m_scene.RegionInfo.RegionSettings.Covenant; + } - TaskInventoryItem tii = part.Inventory.GetInventoryItem(itemID); - if (tii == null) - { - m_log.WarnFormat( - "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but item does not exist", - Name, requestID, itemID, taskID); - return true; - } + // This is non-blocking + MakeAssetRequest(transfer, taskID); - if (tii.Type == (int)AssetType.LSLText) - { - if (!((Scene)m_scene).Permissions.CanEditScript(itemID, taskID, AgentId)) - return true; - } - else if (tii.Type == (int)AssetType.Notecard) - { - if (!((Scene)m_scene).Permissions.CanEditNotecard(itemID, taskID, AgentId)) - return true; - } - else - { - // TODO: Change this code to allow items other than notecards and scripts to be successfully - // shared with group. In fact, this whole block of permissions checking should move to an IPermissionsModule - if (part.OwnerID != AgentId) - { - m_log.WarnFormat( - "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but the prim is owned by {4}", - Name, requestID, itemID, taskID, part.OwnerID); - return true; - } + return true; + } - if ((part.OwnerMask & (uint)PermissionMask.Modify) == 0) - { - m_log.WarnFormat( - "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but modify permissions are not set", - Name, requestID, itemID, taskID); - return true; - } + private void HandleSimInventoryTransferRequestWithPermsCheck(IClientAPI sender, TransferRequestPacket transfer) + { + UUID taskID = new UUID(transfer.TransferInfo.Params, 48); + UUID itemID = new UUID(transfer.TransferInfo.Params, 64); + UUID requestID = new UUID(transfer.TransferInfo.Params, 80); - if (tii.OwnerID != AgentId) - { - m_log.WarnFormat( - "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but the item is owned by {4}", - Name, requestID, itemID, taskID, tii.OwnerID); - return true; - } + //m_log.DebugFormat( + // "[CLIENT]: Got request for asset {0} from item {1} in prim {2} by {3}", + // requestID, itemID, taskID, Name); - if (( - tii.CurrentPermissions & ((uint)PermissionMask.Modify | (uint)PermissionMask.Copy | (uint)PermissionMask.Transfer)) - != ((uint)PermissionMask.Modify | (uint)PermissionMask.Copy | (uint)PermissionMask.Transfer)) - { - m_log.WarnFormat( - "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but item permissions are not modify/copy/transfer", - Name, requestID, itemID, taskID); - return true; - } + //m_log.Debug("Transfer Request: " + transfer.ToString()); + // Validate inventory transfers + // Has to be done here, because AssetCache can't do it + // + if (taskID != UUID.Zero) // Prim + { + SceneObjectPart part = ((Scene)m_scene).GetSceneObjectPart(taskID); - if (tii.AssetID != requestID) - { - m_log.WarnFormat( - "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but this does not match item's asset {4}", - Name, requestID, itemID, taskID, tii.AssetID); - return true; - } - } + if (part == null) + { + m_log.WarnFormat( + "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but prim does not exist", + Name, requestID, itemID, taskID); + return; + } + + TaskInventoryItem tii = part.Inventory.GetInventoryItem(itemID); + if (tii == null) + { + m_log.WarnFormat( + "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but item does not exist", + Name, requestID, itemID, taskID); + return; + } + + if (tii.Type == (int)AssetType.LSLText) + { + if (!((Scene)m_scene).Permissions.CanEditScript(itemID, taskID, AgentId)) + return; + } + else if (tii.Type == (int)AssetType.Notecard) + { + if (!((Scene)m_scene).Permissions.CanEditNotecard(itemID, taskID, AgentId)) + return; + } + else + { + // TODO: Change this code to allow items other than notecards and scripts to be successfully + // shared with group. In fact, this whole block of permissions checking should move to an IPermissionsModule + if (part.OwnerID != AgentId) + { + m_log.WarnFormat( + "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but the prim is owned by {4}", + Name, requestID, itemID, taskID, part.OwnerID); + return; } - else // Agent + + if ((part.OwnerMask & (uint)PermissionMask.Modify) == 0) { - IInventoryAccessModule invAccess = m_scene.RequestModuleInterface(); - if (invAccess != null) - { - if (!invAccess.CanGetAgentInventoryItem(this, itemID, requestID)) - return false; - } - else - { - return false; - } + m_log.WarnFormat( + "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but modify permissions are not set", + Name, requestID, itemID, taskID); + return; + } + + if (tii.OwnerID != AgentId) + { + m_log.WarnFormat( + "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but the item is owned by {4}", + Name, requestID, itemID, taskID, tii.OwnerID); + return; + } + + if (( + tii.CurrentPermissions & ((uint)PermissionMask.Modify | (uint)PermissionMask.Copy | (uint)PermissionMask.Transfer)) + != ((uint)PermissionMask.Modify | (uint)PermissionMask.Copy | (uint)PermissionMask.Transfer)) + { + m_log.WarnFormat( + "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but item permissions are not modify/copy/transfer", + Name, requestID, itemID, taskID); + return; + } + + if (tii.AssetID != requestID) + { + m_log.WarnFormat( + "[CLIENT]: {0} requested asset {1} from item {2} in prim {3} but this does not match item's asset {4}", + Name, requestID, itemID, taskID, tii.AssetID); + return; } } } - else - if (transfer.TransferInfo.SourceType == (int)SourceType.SimEstate) + else // Agent + { + IInventoryAccessModule invAccess = m_scene.RequestModuleInterface(); + if (invAccess != null) { - //TransferRequestPacket does not include covenant uuid? - //get scene covenant uuid - taskID = m_scene.RegionInfo.RegionSettings.Covenant; + if (!invAccess.CanGetAgentInventoryItem(this, itemID, requestID)) + return; } + else + { + return; + } + } + // Permissions out of the way, let's request the asset MakeAssetRequest(transfer, taskID); - return true; } + private bool HandleAssetUploadRequest(IClientAPI sender, Packet Pack) { AssetUploadRequestPacket request = (AssetUploadRequestPacket)Pack; @@ -8477,8 +8970,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP return true; } #endregion - string mapName = Util.UTF8.GetString(map.NameData.Name, 0, - map.NameData.Name.Length - 1); + string mapName = (map.NameData.Name.Length == 0) ? m_scene.RegionInfo.RegionName : + Util.UTF8.GetString(map.NameData.Name, 0, map.NameData.Name.Length - 1); RequestMapName handlerMapNameRequest = OnMapNameRequest; if (handlerMapNameRequest != null) { @@ -8583,7 +9076,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (aCircuit != null && aCircuit.ServiceURLs != null && aCircuit.ServiceURLs.ContainsKey("AssetServerURI")) { string assetServer = aCircuit.ServiceURLs["AssetServerURI"].ToString(); - return ((Scene)Scene).AssetService.Get(assetServer + "/" + id); + if (!string.IsNullOrEmpty(assetServer)) + return ((Scene)Scene).AssetService.Get(assetServer + "/" + id); } return null; @@ -8606,6 +9100,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP TeleportLocationRequest handlerTeleportLocationRequest = OnTeleportLocationRequest; if (handlerTeleportLocationRequest != null) { + // Adjust teleport location to base of a larger region if requested to teleport to a sub-region + uint locX, locY; + Util.RegionHandleToWorldLoc(tpLocReq.Info.RegionHandle, out locX, out locY); + if ((locX >= m_scene.RegionInfo.WorldLocX) + && (locX < (m_scene.RegionInfo.WorldLocX + m_scene.RegionInfo.RegionSizeX)) + && (locY >= m_scene.RegionInfo.WorldLocY) + && (locY < (m_scene.RegionInfo.WorldLocY + m_scene.RegionInfo.RegionSizeY)) ) + { + tpLocReq.Info.RegionHandle = m_scene.RegionInfo.RegionHandle; + tpLocReq.Info.Position.X += locX - m_scene.RegionInfo.WorldLocX; + tpLocReq.Info.Position.Y += locY - m_scene.RegionInfo.WorldLocY; + } + handlerTeleportLocationRequest(this, tpLocReq.Info.RegionHandle, tpLocReq.Info.Position, tpLocReq.Info.LookAt, 16); } @@ -9399,6 +9906,20 @@ namespace OpenSim.Region.ClientStack.LindenUDP } return true; + case "kickestate": + + if(((Scene)m_scene).Permissions.CanIssueEstateCommand(AgentId, false)) + { + UUID invoice = messagePacket.MethodData.Invoice; + UUID SenderID = messagePacket.AgentData.AgentID; + UUID Prey; + + UUID.TryParse(Utils.BytesToString(messagePacket.ParamList[0].Parameter), out Prey); + + OnEstateTeleportOneUserHomeRequest(this, invoice, SenderID, Prey); + } + return true; + default: m_log.WarnFormat( "[LLCLIENTVIEW]: EstateOwnerMessage: Unknown method {0} requested for {1} in {2}", @@ -9604,7 +10125,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP EconomyDataRequest handlerEconomoyDataRequest = OnEconomyDataRequest; if (handlerEconomoyDataRequest != null) { - handlerEconomoyDataRequest(AgentId); + handlerEconomoyDataRequest(this); } return true; } @@ -10032,7 +10553,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP handlerDirFindQuery(this, dirFindQueryPacket.QueryData.QueryID, Utils.BytesToString( - dirFindQueryPacket.QueryData.QueryText), + dirFindQueryPacket.QueryData.QueryText).Trim(), dirFindQueryPacket.QueryData.QueryFlags, dirFindQueryPacket.QueryData.QueryStart); } @@ -11384,8 +11905,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP } /// - /// Send a response back to a client when it asks the asset server (via the region server) if it has - /// its appearance texture cached. /// /// /// At the moment, we always reply that there is no cached texture. @@ -11395,13 +11914,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// protected bool HandleAgentTextureCached(IClientAPI simclient, Packet packet) { - //m_log.Debug("texture cached: " + packet.ToString()); AgentCachedTexturePacket cachedtex = (AgentCachedTexturePacket)packet; AgentCachedTextureResponsePacket cachedresp = (AgentCachedTextureResponsePacket)PacketPool.Instance.GetPacket(PacketType.AgentCachedTextureResponse); if (cachedtex.AgentData.SessionID != SessionId) return false; + // TODO: don't create new blocks if recycling an old packet cachedresp.AgentData.AgentID = AgentId; cachedresp.AgentData.SessionID = m_sessionId; @@ -11410,19 +11929,127 @@ namespace OpenSim.Region.ClientStack.LindenUDP cachedresp.WearableData = new AgentCachedTextureResponsePacket.WearableDataBlock[cachedtex.WearableData.Length]; - for (int i = 0; i < cachedtex.WearableData.Length; i++) + int maxWearablesLoop = cachedtex.WearableData.Length; + if (maxWearablesLoop > AvatarWearable.MAX_WEARABLES) + maxWearablesLoop = AvatarWearable.MAX_WEARABLES; + + // Find the cached baked textures for this user, if they're available + + IAssetService cache = m_scene.AssetService; + IBakedTextureModule bakedTextureModule = m_scene.RequestModuleInterface(); + + WearableCacheItem[] cacheItems = null; + + if (bakedTextureModule != null && cache != null) { - cachedresp.WearableData[i] = new AgentCachedTextureResponsePacket.WearableDataBlock(); - cachedresp.WearableData[i].TextureIndex = cachedtex.WearableData[i].TextureIndex; - cachedresp.WearableData[i].TextureID = UUID.Zero; - cachedresp.WearableData[i].HostName = new byte[0]; + ScenePresence p = m_scene.GetScenePresence(AgentId); + if (p.Appearance != null) + { + if (p.Appearance.WearableCacheItems == null || p.Appearance.WearableCacheItemsDirty) + { + try + { + cacheItems = bakedTextureModule.Get(AgentId); + p.Appearance.WearableCacheItems = cacheItems; + p.Appearance.WearableCacheItemsDirty = false; + } + catch (Exception) + { + cacheItems = null; + } + + } + else if (p.Appearance.WearableCacheItems != null) + { + cacheItems = p.Appearance.WearableCacheItems; + } + } } + if (cacheItems != null) + { + // We need to make sure the asset stored in the bake is available on this server also by its assetid before we map it to a Cacheid. + // Copy the baked textures to the sim's assets cache (local only). + foreach (WearableCacheItem item in cacheItems) + { + if (cache.GetCached(item.TextureID.ToString()) == null) + { + item.TextureAsset.Temporary = true; + item.TextureAsset.Local = true; + cache.Store(item.TextureAsset); + } + } + + // Return the cached textures + for (int i = 0; i < maxWearablesLoop; i++) + { + WearableCacheItem item = + WearableCacheItem.SearchTextureIndex(cachedtex.WearableData[i].TextureIndex, cacheItems); + + cachedresp.WearableData[i] = new AgentCachedTextureResponsePacket.WearableDataBlock(); + cachedresp.WearableData[i].TextureIndex = cachedtex.WearableData[i].TextureIndex; + cachedresp.WearableData[i].HostName = new byte[0]; + if (item != null && cachedtex.WearableData[i].ID == item.CacheId) + { + cachedresp.WearableData[i].TextureID = item.TextureID; + } + else + { + cachedresp.WearableData[i].TextureID = UUID.Zero; + } + } + } + else + { + // Cached textures not available + for (int i = 0; i < maxWearablesLoop; i++) + { + cachedresp.WearableData[i] = new AgentCachedTextureResponsePacket.WearableDataBlock(); + cachedresp.WearableData[i].TextureIndex = cachedtex.WearableData[i].TextureIndex; + cachedresp.WearableData[i].TextureID = UUID.Zero; + cachedresp.WearableData[i].HostName = new byte[0]; + } + } + cachedresp.Header.Zerocoded = true; OutPacket(cachedresp, ThrottleOutPacketType.Task); return true; } + + /// + /// Send a response back to a client when it asks the asset server (via the region server) if it has + /// its appearance texture cached. + /// + /// + /// + /// + /// + public void SendCachedTextureResponse(ISceneEntity avatar, int serial, List cachedTextures) + { + ScenePresence presence = avatar as ScenePresence; + if (presence == null) + return; + + AgentCachedTextureResponsePacket cachedresp = (AgentCachedTextureResponsePacket)PacketPool.Instance.GetPacket(PacketType.AgentCachedTextureResponse); + + // TODO: don't create new blocks if recycling an old packet + cachedresp.AgentData.AgentID = m_agentId; + cachedresp.AgentData.SessionID = m_sessionId; + cachedresp.AgentData.SerialNum = serial; + cachedresp.WearableData = new AgentCachedTextureResponsePacket.WearableDataBlock[cachedTextures.Count]; + + for (int i = 0; i < cachedTextures.Count; i++) + { + cachedresp.WearableData[i] = new AgentCachedTextureResponsePacket.WearableDataBlock(); + cachedresp.WearableData[i].TextureIndex = (byte)cachedTextures[i].BakedTextureIndex; + cachedresp.WearableData[i].TextureID = cachedTextures[i].BakedTextureID; + cachedresp.WearableData[i].HostName = new byte[0]; + } + + cachedresp.Header.Zerocoded = true; + OutPacket(cachedresp, ThrottleOutPacketType.Task); + } protected bool HandleMultipleObjUpdate(IClientAPI simClient, Packet packet) { @@ -11449,8 +12076,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (part == null) { // It's a ghost! tell the client to delete it from view. - simClient.SendKillObject(Scene.RegionInfo.RegionHandle, - new List { localId }); + simClient.SendKillObject(new List { localId }); } else { @@ -11777,6 +12403,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// provide your own method. protected void OutPacket(Packet packet, ThrottleOutPacketType throttlePacketType, bool doAutomaticSplitting, UnackedPacketMethod method) { + if (m_outPacketsToDrop != null) + if (m_outPacketsToDrop.Contains(packet.Type.ToString())) + return; + if (DebugPacketLevel > 0) { bool logPacket = true; @@ -11811,17 +12441,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_udpServer.SendPacket(m_udpClient, packet, throttlePacketType, doAutomaticSplitting, method); } - public bool AddMoney(int debit) - { - if (m_moneyBalance + debit >= 0) - { - m_moneyBalance += debit; - SendMoneyBalance(UUID.Zero, true, Util.StringToBytes256("Poof Poof!"), m_moneyBalance); - return true; - } - return false; - } - protected void HandleAutopilot(Object sender, string method, List args) { float locx = 0; @@ -11846,6 +12465,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// OpenMetaverse.packet public void ProcessInPacket(Packet packet) { + if (m_inPacketsToDrop != null) + if (m_inPacketsToDrop.Contains(packet.Type.ToString())) + return; + if (DebugPacketLevel > 0) { bool logPacket = true; @@ -11880,6 +12503,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP shape.PCode = addPacket.ObjectData.PCode; shape.State = addPacket.ObjectData.State; + shape.LastAttachPoint = addPacket.ObjectData.State; shape.PathBegin = addPacket.ObjectData.PathBegin; shape.PathEnd = addPacket.ObjectData.PathEnd; shape.PathScaleX = addPacket.ObjectData.PathScaleX; @@ -11910,7 +12534,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP ClientInfo info = m_udpClient.GetClientInfo(); info.proxyEP = null; - info.agentcircuit = RequestClientInfo(); + if (info.agentcircuit == null) + info.agentcircuit = RequestClientInfo(); return info; } @@ -12079,6 +12704,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP return String.Empty; } + public OSDMap OReport(string uptime, string version) + { + return new OSDMap(); + } + /// /// Make an asset request to the asset service in response to a client request. /// @@ -12126,16 +12756,33 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (asset == null) { - req.AssetInf = null; - req.AssetRequestSource = source; - req.IsTextureRequest = false; - req.NumPackets = 0; - req.Params = transferRequest.TransferInfo.Params; - req.RequestAssetID = requestID; - req.TransferRequestID = transferRequest.TransferInfo.TransferID; + // Try the user's asset server + IInventoryAccessModule inventoryAccessModule = Scene.RequestModuleInterface(); + + string assetServerURL = string.Empty; + if (inventoryAccessModule.IsForeignUser(AgentId, out assetServerURL) && !string.IsNullOrEmpty(assetServerURL)) + { + if (!assetServerURL.EndsWith("/") && !assetServerURL.EndsWith("=")) + assetServerURL = assetServerURL + "/"; + + //m_log.DebugFormat("[LLCLIENTVIEW]: asset {0} not found in local storage. Trying user's storage.", assetServerURL + id); + asset = m_scene.AssetService.Get(assetServerURL + id); + } + + if (asset == null) + { + req.AssetInf = null; + req.AssetRequestSource = source; + req.IsTextureRequest = false; + req.NumPackets = 0; + req.Params = transferRequest.TransferInfo.Params; + req.RequestAssetID = requestID; + req.TransferRequestID = transferRequest.TransferInfo.TransferID; + + SendAssetNotFound(req); + return; + } - SendAssetNotFound(req); - return; } if (transferRequest.TransferInfo.SourceType == (int)SourceType.Asset) @@ -12201,8 +12848,21 @@ namespace OpenSim.Region.ClientStack.LindenUDP public struct PacketProcessor { - public PacketMethod method; - public bool Async; + /// + /// Packet handling method. + /// + public PacketMethod method { get; set; } + + /// + /// Should this packet be handled asynchronously? + /// + public bool Async { get; set; } + + /// + /// If async is true, should this packet be handled in the async engine or given directly to a threadpool + /// thread? + /// + public bool InEngine { get; set; } } public class AsyncPacketProcess @@ -12284,11 +12944,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP OutPacket(dialog, ThrottleOutPacketType.Task); } - public void StopFlying(ISceneEntity p) + public void SendAgentTerseUpdate(ISceneEntity p) { if (p is ScenePresence) { - ScenePresence presence = p as ScenePresence; +// m_log.DebugFormat( +// "[LLCLIENTVIEW]: Immediately sending terse agent update for {0} to {1} in {2}", +// p.Name, Name, Scene.Name); + // It turns out to get the agent to stop flying, you have to feed it stop flying velocities // There's no explicit message to send the client to tell it to stop flying.. it relies on the // velocity, collision plane and avatar height @@ -12296,34 +12959,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Add 1/6 the avatar's height to it's position so it doesn't shoot into the air // when the avatar stands up - Vector3 pos = presence.AbsolutePosition; - - if (presence.Appearance.AvatarHeight != 127.0f) - pos += new Vector3(0f, 0f, (presence.Appearance.AvatarHeight/6f)); - else - pos += new Vector3(0f, 0f, (1.56f/6f)); - - presence.AbsolutePosition = pos; - - // attach a suitable collision plane regardless of the actual situation to force the LLClient to land. - // Collision plane below the avatar's position a 6th of the avatar's height is suitable. - // Mind you, that this method doesn't get called if the avatar's velocity magnitude is greater then a - // certain amount.. because the LLClient wouldn't land in that situation anyway. - - // why are we still testing for this really old height value default??? - if (presence.Appearance.AvatarHeight != 127.0f) - presence.CollisionPlane = new Vector4(0, 0, 0, pos.Z - presence.Appearance.AvatarHeight/6f); - else - presence.CollisionPlane = new Vector4(0, 0, 0, pos.Z - (1.56f/6f)); - - ImprovedTerseObjectUpdatePacket.ObjectDataBlock block = CreateImprovedTerseBlock(p, false); const float TIME_DILATION = 1.0f; ushort timeDilation = Utils.FloatToUInt16(TIME_DILATION, 0.0f, 1.0f); - ImprovedTerseObjectUpdatePacket packet = (ImprovedTerseObjectUpdatePacket)PacketPool.Instance.GetPacket( PacketType.ImprovedTerseObjectUpdate); @@ -12565,5 +13206,51 @@ namespace OpenSim.Region.ClientStack.LindenUDP eq.Enqueue(BuildEvent("BulkUpdateInventory", llsd), AgentId); } + + private HashSet m_outPacketsToDrop; + + public bool AddOutPacketToDropSet(string packetName) + { + if (m_outPacketsToDrop == null) + m_outPacketsToDrop = new HashSet(); + + return m_outPacketsToDrop.Add(packetName); + } + + public bool RemoveOutPacketFromDropSet(string packetName) + { + if (m_outPacketsToDrop == null) + return false; + + return m_outPacketsToDrop.Remove(packetName); + } + + public HashSet GetOutPacketDropSet() + { + return new HashSet(m_outPacketsToDrop); + } + + private HashSet m_inPacketsToDrop; + + public bool AddInPacketToDropSet(string packetName) + { + if (m_inPacketsToDrop == null) + m_inPacketsToDrop = new HashSet(); + + return m_inPacketsToDrop.Add(packetName); + } + + public bool RemoveInPacketFromDropSet(string packetName) + { + if (m_inPacketsToDrop == null) + return false; + + return m_inPacketsToDrop.Remove(packetName); + } + + public HashSet GetInPacketDropSet() + { + return new HashSet(m_inPacketsToDrop); + } } } diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLImageManager.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLImageManager.cs index 073c357..41dd4d1 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLImageManager.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLImageManager.cs @@ -206,6 +206,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } + public bool HasUpdates() + { + J2KImage image = GetHighestPriorityImage(); + + return image != null && image.IsDecoded; + } + public bool ProcessImageQueue(int packetsToSend) { int packetsSent = 0; diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs index 8963756..0394e54 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs @@ -31,6 +31,7 @@ using System.Net; using System.Threading; using log4net; using OpenSim.Framework; +using OpenSim.Framework.Monitoring; using OpenMetaverse; using OpenMetaverse.Packets; @@ -75,12 +76,41 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// or removed, this number must also change const int THROTTLE_CATEGORY_COUNT = 8; + /// + /// Controls whether information is logged about each outbound packet immediately before it is sent. For debug purposes. + /// + /// Any level above 0 will turn on logging. + public int DebugDataOutLevel { get; set; } + + /// + /// Controls whether information is logged about each outbound packet immediately before it is sent. For debug purposes. + /// + /// Any level above 0 will turn on logging. + public int ThrottleDebugLevel + { + get + { + return m_throttleDebugLevel; + } + + set + { + m_throttleDebugLevel = value; + m_throttleClient.DebugLevel = m_throttleDebugLevel; + foreach (TokenBucket tb in m_throttleCategories) + tb.DebugLevel = m_throttleDebugLevel; + } + } + private int m_throttleDebugLevel; + /// Fired when updated networking stats are produced for this client public event PacketStats OnPacketStats; /// Fired when the queue for a packet category is empty. This event can be /// hooked to put more data on the empty queue public event QueueEmpty OnQueueEmpty; + public event Func HasUpdates; + /// AgentID for this client public readonly UUID AgentID; /// The remote address of the connected client @@ -89,8 +119,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP public readonly uint CircuitCode; /// Sequence numbers of packets we've received (for duplicate checking) public readonly IncomingPacketHistoryCollection PacketArchive = new IncomingPacketHistoryCollection(200); + + /// + /// If true then we take action in response to unacked reliably sent packets such as resending the packet. + /// + public bool ProcessUnackedSends { get; set; } + /// Packets we have sent that need to be ACKed by the client public readonly UnackedPacketCollection NeedAcks = new UnackedPacketCollection(); + /// ACKs that are queued up, waiting to be sent to the client public readonly OpenSim.Framework.LocklessQueue PendingAcks = new OpenSim.Framework.LocklessQueue(); @@ -141,8 +178,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP get { return m_throttleClient; } } - /// Throttle bucket for this agent's connection - private readonly TokenBucket m_throttleCategory; /// Throttle buckets for each packet category private readonly TokenBucket[] m_throttleCategories; /// Outgoing queues for throttled packets @@ -160,6 +195,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP private int m_maxRTO = 60000; /// + /// This is the percentage of the udp texture queue to add to the task queue since + /// textures are now generally handled through http. + /// + private double m_cannibalrate = 0.0; + + private ClientInfo m_info = new ClientInfo(); + + /// /// Default constructor /// /// Reference to the UDP server this client is connected to @@ -189,21 +232,31 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (maxRTO != 0) m_maxRTO = maxRTO; + ProcessUnackedSends = true; + // Create a token bucket throttle for this client that has the scene token bucket as a parent - m_throttleClient = new AdaptiveTokenBucket(parentThrottle, rates.Total, rates.AdaptiveThrottlesEnabled); - // Create a token bucket throttle for the total categary with the client bucket as a throttle - m_throttleCategory = new TokenBucket(m_throttleClient, 0); + m_throttleClient + = new AdaptiveTokenBucket( + string.Format("adaptive throttle for {0} in {1}", AgentID, server.Scene.Name), + parentThrottle, 0, rates.Total, rates.MinimumAdaptiveThrottleRate, rates.AdaptiveThrottlesEnabled); + // Create an array of token buckets for this clients different throttle categories m_throttleCategories = new TokenBucket[THROTTLE_CATEGORY_COUNT]; + m_cannibalrate = rates.CannibalizeTextureRate; + for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) { ThrottleOutPacketType type = (ThrottleOutPacketType)i; // Initialize the packet outboxes, where packets sit while they are waiting for tokens m_packetOutboxes[i] = new OpenSim.Framework.LocklessQueue(); + // Initialize the token buckets that control the throttling for each category - m_throttleCategories[i] = new TokenBucket(m_throttleCategory, rates.GetRate(type)); + m_throttleCategories[i] + = new TokenBucket( + string.Format("{0} throttle for {1} in {2}", type, AgentID, server.Scene.Name), + m_throttleClient, rates.GetRate(type), 0); } // Default the retransmission timeout to one second @@ -240,20 +293,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP // TODO: This data structure is wrong in so many ways. Locking and copying the entire lists // of pending and needed ACKs for every client every time some method wants information about // this connection is a recipe for poor performance - ClientInfo info = new ClientInfo(); - info.pendingAcks = new Dictionary(); - info.needAck = new Dictionary(); - - info.resendThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Resend].DripRate; - info.landThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Land].DripRate; - info.windThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate; - info.cloudThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate; - info.taskThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Task].DripRate; - info.assetThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate; - info.textureThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate; - info.totalThrottle = (int)m_throttleCategory.DripRate; - - return info; + + m_info.resendThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Resend].DripRate; + m_info.landThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Land].DripRate; + m_info.windThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate; + m_info.cloudThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate; + m_info.taskThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Task].DripRate; + m_info.assetThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate; + m_info.textureThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate; + m_info.totalThrottle = (int)m_throttleClient.DripRate; + m_info.targetThrottle = (int)m_throttleClient.TargetDripRate; + m_info.maxThrottle = (int)m_throttleClient.MaxDripRate; + + return m_info; } /// @@ -269,6 +321,33 @@ namespace OpenSim.Region.ClientStack.LindenUDP } /// + /// Get the total number of pakcets queued for this client. + /// + /// + public int GetTotalPacketsQueuedCount() + { + int total = 0; + + for (int i = 0; i <= (int)ThrottleOutPacketType.Asset; i++) + total += m_packetOutboxes[i].Count; + + return total; + } + + /// + /// Get the number of packets queued for the given throttle type. + /// + /// + /// + public int GetPacketsQueuedCount(ThrottleOutPacketType throttleType) + { + if ((int)throttleType > 0) + return m_packetOutboxes[(int)throttleType].Count; + else + return 0; + } + + /// /// Return statistics information about client packet queues. /// /// @@ -278,7 +357,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP public string GetStats() { return string.Format( - "{0,7} {1,7} {2,7} {3,9} {4,7} {5,7} {6,7} {7,7} {8,7} {9,8} {10,7} {11,7} {12,7}", + "{0,7} {1,7} {2,7} {3,9} {4,7} {5,7} {6,7} {7,7} {8,7} {9,8} {10,7} {11,7}", Util.EnvironmentTickCountSubtract(TickLastPacketReceived), PacketsReceived, PacketsSent, @@ -290,8 +369,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_packetOutboxes[(int)ThrottleOutPacketType.Cloud].Count, m_packetOutboxes[(int)ThrottleOutPacketType.Task].Count, m_packetOutboxes[(int)ThrottleOutPacketType.Texture].Count, - m_packetOutboxes[(int)ThrottleOutPacketType.Asset].Count, - m_packetOutboxes[(int)ThrottleOutPacketType.State].Count); + m_packetOutboxes[(int)ThrottleOutPacketType.Asset].Count); } public void SendPacketStats() @@ -337,8 +415,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP int task = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; int texture = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); - // State is a subcategory of task that we allocate a percentage to - int state = 0; + + if (ThrottleDebugLevel > 0) + { + long total = resend + land + wind + cloud + task + texture + asset; + m_log.DebugFormat( + "[LLUDPCLIENT]: {0} is setting throttles in {1} to Resend={2}, Land={3}, Wind={4}, Cloud={5}, Task={6}, Texture={7}, Asset={8}, TOTAL = {9}", + AgentID, m_udpServer.Scene.Name, resend, land, wind, cloud, task, texture, asset, total); + } // Make sure none of the throttles are set below our packet MTU, // otherwise a throttle could become permanently clogged @@ -350,11 +434,29 @@ namespace OpenSim.Region.ClientStack.LindenUDP texture = Math.Max(texture, LLUDPServer.MTU); asset = Math.Max(asset, LLUDPServer.MTU); + // Since most textures are now delivered through http, make it possible + // to cannibalize some of the bw from the texture throttle to use for + // the task queue (e.g. object updates) + task = task + (int)(m_cannibalrate * texture); + texture = (int)((1 - m_cannibalrate) * texture); + //int total = resend + land + wind + cloud + task + texture + asset; - //m_log.DebugFormat("[LLUDPCLIENT]: {0} is setting throttles. Resend={1}, Land={2}, Wind={3}, Cloud={4}, Task={5}, Texture={6}, Asset={7}, Total={8}", - // AgentID, resend, land, wind, cloud, task, texture, asset, total); + + if (ThrottleDebugLevel > 0) + { + long total = resend + land + wind + cloud + task + texture + asset; + m_log.DebugFormat( + "[LLUDPCLIENT]: {0} is setting throttles in {1} to Resend={2}, Land={3}, Wind={4}, Cloud={5}, Task={6}, Texture={7}, Asset={8}, TOTAL = {9}", + AgentID, m_udpServer.Scene.Name, resend, land, wind, cloud, task, texture, asset, total); + } // Update the token buckets with new throttle values + if (m_throttleClient.AdaptiveEnabled) + { + long total = resend + land + wind + cloud + task + texture + asset; + m_throttleClient.TargetDripRate = total; + } + TokenBucket bucket; bucket = m_throttleCategories[(int)ThrottleOutPacketType.Resend]; @@ -375,9 +477,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP bucket = m_throttleCategories[(int)ThrottleOutPacketType.Task]; bucket.RequestedDripRate = task; - bucket = m_throttleCategories[(int)ThrottleOutPacketType.State]; - bucket.RequestedDripRate = state; - bucket = m_throttleCategories[(int)ThrottleOutPacketType.Texture]; bucket.RequestedDripRate = texture; @@ -620,15 +719,45 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// Throttle categories to fire the callback for private void BeginFireQueueEmpty(ThrottleOutPacketTypeFlags categories) { - if (m_nextOnQueueEmpty != 0 && (Environment.TickCount & Int32.MaxValue) >= m_nextOnQueueEmpty) +// if (m_nextOnQueueEmpty != 0 && (Environment.TickCount & Int32.MaxValue) >= m_nextOnQueueEmpty) + if (!m_isQueueEmptyRunning && (Environment.TickCount & Int32.MaxValue) >= m_nextOnQueueEmpty) { + m_isQueueEmptyRunning = true; + + int start = Environment.TickCount & Int32.MaxValue; + const int MIN_CALLBACK_MS = 30; + + m_nextOnQueueEmpty = start + MIN_CALLBACK_MS; + if (m_nextOnQueueEmpty == 0) + m_nextOnQueueEmpty = 1; + // Use a value of 0 to signal that FireQueueEmpty is running - m_nextOnQueueEmpty = 0; - // Asynchronously run the callback - Util.FireAndForget(FireQueueEmpty, categories); +// m_nextOnQueueEmpty = 0; + + m_categories = categories; + + if (HasUpdates(m_categories)) + { + if (!m_udpServer.OqrEngine.IsRunning) + { + // Asynchronously run the callback + Util.FireAndForget(FireQueueEmpty, categories, "LLUDPClient.BeginFireQueueEmpty"); + } + else + { + m_udpServer.OqrEngine.QueueJob(AgentID.ToString(), () => FireQueueEmpty(categories)); + } + } + else + { + m_isQueueEmptyRunning = false; + } } } + private bool m_isQueueEmptyRunning; + private ThrottleOutPacketTypeFlags m_categories = 0; + /// /// Fires the OnQueueEmpty callback and sets the minimum time that it /// can be called again @@ -636,24 +765,35 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// Throttle categories to fire the callback for, /// stored as an object to match the WaitCallback delegate /// signature - private void FireQueueEmpty(object o) + public void FireQueueEmpty(object o) { - const int MIN_CALLBACK_MS = 30; +// m_log.DebugFormat("[LLUDPCLIENT]: FireQueueEmpty for {0} in {1}", AgentID, m_udpServer.Scene.Name); - ThrottleOutPacketTypeFlags categories = (ThrottleOutPacketTypeFlags)o; - QueueEmpty callback = OnQueueEmpty; - - int start = Environment.TickCount & Int32.MaxValue; +// int start = Environment.TickCount & Int32.MaxValue; +// const int MIN_CALLBACK_MS = 30; - if (callback != null) - { - try { callback(categories); } - catch (Exception e) { m_log.Error("[LLUDPCLIENT]: OnQueueEmpty(" + categories + ") threw an exception: " + e.Message, e); } - } +// if (m_udpServer.IsRunningOutbound) +// { + ThrottleOutPacketTypeFlags categories = (ThrottleOutPacketTypeFlags)o; + QueueEmpty callback = OnQueueEmpty; + + if (callback != null) + { +// if (m_udpServer.IsRunningOutbound) +// { + try { callback(categories); } + catch (Exception e) { m_log.Error("[LLUDPCLIENT]: OnQueueEmpty(" + categories + ") threw an exception: " + e.Message, e); } +// } + } +// } + +// m_nextOnQueueEmpty = start + MIN_CALLBACK_MS; +// if (m_nextOnQueueEmpty == 0) +// m_nextOnQueueEmpty = 1; + +// } - m_nextOnQueueEmpty = start + MIN_CALLBACK_MS; - if (m_nextOnQueueEmpty == 0) - m_nextOnQueueEmpty = 1; + m_isQueueEmptyRunning = false; } /// @@ -678,9 +818,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP Texture = 5, /// Non-texture assets Asset = 6, - /// Avatar and primitive data - /// This is a sub-category of Task - State = 7, */ switch (category) @@ -697,11 +834,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP return ThrottleOutPacketTypeFlags.Texture; case ThrottleOutPacketType.Asset: return ThrottleOutPacketTypeFlags.Asset; - case ThrottleOutPacketType.State: - return ThrottleOutPacketTypeFlags.State; default: return 0; } } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs index a7628d2..4528714 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs @@ -40,8 +40,9 @@ using OpenSim.Framework; using OpenSim.Framework.Console; using OpenSim.Framework.Monitoring; using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; using OpenMetaverse; - +using Mono.Addins; using TokenBucket = OpenSim.Region.ClientStack.LindenUDP.TokenBucket; namespace OpenSim.Region.ClientStack.LindenUDP @@ -49,22 +50,58 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// /// A shim around LLUDPServer that implements the IClientNetworkServer interface /// - public sealed class LLUDPServerShim : IClientNetworkServer + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "LLUDPServerShim")] + public sealed class LLUDPServerShim : INonSharedRegionModule { + private bool m_Enabled = true; + private IConfigSource m_Config; LLUDPServer m_udpServer; - public LLUDPServerShim() + #region INonSharedRegionModule + public string Name { + get { return "LLUDPServerShim"; } } - public void Initialise(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager) + public Type ReplaceableInterface { - m_udpServer = new LLUDPServer(listenIP, ref port, proxyPortOffsetParm, allow_alternate_port, configSource, circuitManager); + get { return null; } } - public void NetworkStop() + public void Initialise(IConfigSource source) { - m_udpServer.Stop(); + m_Config = source; + } + + public void Close() + { + } + + public void AddRegion(Scene scene) + { + uint port = (uint)scene.RegionInfo.InternalEndPoint.Port; + + IPAddress listenIP = scene.RegionInfo.InternalEndPoint.Address; + Initialise(listenIP, ref port, scene.RegionInfo.ProxyOffset, scene.RegionInfo.m_allow_alternate_ports, m_Config, scene.AuthenticateHandler); + scene.RegionInfo.InternalEndPoint.Port = (int)port; + + AddScene(scene); + } + + public void RemoveRegion(Scene scene) + { + Stop(); + } + + public void RegionLoaded(Scene scene) + { + Start(); + } + #endregion + + public void Initialise(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager) + { + m_udpServer = new LLUDPServer(listenIP, ref port, proxyPortOffsetParm, allow_alternate_port, configSource, circuitManager); } public void AddScene(IScene scene) @@ -73,9 +110,35 @@ namespace OpenSim.Region.ClientStack.LindenUDP StatsManager.RegisterStat( new Stat( + "ClientLogoutsDueToNoReceives", + "Number of times a client has been logged out because no packets were received before the timeout.", + "", + "", + "clientstack", + scene.Name, + StatType.Pull, + MeasuresOfInterest.None, + stat => stat.Value = m_udpServer.ClientLogoutsDueToNoReceives, + StatVerbosity.Debug)); + + StatsManager.RegisterStat( + new Stat( + "IncomingUDPReceivesCount", + "Number of UDP receives performed", + "", + "", + "clientstack", + scene.Name, + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => stat.Value = m_udpServer.UdpReceives, + StatVerbosity.Debug)); + + StatsManager.RegisterStat( + new Stat( "IncomingPacketsProcessedCount", - "Number of inbound UDP packets processed", - "Number of inbound UDP packets processed", + "Number of inbound LL protocol packets processed", + "", "", "clientstack", scene.Name, @@ -83,6 +146,86 @@ namespace OpenSim.Region.ClientStack.LindenUDP MeasuresOfInterest.AverageChangeOverTime, stat => stat.Value = m_udpServer.IncomingPacketsProcessed, StatVerbosity.Debug)); + + StatsManager.RegisterStat( + new Stat( + "IncomingPacketsMalformedCount", + "Number of inbound UDP packets that could not be recognized as LL protocol packets.", + "", + "", + "clientstack", + scene.Name, + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => stat.Value = m_udpServer.IncomingMalformedPacketCount, + StatVerbosity.Info)); + + StatsManager.RegisterStat( + new Stat( + "IncomingPacketsOrphanedCount", + "Number of inbound packets that were not initial connections packets and could not be associated with a viewer.", + "", + "", + "clientstack", + scene.Name, + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => stat.Value = m_udpServer.IncomingOrphanedPacketCount, + StatVerbosity.Info)); + + StatsManager.RegisterStat( + new Stat( + "IncomingPacketsResentCount", + "Number of inbound packets that clients indicate are resends.", + "", + "", + "clientstack", + scene.Name, + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => stat.Value = m_udpServer.IncomingPacketsResentCount, + StatVerbosity.Debug)); + + StatsManager.RegisterStat( + new Stat( + "OutgoingUDPSendsCount", + "Number of UDP sends performed", + "", + "", + "clientstack", + scene.Name, + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => stat.Value = m_udpServer.UdpSends, + StatVerbosity.Debug)); + + StatsManager.RegisterStat( + new Stat( + "OutgoingPacketsResentCount", + "Number of packets resent because a client did not acknowledge receipt", + "", + "", + "clientstack", + scene.Name, + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => stat.Value = m_udpServer.PacketsResentCount, + StatVerbosity.Debug)); + + StatsManager.RegisterStat( + new Stat( + "AverageUDPProcessTime", + "Average number of milliseconds taken to process each incoming UDP packet in a sample.", + "This is for initial receive processing which is separate from the later client LL packet processing stage.", + "ms", + "clientstack", + scene.Name, + StatType.Pull, + MeasuresOfInterest.None, + stat => stat.Value = m_udpServer.AverageReceiveTicksForLastSamplePeriod, +// stat => +// stat.Value = Math.Round(m_udpServer.AverageReceiveTicksForLastSamplePeriod, 7), + StatVerbosity.Debug)); } public bool HandlesRegion(Location x) @@ -99,6 +242,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP { m_udpServer.Stop(); } + } /// @@ -107,10 +251,24 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// public class LLUDPServer : OpenSimUDPBase { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + /// Maximum transmission unit, or UDP packet size, for the LLUDP protocol public const int MTU = 1400; - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + /// Number of forced client logouts due to no receipt of packets before timeout. + public int ClientLogoutsDueToNoReceives { get; private set; } + + /// + /// Default packet debug level given to new clients + /// + public int DefaultClientPacketDebugLevel { get; set; } + + /// + /// If set then all inbound agent updates are discarded. For debugging purposes. + /// discard agent update. + /// + public bool DiscardInboundAgentUpdates { get; set; } /// The measured resolution of Environment.TickCount public readonly float TickCountResolution; @@ -128,19 +286,22 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// Incoming packets that are awaiting handling private OpenMetaverse.BlockingQueue packetInbox = new OpenMetaverse.BlockingQueue(); - /// - //private UDPClientCollection m_clients = new UDPClientCollection(); /// Bandwidth throttle for this UDP server - protected TokenBucket m_throttle; + public TokenBucket Throttle { get; private set; } - /// Bandwidth throttle rates for this UDP server + /// Per client throttle rates enforced by this server + /// + /// If the total rate is non-zero, then this is the maximum total throttle setting that any client can ever have. + /// The other rates (resend, asset, etc.) are the defaults for a new client and can be changed (and usually + /// do get changed immediately). They do not need to sum to the total. + /// public ThrottleRates ThrottleRates { get; private set; } /// Manages authentication for agent circuits private AgentCircuitManager m_circuitManager; /// Reference to the scene this UDP server is attached to - protected Scene m_scene; + public Scene Scene { get; private set; } /// The X/Y coordinates of the scene this UDP server is attached to private Location m_location; @@ -181,6 +342,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// Flag to signal when clients should send pings protected bool m_sendPing; + /// + /// Event used to signal when queued packets are available for sending. + /// + /// + /// This allows the outbound loop to only operate when there is data to send rather than continuously polling. + /// Some data is sent immediately and not queued. That data would not trigger this event. + /// + private AutoResetEvent m_dataPresentEvent = new AutoResetEvent(false); + private Pool m_incomingPacketPool; /// @@ -201,7 +371,30 @@ namespace OpenSim.Region.ClientStack.LindenUDP public Socket Server { get { return null; } } - private int m_malformedCount = 0; // Guard against a spamming attack + /// + /// Record how many packets have been resent + /// + internal int PacketsResentCount { get; set; } + + /// + /// Record how many packets have been sent + /// + internal int PacketsSentCount { get; set; } + + /// + /// Record how many incoming packets are indicated as resends by clients. + /// + internal int IncomingPacketsResentCount { get; set; } + + /// + /// Record how many inbound packets could not be recognized as LLUDP packets. + /// + public int IncomingMalformedPacketCount { get; private set; } + + /// + /// Record how many inbound packets could not be associated with a simulator circuit. + /// + public int IncomingOrphanedPacketCount { get; private set; } /// /// Record current outgoing client for monitoring purposes. @@ -213,6 +406,22 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// private IClientAPI m_currentIncomingClient; + /// + /// Queue some low priority but potentially high volume async requests so that they don't overwhelm available + /// threadpool threads. + /// + public JobEngine IpahEngine { get; private set; } + + /// + /// Run queue empty processing within a single persistent thread. + /// + /// + /// This is the alternative to having every + /// connection schedule its own job in the threadpool which causes performance problems when there are many + /// connections. + /// + public JobEngine OqrEngine { get; private set; } + public LLUDPServer( IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager) @@ -278,27 +487,20 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_shouldCollectStats = false; if (config != null) { - if (config.Contains("enabled") && config.GetBoolean("enabled")) - { - if (config.Contains("collect_packet_headers")) - m_shouldCollectStats = config.GetBoolean("collect_packet_headers"); - if (config.Contains("packet_headers_period_seconds")) - { - binStatsMaxFilesize = TimeSpan.FromSeconds(config.GetInt("region_stats_period_seconds")); - } - if (config.Contains("stats_dir")) - { - binStatsDir = config.GetString("stats_dir"); - } - } - else - { - m_shouldCollectStats = false; - } - } - #endregion BinaryStats - - m_throttle = new TokenBucket(null, sceneThrottleBps); + m_shouldCollectStats = config.GetBoolean("Enabled", false); + binStatsMaxFilesize = TimeSpan.FromSeconds(config.GetInt("packet_headers_period_seconds", 300)); + binStatsDir = config.GetString("stats_dir", "."); + m_aggregatedBWStats = config.GetBoolean("aggregatedBWStats", false); + } + #endregion BinaryStats + + // FIXME: Can't add info here because don't know scene yet. +// m_throttle +// = new TokenBucket( +// string.Format("server throttle bucket for {0}", Scene.Name), null, sceneThrottleBps); + + Throttle = new TokenBucket("server throttle bucket", null, 0, sceneThrottleBps); + ThrottleRates = new ThrottleRates(configSource); if (usePools) @@ -309,11 +511,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP { StartInbound(); StartOutbound(); + IpahEngine.Start(); + OqrEngine.Start(); m_elapsedMSSinceLastStatReport = Environment.TickCount; } - private void StartInbound() + public void StartInbound() { m_log.InfoFormat( "[LLUDPSERVER]: Starting inbound packet processing for the LLUDP server in {0} mode with UsePools = {1}", @@ -322,9 +526,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP base.StartInbound(m_recvBufferSize, m_asyncPacketHandling); // This thread will process the packets received that are placed on the packetInbox - Watchdog.StartThread( + WorkManager.StartThread( IncomingPacketHandler, - string.Format("Incoming Packets ({0})", m_scene.RegionInfo.RegionName), + string.Format("Incoming Packets ({0})", Scene.Name), ThreadPriority.Normal, false, true, @@ -332,15 +536,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS); } - private new void StartOutbound() + public override void StartOutbound() { m_log.Info("[LLUDPSERVER]: Starting outbound packet processing for the LLUDP server"); base.StartOutbound(); - Watchdog.StartThread( + WorkManager.StartThread( OutgoingPacketHandler, - string.Format("Outgoing Packets ({0})", m_scene.RegionInfo.RegionName), + string.Format("Outgoing Packets ({0})", Scene.Name), ThreadPriority.Normal, false, true, @@ -350,12 +554,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void Stop() { - m_log.Info("[LLUDPSERVER]: Shutting down the LLUDP server for " + m_scene.RegionInfo.RegionName); + m_log.Info("[LLUDPSERVER]: Shutting down the LLUDP server for " + Scene.Name); base.StopOutbound(); base.StopInbound(); + IpahEngine.Stop(); + OqrEngine.Stop(); } - protected override bool EnablePools() + public override bool EnablePools() { if (!UsePools) { @@ -369,7 +575,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP return false; } - protected override bool DisablePools() + public override bool DisablePools() { if (UsePools) { @@ -389,7 +595,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// This is a seperate method so that it can be called once we have an m_scene to distinguish different scene /// stats. /// - private void EnablePoolStats() + protected internal void EnablePoolStats() { m_poolCountStat = new Stat( @@ -398,7 +604,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP "The number of objects currently stored within the UDPPacketBuffer pool", "", "clientstack", - m_scene.Name, + Scene.Name, StatType.Pull, stat => stat.Value = Pool.Count, StatVerbosity.Debug); @@ -412,7 +618,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP "The number of objects currently stored within the incoming packet pool", "", "clientstack", - m_scene.Name, + Scene.Name, StatType.Pull, stat => stat.Value = m_incomingPacketPool.Count, StatVerbosity.Debug); @@ -423,7 +629,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// /// Disables pool stats. /// - private void DisablePoolStats() + protected internal void DisablePoolStats() { StatsManager.DeregisterStat(m_poolCountStat); m_poolCountStat = null; @@ -456,7 +662,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void AddScene(IScene scene) { - if (m_scene != null) + if (Scene != null) { m_log.Error("[LLUDPSERVER]: AddScene() called on an LLUDPServer that already has a scene"); return; @@ -468,8 +674,31 @@ namespace OpenSim.Region.ClientStack.LindenUDP return; } - m_scene = (Scene)scene; - m_location = new Location(m_scene.RegionInfo.RegionHandle); + Scene = (Scene)scene; + m_location = new Location(Scene.RegionInfo.RegionHandle); + + IpahEngine + = new JobEngine( + string.Format("Incoming Packet Async Handling Engine ({0})", Scene.Name), + "INCOMING PACKET ASYNC HANDLING ENGINE"); + + OqrEngine + = new JobEngine( + string.Format("Outgoing Queue Refill Engine ({0})", Scene.Name), + "OUTGOING QUEUE REFILL ENGINE"); + + StatsManager.RegisterStat( + new Stat( + "InboxPacketsCount", + "Number of LL protocol packets waiting for the second stage of processing after initial receive.", + "Number of LL protocol packets waiting for the second stage of processing after initial receive.", + "", + "clientstack", + scene.Name, + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => stat.Value = packetInbox.Count, + StatVerbosity.Debug)); // XXX: These stats are also pool stats but we register them separately since they are currently not // turned on and off by EnablePools()/DisablePools() @@ -479,7 +708,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP "Packets reused", "Number of packets reused out of all requests to the packet pool", "clientstack", - m_scene.Name, + Scene.Name, StatType.Pull, stat => { PercentageStat pstat = (PercentageStat)stat; @@ -493,7 +722,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP "Packet data blocks reused", "Number of data blocks reused out of all requests to the packet pool", "clientstack", - m_scene.Name, + Scene.Name, StatType.Pull, stat => { PercentageStat pstat = (PercentageStat)stat; @@ -508,7 +737,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP "The number of objects currently stored within the packet pool", "", "clientstack", - m_scene.Name, + Scene.Name, StatType.Pull, stat => stat.Value = PacketPool.Instance.PacketsPooled, StatVerbosity.Debug)); @@ -520,132 +749,57 @@ namespace OpenSim.Region.ClientStack.LindenUDP "The number of objects currently stored within the packet data block pool", "", "clientstack", - m_scene.Name, + Scene.Name, StatType.Pull, stat => stat.Value = PacketPool.Instance.BlocksPooled, StatVerbosity.Debug)); + + StatsManager.RegisterStat( + new Stat( + "OutgoingPacketsQueuedCount", + "Packets queued for outgoing send", + "Number of queued outgoing packets across all connections", + "", + "clientstack", + Scene.Name, + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => stat.Value = GetTotalQueuedOutgoingPackets(), + StatVerbosity.Info)); + + StatsManager.RegisterStat( + new Stat( + "IncomingPacketAsyncRequestsWaiting", + "Number of incoming packets waiting for async processing in engine.", + "", + "", + "clientstack", + Scene.Name, + StatType.Pull, + MeasuresOfInterest.None, + stat => stat.Value = IpahEngine.JobsWaiting, + StatVerbosity.Debug)); + + StatsManager.RegisterStat( + new Stat( + "OQRERequestsWaiting", + "Number of outgong queue refill requests waiting for processing.", + "", + "", + "clientstack", + Scene.Name, + StatType.Pull, + MeasuresOfInterest.None, + stat => stat.Value = OqrEngine.JobsWaiting, + StatVerbosity.Debug)); // We delay enabling pool stats to AddScene() instead of Initialize() so that we can distinguish pool stats by // scene name if (UsePools) EnablePoolStats(); - MainConsole.Instance.Commands.AddCommand( - "Debug", - false, - "debug lludp start", - "debug lludp start ", - "Control LLUDP packet processing.", - "No effect if packet processing has already started.\n" - + "in - start inbound processing.\n" - + "out - start outbound processing.\n" - + "all - start in and outbound processing.\n", - HandleStartCommand); - - MainConsole.Instance.Commands.AddCommand( - "Debug", - false, - "debug lludp stop", - "debug lludp stop ", - "Stop LLUDP packet processing.", - "No effect if packet processing has already stopped.\n" - + "in - stop inbound processing.\n" - + "out - stop outbound processing.\n" - + "all - stop in and outbound processing.\n", - HandleStopCommand); - - MainConsole.Instance.Commands.AddCommand( - "Debug", - false, - "debug lludp pool", - "debug lludp pool ", - "Turn object pooling within the lludp component on or off.", - HandlePoolCommand); - - MainConsole.Instance.Commands.AddCommand( - "Debug", - false, - "debug lludp status", - "debug lludp status", - "Return status of LLUDP packet processing.", - HandleStatusCommand); - } - - private void HandleStartCommand(string module, string[] args) - { - if (args.Length != 4) - { - MainConsole.Instance.Output("Usage: debug lludp start "); - return; - } - - string subCommand = args[3]; - - if (subCommand == "in" || subCommand == "all") - StartInbound(); - - if (subCommand == "out" || subCommand == "all") - StartOutbound(); - } - - private void HandleStopCommand(string module, string[] args) - { - if (args.Length != 4) - { - MainConsole.Instance.Output("Usage: debug lludp stop "); - return; - } - - string subCommand = args[3]; - - if (subCommand == "in" || subCommand == "all") - StopInbound(); - - if (subCommand == "out" || subCommand == "all") - StopOutbound(); - } - - private void HandlePoolCommand(string module, string[] args) - { - if (args.Length != 4) - { - MainConsole.Instance.Output("Usage: debug lludp pool "); - return; - } - - string enabled = args[3]; - - if (enabled == "on") - { - if (EnablePools()) - { - EnablePoolStats(); - MainConsole.Instance.OutputFormat("Packet pools enabled on {0}", m_scene.Name); - } - } - else if (enabled == "off") - { - if (DisablePools()) - { - DisablePoolStats(); - MainConsole.Instance.OutputFormat("Packet pools disabled on {0}", m_scene.Name); - } - } - else - { - MainConsole.Instance.Output("Usage: debug lludp pool "); - } - } - - private void HandleStatusCommand(string module, string[] args) - { - MainConsole.Instance.OutputFormat( - "IN LLUDP packet processing for {0} is {1}", m_scene.Name, IsRunningInbound ? "enabled" : "disabled"); - - MainConsole.Instance.OutputFormat( - "OUT LLUDP packet processing for {0} is {1}", m_scene.Name, IsRunningOutbound ? "enabled" : "disabled"); - - MainConsole.Instance.OutputFormat("LLUDP pools in {0} are {1}", m_scene.Name, UsePools ? "on" : "off"); + LLUDPServerCommands commands = new LLUDPServerCommands(MainConsole.Instance, this); + commands.Register(); } public bool HandlesRegion(Location x) @@ -653,45 +807,62 @@ namespace OpenSim.Region.ClientStack.LindenUDP return x == m_location; } - public void BroadcastPacket(Packet packet, ThrottleOutPacketType category, bool sendToPausedAgents, bool allowSplitting) + public int GetTotalQueuedOutgoingPackets() { - // CoarseLocationUpdate and AvatarGroupsReply packets cannot be split in an automated way - if ((packet.Type == PacketType.CoarseLocationUpdate || packet.Type == PacketType.AvatarGroupsReply) && allowSplitting) - allowSplitting = false; + int total = 0; - if (allowSplitting && packet.HasVariableBlocks) + foreach (ScenePresence sp in Scene.GetScenePresences()) { - byte[][] datas = packet.ToBytesMultiple(); - int packetCount = datas.Length; - - if (packetCount < 1) - m_log.Error("[LLUDPSERVER]: Failed to split " + packet.Type + " with estimated length " + packet.Length); - - for (int i = 0; i < packetCount; i++) + // XXX: Need a better way to determine which IClientAPIs have UDPClients (NPCs do not, for instance). + if (sp.ControllingClient is LLClientView) { - byte[] data = datas[i]; - m_scene.ForEachClient( - delegate(IClientAPI client) - { - if (client is LLClientView) - SendPacketData(((LLClientView)client).UDPClient, data, packet.Type, category, null); - } - ); + LLUDPClient udpClient = ((LLClientView)sp.ControllingClient).UDPClient; + total += udpClient.GetTotalPacketsQueuedCount(); } } - else - { - byte[] data = packet.ToBytes(); - m_scene.ForEachClient( - delegate(IClientAPI client) - { - if (client is LLClientView) - SendPacketData(((LLClientView)client).UDPClient, data, packet.Type, category, null); - } - ); - } + + return total; } +// public void BroadcastPacket(Packet packet, ThrottleOutPacketType category, bool sendToPausedAgents, bool allowSplitting) +// { +// // CoarseLocationUpdate and AvatarGroupsReply packets cannot be split in an automated way +// if ((packet.Type == PacketType.CoarseLocationUpdate || packet.Type == PacketType.AvatarGroupsReply) && allowSplitting) +// allowSplitting = false; +// +// if (allowSplitting && packet.HasVariableBlocks) +// { +// byte[][] datas = packet.ToBytesMultiple(); +// int packetCount = datas.Length; +// +// if (packetCount < 1) +// m_log.Error("[LLUDPSERVER]: Failed to split " + packet.Type + " with estimated length " + packet.Length); +// +// for (int i = 0; i < packetCount; i++) +// { +// byte[] data = datas[i]; +// m_scene.ForEachClient( +// delegate(IClientAPI client) +// { +// if (client is LLClientView) +// SendPacketData(((LLClientView)client).UDPClient, data, packet.Type, category, null); +// } +// ); +// } +// } +// else +// { +// byte[] data = packet.ToBytes(); +// m_scene.ForEachClient( +// delegate(IClientAPI client) +// { +// if (client is LLClientView) +// SendPacketData(((LLClientView)client).UDPClient, data, packet.Type, category, null); +// } +// ); +// } +// } + /// /// Start the process of sending a packet to the client. /// @@ -710,6 +881,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (packet.Type == PacketType.CoarseLocationUpdate && allowSplitting) allowSplitting = false; + bool packetQueued = false; + if (allowSplitting && packet.HasVariableBlocks) { byte[][] datas = packet.ToBytesMultiple(); @@ -721,16 +894,21 @@ namespace OpenSim.Region.ClientStack.LindenUDP for (int i = 0; i < packetCount; i++) { byte[] data = datas[i]; - SendPacketData(udpClient, data, packet.Type, category, method); + if (!SendPacketData(udpClient, data, packet.Type, category, method)) + packetQueued = true; } } else { byte[] data = packet.ToBytes(); - SendPacketData(udpClient, data, packet.Type, category, method); + if (!SendPacketData(udpClient, data, packet.Type, category, method)) + packetQueued = true; } PacketPool.Instance.ReturnPacket(packet); + + if (packetQueued) + m_dataPresentEvent.Set(); } /// @@ -744,7 +922,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// The method to call if the packet is not acked by the client. If null, then a standard /// resend of the packet is done. /// - public void SendPacketData( + /// true if the data was sent immediately, false if it was queued for sending + public bool SendPacketData( LLUDPClient udpClient, byte[] data, PacketType type, ThrottleOutPacketType category, UnackedPacketMethod method) { int dataLength = data.Length; @@ -801,15 +980,34 @@ namespace OpenSim.Region.ClientStack.LindenUDP #region Queue or Send OutgoingPacket outgoingPacket = new OutgoingPacket(udpClient, buffer, category, null); + // If we were not provided a method for handling unacked, use the UDPServer default method - outgoingPacket.UnackedMethod = ((method == null) ? delegate(OutgoingPacket oPacket) { ResendUnacked(oPacket); } : method); + if ((outgoingPacket.Buffer.Data[0] & Helpers.MSG_RELIABLE) != 0) + outgoingPacket.UnackedMethod = ((method == null) ? delegate(OutgoingPacket oPacket) { ResendUnacked(oPacket); } : method); // If a Linden Lab 1.23.5 client receives an update packet after a kill packet for an object, it will // continue to display the deleted object until relog. Therefore, we need to always queue a kill object // packet so that it isn't sent before a queued update packet. - bool requestQueue = type == PacketType.KillObject; - if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket, requestQueue)) + bool forceQueue = (type == PacketType.KillObject); + +// if (type == PacketType.ImprovedTerseObjectUpdate) +// { +// m_log.DebugFormat("Direct send ITOU to {0} in {1}", udpClient.AgentID, Scene.Name); +// SendPacketFinal(outgoingPacket); +// return false; +// } +// else +// { + if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket, forceQueue)) + { SendPacketFinal(outgoingPacket); + return true; + } + else + { + return false; + } +// } #endregion Queue or Send } @@ -885,7 +1083,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Fire this out on a different thread so that we don't hold up outgoing packet processing for // everybody else if this is being called due to an ack timeout. // This is the same as processing as the async process of a logout request. - Util.FireAndForget(o => DeactivateClientDueToTimeout(client)); + Util.FireAndForget( + o => DeactivateClientDueToTimeout(client, timeoutTicks), null, "LLUDPServer.DeactivateClientDueToTimeout"); return; } @@ -981,7 +1180,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP Utils.UIntToBytesBig(sequenceNumber, buffer.Data, 1); outgoingPacket.SequenceNumber = sequenceNumber; - if (isReliable) + if (udpClient.ProcessUnackedSends && isReliable) { // Add this packet to the list of ACK responses we are waiting on from the server udpClient.NeedAcks.Add(outgoingPacket); @@ -990,6 +1189,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP else { Interlocked.Increment(ref udpClient.PacketsResent); + + // We're not going to worry about interlock yet since its not currently critical that this total count + // is 100% correct + PacketsResentCount++; } #endregion Sequence Number Assignment @@ -997,6 +1200,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Stats tracking Interlocked.Increment(ref udpClient.PacketsSent); + // We're not going to worry about interlock yet since its not currently critical that this total count + // is 100% correct + PacketsSentCount++; + + if (udpClient.DebugDataOutLevel > 0) + m_log.DebugFormat( + "[LLUDPSERVER]: Sending packet #{0} (rel: {1}, res: {2}) to {3} from {4}", + outgoingPacket.SequenceNumber, isReliable, isResend, udpClient.AgentID, Scene.Name); + // Put the UDP payload on the wire AsyncBeginSend(buffer); @@ -1004,6 +1216,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP outgoingPacket.TickCount = Environment.TickCount & Int32.MaxValue; } + private void RecordMalformedInboundPacket(IPEndPoint endPoint) + { +// if (m_malformedCount < 100) +// m_log.DebugFormat("[LLUDPSERVER]: Dropped malformed packet: " + e.ToString()); + + IncomingMalformedPacketCount++; + + if ((IncomingMalformedPacketCount % 10000) == 0) + m_log.WarnFormat( + "[LLUDPSERVER]: Received {0} malformed packets so far, probable network attack. Last was from {1}", + IncomingMalformedPacketCount, endPoint); + } + public override void PacketReceived(UDPPacketBuffer buffer) { // Debugging/Profiling @@ -1025,6 +1250,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP // "[LLUDPSERVER]: Dropping undersized packet with {0} bytes received from {1} in {2}", // buffer.DataLength, buffer.RemoteEndPoint, m_scene.RegionInfo.RegionName); + RecordMalformedInboundPacket(endPoint); + return; // Drop undersized packet } @@ -1043,6 +1270,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP // "[LLUDPSERVER]: Dropping packet with malformed header received from {0} in {1}", // buffer.RemoteEndPoint, m_scene.RegionInfo.RegionName); + RecordMalformedInboundPacket(endPoint); + return; // Malformed header } @@ -1058,34 +1287,23 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Only allocate a buffer for zerodecoding if the packet is zerocoded ((buffer.Data[0] & Helpers.MSG_ZEROCODED) != 0) ? new byte[4096] : null); } - catch (MalformedDataException) - { - } - catch (IndexOutOfRangeException) - { -// m_log.WarnFormat( -// "[LLUDPSERVER]: Dropping short packet received from {0} in {1}", -// buffer.RemoteEndPoint, m_scene.RegionInfo.RegionName); - - return; // Drop short packet - } catch (Exception e) { - if (m_malformedCount < 100) + if (IncomingMalformedPacketCount < 100) m_log.DebugFormat("[LLUDPSERVER]: Dropped malformed packet: " + e.ToString()); - - m_malformedCount++; - - if ((m_malformedCount % 100000) == 0) - m_log.DebugFormat("[LLUDPSERVER]: Received {0} malformed packets so far, probable network attack.", m_malformedCount); } // Fail-safe check if (packet == null) { - m_log.ErrorFormat("[LLUDPSERVER]: Malformed data, cannot parse {0} byte packet from {1}:", - buffer.DataLength, buffer.RemoteEndPoint); - m_log.Error(Utils.BytesToHexString(buffer.Data, buffer.DataLength, null)); + if (IncomingMalformedPacketCount < 100) + { + m_log.WarnFormat("[LLUDPSERVER]: Malformed data, cannot parse {0} byte packet from {1}, data {2}:", + buffer.DataLength, buffer.RemoteEndPoint, Utils.BytesToHexString(buffer.Data, buffer.DataLength, null)); + } + + RecordMalformedInboundPacket(endPoint); + return; } @@ -1100,16 +1318,38 @@ namespace OpenSim.Region.ClientStack.LindenUDP // buffer. object[] array = new object[] { new IPEndPoint(endPoint.Address, endPoint.Port), packet }; - Util.FireAndForget(HandleUseCircuitCode, array); + Util.FireAndForget(HandleUseCircuitCode, array, "LLUDPServer.HandleUseCircuitCode"); + + return; + } + else if (packet.Type == PacketType.CompleteAgentMovement) + { + // Send ack straight away to let the viewer know that we got it. + SendAckImmediate(endPoint, packet.Header.Sequence); + + // We need to copy the endpoint so that it doesn't get changed when another thread reuses the + // buffer. + object[] array = new object[] { new IPEndPoint(endPoint.Address, endPoint.Port), packet }; + + Util.FireAndForget( + HandleCompleteMovementIntoRegion, array, "LLUDPServer.HandleCompleteMovementIntoRegion"); return; } // Determine which agent this packet came from IClientAPI client; - if (!m_scene.TryGetClient(endPoint, out client) || !(client is LLClientView)) + if (!Scene.TryGetClient(endPoint, out client) || !(client is LLClientView)) { //m_log.Debug("[LLUDPSERVER]: Received a " + packet.Type + " packet from an unrecognized source: " + address + " in " + m_scene.RegionInfo.RegionName); + + IncomingOrphanedPacketCount++; + + if ((IncomingOrphanedPacketCount % 10000) == 0) + m_log.WarnFormat( + "[LLUDPSERVER]: Received {0} orphaned packets so far. Last was from {1}", + IncomingOrphanedPacketCount, endPoint); + return; } @@ -1128,30 +1368,37 @@ namespace OpenSim.Region.ClientStack.LindenUDP #region ACK Receiving - // Handle appended ACKs - if (packet.Header.AppendedAcks && packet.Header.AckList != null) + if (udpClient.ProcessUnackedSends) { -// m_log.DebugFormat( -// "[LLUDPSERVER]: Handling {0} appended acks from {1} in {2}", -// packet.Header.AckList.Length, client.Name, m_scene.Name); + // Handle appended ACKs + if (packet.Header.AppendedAcks && packet.Header.AckList != null) + { + // m_log.DebugFormat( + // "[LLUDPSERVER]: Handling {0} appended acks from {1} in {2}", + // packet.Header.AckList.Length, client.Name, m_scene.Name); - for (int i = 0; i < packet.Header.AckList.Length; i++) - udpClient.NeedAcks.Acknowledge(packet.Header.AckList[i], now, packet.Header.Resent); - } + for (int i = 0; i < packet.Header.AckList.Length; i++) + udpClient.NeedAcks.Acknowledge(packet.Header.AckList[i], now, packet.Header.Resent); + } - // Handle PacketAck packets - if (packet.Type == PacketType.PacketAck) - { - PacketAckPacket ackPacket = (PacketAckPacket)packet; + // Handle PacketAck packets + if (packet.Type == PacketType.PacketAck) + { + PacketAckPacket ackPacket = (PacketAckPacket)packet; -// m_log.DebugFormat( -// "[LLUDPSERVER]: Handling {0} packet acks for {1} in {2}", -// ackPacket.Packets.Length, client.Name, m_scene.Name); + // m_log.DebugFormat( + // "[LLUDPSERVER]: Handling {0} packet acks for {1} in {2}", + // ackPacket.Packets.Length, client.Name, m_scene.Name); - for (int i = 0; i < ackPacket.Packets.Length; i++) - udpClient.NeedAcks.Acknowledge(ackPacket.Packets[i].ID, now, packet.Header.Resent); + for (int i = 0; i < ackPacket.Packets.Length; i++) + udpClient.NeedAcks.Acknowledge(ackPacket.Packets[i].ID, now, packet.Header.Resent); - // We don't need to do anything else with PacketAck packets + // We don't need to do anything else with PacketAck packets + return; + } + } + else if (packet.Type == PacketType.PacketAck) + { return; } @@ -1185,6 +1432,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP #region Incoming Packet Accounting + // We're not going to worry about interlock yet since its not currently critical that this total count + // is 100% correct + if (packet.Header.Resent) + IncomingPacketsResentCount++; + // Check the archive of received reliable packet IDs to see whether we already received this packet if (packet.Header.Reliable && !udpClient.PacketArchive.TryEnqueue(packet.Header.Sequence)) { @@ -1207,6 +1459,25 @@ namespace OpenSim.Region.ClientStack.LindenUDP LogPacketHeader(true, udpClient.CircuitCode, 0, packet.Type, (ushort)packet.Length); #endregion BinaryStats + if (packet.Type == PacketType.AgentUpdate) + { + if (DiscardInboundAgentUpdates) + return; + + ((LLClientView)client).TotalAgentUpdates++; + + AgentUpdatePacket agentUpdate = (AgentUpdatePacket)packet; + + LLClientView llClient = client as LLClientView; + if (agentUpdate.AgentData.SessionID != client.SessionId + || agentUpdate.AgentData.AgentID != client.AgentId + || !(llClient == null || llClient.CheckAgentUpdateSignificance(agentUpdate.AgentData)) ) + { + PacketPool.Instance.ReturnPacket(packet); + return; + } + } + #region Ping Check Handling if (packet.Type == PacketType.StartPingCheck) @@ -1266,8 +1537,34 @@ namespace OpenSim.Region.ClientStack.LindenUDP static object binStatsLogLock = new object(); static string binStatsDir = ""; + //for Aggregated In/Out BW logging + static bool m_aggregatedBWStats = false; + static long m_aggregatedBytesIn = 0; + static long m_aggregatedByestOut = 0; + static object aggBWStatsLock = new object(); + + public static long AggregatedLLUDPBytesIn + { + get { return m_aggregatedBytesIn; } + } + public static long AggregatedLLUDPBytesOut + { + get {return m_aggregatedByestOut;} + } + public static void LogPacketHeader(bool incoming, uint circuit, byte flags, PacketType packetType, ushort size) { + if (m_aggregatedBWStats) + { + lock (aggBWStatsLock) + { + if (incoming) + m_aggregatedBytesIn += size; + else + m_aggregatedByestOut += size; + } + } + if (!m_shouldCollectStats) return; // Binary logging format is TTTTTTTTCCCCFPPPSS, T=Time, C=Circuit, F=Flags, P=PacketType, S=size @@ -1344,7 +1641,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_log.DebugFormat( "[LLUDPSERVER]: Handling UseCircuitCode request for circuit {0} to {1} from IP {2}", - uccp.CircuitCode.Code, m_scene.RegionInfo.RegionName, endPoint); + uccp.CircuitCode.Code, Scene.RegionInfo.RegionName, endPoint); AuthenticateResponse sessionInfo; if (IsClientAuthorized(uccp, out sessionInfo)) @@ -1365,14 +1662,20 @@ namespace OpenSim.Region.ClientStack.LindenUDP // We only want to send initial data to new clients, not ones which are being converted from child to root. if (client != null) - client.SceneAgent.SendInitialDataToMe(); + { + AgentCircuitData aCircuit = Scene.AuthenticateHandler.GetAgentCircuitData(uccp.CircuitCode.Code); + bool tp = (aCircuit.teleportFlags > 0); + // Let's delay this for TP agents, otherwise the viewer doesn't know where to get resources from + if (!tp && !client.SceneAgent.SentInitialDataToClient) + client.SceneAgent.SendInitialDataToClient(); + } } else { // Don't create clients for unauthorized requesters. m_log.WarnFormat( "[LLUDPSERVER]: Ignoring connection request for {0} to {1} with unknown circuit code {2} from IP {3}", - uccp.CircuitCode.ID, m_scene.RegionInfo.RegionName, uccp.CircuitCode.Code, endPoint); + uccp.CircuitCode.ID, Scene.RegionInfo.RegionName, uccp.CircuitCode.Code, endPoint); } // m_log.DebugFormat( @@ -1392,6 +1695,116 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } + private void HandleCompleteMovementIntoRegion(object o) + { + IPEndPoint endPoint = null; + IClientAPI client = null; + + try + { + object[] array = (object[])o; + endPoint = (IPEndPoint)array[0]; + CompleteAgentMovementPacket packet = (CompleteAgentMovementPacket)array[1]; + + m_log.DebugFormat( + "[LLUDPSERVER]: Handling CompleteAgentMovement request from {0} in {1}", endPoint, Scene.Name); + + // Determine which agent this packet came from + // We need to wait here because in when using the OpenSimulator V2 teleport protocol to travel to a destination + // simulator with no existing child presence, the viewer (at least LL 3.3.4) will send UseCircuitCode + // and then CompleteAgentMovement immediately without waiting for an ack. As we are now handling these + // packets asynchronously, we need to account for this thread proceeding more quickly than the + // UseCircuitCode thread. + int count = 40; + while (count-- > 0) + { + if (Scene.TryGetClient(endPoint, out client)) + { + if (!client.IsActive) + { + // This check exists to catch a condition where the client has been closed by another thread + // but has not yet been removed from the client manager (and possibly a new connection has + // not yet been established). + m_log.DebugFormat( + "[LLUDPSERVER]: Received a CompleteAgentMovement from {0} for {1} in {2} but client is not active yet. Waiting.", + endPoint, client.Name, Scene.Name); + } + else if (client.SceneAgent == null) + { + // This check exists to catch a condition where the new client has been added to the client + // manager but the SceneAgent has not yet been set in Scene.AddNewAgent(). If we are too + // eager, then the new ScenePresence may not have registered a listener for this messsage + // before we try to process it. + // XXX: A better long term fix may be to add the SceneAgent before the client is added to + // the client manager + m_log.DebugFormat( + "[LLUDPSERVER]: Received a CompleteAgentMovement from {0} for {1} in {2} but client SceneAgent not set yet. Waiting.", + endPoint, client.Name, Scene.Name); + } + else + { + break; + } + } + else + { + m_log.DebugFormat( + "[LLUDPSERVER]: Received a CompleteAgentMovement from {0} in {1} but no client exists yet. Waiting.", + endPoint, Scene.Name); + } + + Thread.Sleep(200); + } + + if (client == null) + { + m_log.DebugFormat( + "[LLUDPSERVER]: No client found for CompleteAgentMovement from {0} in {1} after wait. Dropping.", + endPoint, Scene.Name); + + return; + } + else if (!client.IsActive || client.SceneAgent == null) + { + // This check exists to catch a condition where the client has been closed by another thread + // but has not yet been removed from the client manager. + // The packet could be simply ignored but it is useful to know if this condition occurred for other debugging + // purposes. + m_log.DebugFormat( + "[LLUDPSERVER]: Received a CompleteAgentMovement from {0} for {1} in {2} but client is not active after wait. Dropping.", + endPoint, client.Name, Scene.Name); + + return; + } + + IncomingPacket incomingPacket1; + + // Inbox insertion + if (UsePools) + { + incomingPacket1 = m_incomingPacketPool.GetObject(); + incomingPacket1.Client = (LLClientView)client; + incomingPacket1.Packet = packet; + } + else + { + incomingPacket1 = new IncomingPacket((LLClientView)client, packet); + } + + packetInbox.Enqueue(incomingPacket1); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[LLUDPSERVER]: CompleteAgentMovement handling from endpoint {0}, client {1} {2} failed. Exception {3}{4}", + endPoint != null ? endPoint.ToString() : "n/a", + client != null ? client.Name : "unknown", + client != null ? client.AgentId.ToString() : "unknown", + e.Message, + e.StackTrace); + } + } + /// /// Send an ack immediately to the given endpoint. /// @@ -1454,12 +1867,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP // consistently, this lock could probably be removed. lock (this) { - if (!m_scene.TryGetClient(agentID, out client)) + if (!Scene.TryGetClient(agentID, out client)) { - LLUDPClient udpClient = new LLUDPClient(this, ThrottleRates, m_throttle, circuitCode, agentID, remoteEndPoint, m_defaultRTO, m_maxRTO); + LLUDPClient udpClient = new LLUDPClient(this, ThrottleRates, Throttle, circuitCode, agentID, remoteEndPoint, m_defaultRTO, m_maxRTO); - client = new LLClientView(m_scene, this, udpClient, sessionInfo, agentID, sessionID, circuitCode); + client = new LLClientView(Scene, this, udpClient, sessionInfo, agentID, sessionID, circuitCode); client.OnLogout += LogoutHandler; + client.DebugPacketLevel = DefaultClientPacketDebugLevel; ((LLClientView)client).DisableFacelights = m_disableFacelights; @@ -1478,25 +1892,28 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// regular client pings. /// /// - private void DeactivateClientDueToTimeout(LLClientView client) + /// + private void DeactivateClientDueToTimeout(LLClientView client, int timeoutTicks) { lock (client.CloseSyncLock) - { + { + ClientLogoutsDueToNoReceives++; + m_log.WarnFormat( - "[LLUDPSERVER]: Ack timeout, disconnecting {0} agent for {1} in {2}", - client.SceneAgent.IsChildAgent ? "child" : "root", client.Name, m_scene.RegionInfo.RegionName); - - StatsManager.SimExtraStats.AddAbnormalClientThreadTermination(); + "[LLUDPSERVER]: No packets received from {0} agent of {1} for {2}ms in {3}. Disconnecting.", + client.SceneAgent.IsChildAgent ? "child" : "root", client.Name, timeoutTicks, Scene.Name); if (!client.SceneAgent.IsChildAgent) - client.Kick("Simulator logged you out due to connection timeout"); - - client.CloseWithoutChecks(); + client.Kick("Simulator logged you out due to connection timeout."); } + + Scene.CloseAgent(client.AgentId, true); } private void IncomingPacketHandler() { + Thread.CurrentThread.Priority = ThreadPriority.Highest; + // Set this culture for the thread that incoming packets are received // on to en-US to avoid number parsing issues Culture.SetCurrentCulture(); @@ -1507,6 +1924,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP { IncomingPacket incomingPacket = null; + /* // HACK: This is a test to try and rate limit packet handling on Mono. // If it works, a more elegant solution can be devised if (Util.FireAndForgetCount() < 2) @@ -1514,6 +1932,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP //m_log.Debug("[LLUDPSERVER]: Incoming packet handler is sleeping"); Thread.Sleep(30); } + */ if (packetInbox.Dequeue(100, ref incomingPacket)) { @@ -1540,6 +1959,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP private void OutgoingPacketHandler() { + Thread.CurrentThread.Priority = ThreadPriority.Highest; + // Set this culture for the thread that outgoing packets are sent // on to en-US to avoid number parsing issues Culture.SetCurrentCulture(); @@ -1602,14 +2023,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Handle outgoing packets, resends, acknowledgements, and pings for each // client. m_packetSent will be set to true if a packet is sent - m_scene.ForEachClient(clientPacketHandler); + Scene.ForEachClient(clientPacketHandler); m_currentOutgoingClient = null; // If nothing was sent, sleep for the minimum amount of time before a // token bucket could get more tokens + //if (!m_packetSent) + // Thread.Sleep((int)TickCountResolution); + // + // Instead, now wait for data present to be explicitly signalled. Evidence so far is that with + // modern mono it reduces CPU base load since there is no more continuous polling. if (!m_packetSent) - Thread.Sleep((int)TickCountResolution); + m_dataPresentEvent.WaitOne(100); Watchdog.UpdateThread(); } @@ -1635,7 +2061,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (udpClient.IsConnected) { - if (m_resendUnacked) + if (udpClient.ProcessUnackedSends && m_resendUnacked) HandleUnacked(llClient); if (m_sendAcks) @@ -1764,7 +2190,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP watch1.Reset(); // reuse this -- it's every ~100ms - if (m_scene.EmergencyMonitoring && nticks % 100 == 0) + if (Scene.EmergencyMonitoring && nticks % 100 == 0) { m_log.InfoFormat("[LLUDPSERVER]: avg processing ticks: {0} avg unacked: {1} avg acks: {2} avg ping: {3} avg dequeue: {4} (TickCountRes: {5} sent: {6} notsent: {7})", avgProcessingTicks, avgResendUnackedTicks, avgSendAcksTicks, avgSendPingTicks, avgDequeueTicks, TickCountResolution, npacksSent, npackNotSent); @@ -1813,7 +2239,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP { m_log.DebugFormat( "[LLUDPSERVER]: Dropped incoming {0} for dead client {1} in {2}", - packet.Type, client.Name, m_scene.RegionInfo.RegionName); + packet.Type, client.Name, Scene.RegionInfo.RegionName); } IncomingPacketsProcessed++; @@ -1826,8 +2252,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (!client.IsLoggingOut) { client.IsLoggingOut = true; - client.Close(); + Scene.CloseAgent(client.AgentId, false); } } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServerCommands.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServerCommands.cs new file mode 100644 index 0000000..ac6c0b4 --- /dev/null +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServerCommands.cs @@ -0,0 +1,901 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using NDesk.Options; +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.ClientStack.LindenUDP +{ + public class LLUDPServerCommands + { + private ICommandConsole m_console; + private LLUDPServer m_udpServer; + + public LLUDPServerCommands(ICommandConsole console, LLUDPServer udpServer) + { + m_console = console; + m_udpServer = udpServer; + } + + public void Register() + { + m_console.Commands.AddCommand( + "Comms", false, "show server throttles", + "show server throttles", + "Show information about server throttles", + HandleShowServerThrottlesCommand); + + m_console.Commands.AddCommand( + "Debug", false, "debug lludp packet", + "debug lludp packet [--default | --all] [ ]", + "Turn on packet debugging. This logs information when the client stack hands a processed packet off to downstream code or when upstream code first requests that a certain packet be sent.", + "If level > 255 then all incoming and outgoing packets are logged.\n" + + "If level <= 255 then incoming AgentUpdate and outgoing SimStats and SimulatorViewerTimeMessage packets are not logged.\n" + + "If level <= 200 then incoming RequestImage and outgoing ImagePacket, ImageData, LayerData and CoarseLocationUpdate packets are not logged.\n" + + "If level <= 100 then incoming ViewerEffect and AgentAnimation and outgoing ViewerEffect and AvatarAnimation packets are not logged.\n" + + "If level <= 50 then outgoing ImprovedTerseObjectUpdate packets are not logged.\n" + + "If level <= 0 then no packets are logged.\n" + + "If --default is specified then the level becomes the default logging level for all subsequent agents.\n" + + "If --all is specified then the level becomes the default logging level for all current and subsequent agents.\n" + + "In these cases, you cannot also specify an avatar name.\n" + + "If an avatar name is given then only packets from that avatar are logged.", + HandlePacketCommand); + + m_console.Commands.AddCommand( + "Debug", false, "debug lludp data out", + "debug lludp data out \"", + "Turn on debugging for final outgoing data to the given user's client.", + "This operates at a much lower level than the packet command and prints out available details when the data is actually sent.\n" + + "If level > 0 then information about all outgoing UDP data for this avatar is logged.\n" + + "If level <= 0 then no information about outgoing UDP data for this avatar is logged.", + HandleDataCommand); + + m_console.Commands.AddCommand( + "Debug", false, "debug lludp drop", + "debug lludp drop ", + "Drop all in or outbound packets that match the given name", + "For test purposes.", + HandleDropCommand); + + m_console.Commands.AddCommand( + "Debug", + false, + "debug lludp start", + "debug lludp start ", + "Control LLUDP packet processing.", + "No effect if packet processing has already started.\n" + + "in - start inbound processing.\n" + + "out - start outbound processing.\n" + + "all - start in and outbound processing.\n", + HandleStartCommand); + + m_console.Commands.AddCommand( + "Debug", + false, + "debug lludp stop", + "debug lludp stop ", + "Stop LLUDP packet processing.", + "No effect if packet processing has already stopped.\n" + + "in - stop inbound processing.\n" + + "out - stop outbound processing.\n" + + "all - stop in and outbound processing.\n", + HandleStopCommand); + + m_console.Commands.AddCommand( + "Debug", + false, + "debug lludp pool", + "debug lludp pool ", + "Turn object pooling within the lludp component on or off.", + HandlePoolCommand); + + m_console.Commands.AddCommand( + "Debug", + false, + "debug lludp status", + "debug lludp status", + "Return status of LLUDP packet processing.", + HandleStatusCommand); + + m_console.Commands.AddCommand( + "Debug", + false, + "debug lludp throttles log", + "debug lludp throttles log [ ]", + "Change debug logging level for throttles.", + "If level >= 0 then throttle debug logging is performed.\n" + + "If level <= 0 then no throttle debug logging is performed.", + HandleThrottleCommand); + + m_console.Commands.AddCommand( + "Debug", + false, + "debug lludp throttles get", + "debug lludp throttles get [ ]", + "Return debug settings for throttles.", + "adaptive - true/false, controls adaptive throttle setting.\n" + + "request - request drip rate in kbps.\n" + + "max - the max kbps throttle allowed for the specified existing clients. Use 'debug lludp get new-client-throttle-max' to see the setting for new clients.\n", + HandleThrottleGetCommand); + + m_console.Commands.AddCommand( + "Debug", + false, + "debug lludp throttles set", + "debug lludp throttles set [ ]", + "Set a throttle parameter for the given client.", + "adaptive - true/false, controls adaptive throttle setting.\n" + + "current - current drip rate in kbps.\n" + + "request - requested drip rate in kbps.\n" + + "max - the max kbps throttle allowed for the specified existing clients. Use 'debug lludp set new-client-throttle-max' to change the settings for new clients.\n", + HandleThrottleSetCommand); + + m_console.Commands.AddCommand( + "Debug", + false, + "debug lludp get", + "debug lludp get", + "Get debug parameters for the server.", + "max-scene-throttle - the current max cumulative kbps provided for this scene to clients.\n" + + "max-new-client-throttle - the max kbps throttle allowed to new clients. Use 'debug lludp throttles get max' to see the settings for existing clients.", + HandleGetCommand); + + m_console.Commands.AddCommand( + "Debug", + false, + "debug lludp set", + "debug lludp set ", + "Set a parameter for the server.", + "max-scene-throttle - the current max cumulative kbps provided for this scene to clients.\n" + + "max-new-client-throttle - the max kbps throttle allowed to each new client. Use 'debug lludp throttles set max' to set for existing clients.", + HandleSetCommand); + + m_console.Commands.AddCommand( + "Debug", + false, + "debug lludp toggle agentupdate", + "debug lludp toggle agentupdate", + "Toggle whether agentupdate packets are processed or simply discarded.", + HandleAgentUpdateCommand); + + MainConsole.Instance.Commands.AddCommand( + "Debug", + false, + "debug lludp oqre", + "debug lludp oqre ", + "Start, stop or get status of OutgoingQueueRefillEngine.", + "If stopped then refill requests are processed directly via the threadpool.", + HandleOqreCommand); + + m_console.Commands.AddCommand( + "Debug", + false, + "debug lludp client get", + "debug lludp client get [ ]", + "Get debug parameters for the client. If no name is given then all client information is returned.", + "process-unacked-sends - Do we take action if a sent reliable packet has not been acked.", + HandleClientGetCommand); + + m_console.Commands.AddCommand( + "Debug", + false, + "debug lludp client set", + "debug lludp client set [ ]", + "Set a debug parameter for a particular client. If no name is given then the value is set on all clients.", + "process-unacked-sends - Do we take action if a sent reliable packet has not been acked.", + HandleClientSetCommand); + } + + private void HandleShowServerThrottlesCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + m_console.OutputFormat("Throttles for {0}", m_udpServer.Scene.Name); + ConsoleDisplayList cdl = new ConsoleDisplayList(); + cdl.AddRow("Adaptive throttles", m_udpServer.ThrottleRates.AdaptiveThrottlesEnabled); + + long maxSceneDripRate = m_udpServer.Throttle.MaxDripRate; + cdl.AddRow( + "Max scene throttle", + maxSceneDripRate != 0 ? string.Format("{0} kbps", maxSceneDripRate * 8 / 1000) : "unset"); + + int maxClientDripRate = m_udpServer.ThrottleRates.Total; + cdl.AddRow( + "Max new client throttle", + maxClientDripRate != 0 ? string.Format("{0} kbps", maxClientDripRate * 8 / 1000) : "unset"); + + m_console.Output(cdl.ToString()); + + m_console.OutputFormat("{0}\n", GetServerThrottlesReport(m_udpServer)); + } + + private string GetServerThrottlesReport(LLUDPServer udpServer) + { + StringBuilder report = new StringBuilder(); + + report.AppendFormat( + "{0,7} {1,8} {2,7} {3,7} {4,7} {5,7} {6,9} {7,7}\n", + "Total", + "Resend", + "Land", + "Wind", + "Cloud", + "Task", + "Texture", + "Asset"); + + report.AppendFormat( + "{0,7} {1,8} {2,7} {3,7} {4,7} {5,7} {6,9} {7,7}\n", + "kb/s", + "kb/s", + "kb/s", + "kb/s", + "kb/s", + "kb/s", + "kb/s", + "kb/s"); + + ThrottleRates throttleRates = udpServer.ThrottleRates; + report.AppendFormat( + "{0,7} {1,8} {2,7} {3,7} {4,7} {5,7} {6,9} {7,7}", + (throttleRates.Total * 8) / 1000, + (throttleRates.Resend * 8) / 1000, + (throttleRates.Land * 8) / 1000, + (throttleRates.Wind * 8) / 1000, + (throttleRates.Cloud * 8) / 1000, + (throttleRates.Task * 8) / 1000, + (throttleRates.Texture * 8) / 1000, + (throttleRates.Asset * 8) / 1000); + + return report.ToString(); + } + + protected string GetColumnEntry(string entry, int maxLength, int columnPadding) + { + return string.Format( + "{0,-" + maxLength + "}{1,-" + columnPadding + "}", + entry.Length > maxLength ? entry.Substring(0, maxLength) : entry, + ""); + } + + private void HandleDataCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + if (args.Length != 7) + { + MainConsole.Instance.OutputFormat("Usage: debug lludp data out "); + return; + } + + int level; + if (!ConsoleUtil.TryParseConsoleInt(MainConsole.Instance, args[4], out level)) + return; + + string firstName = args[5]; + string lastName = args[6]; + + m_udpServer.Scene.ForEachScenePresence(sp => + { + if (sp.Firstname == firstName && sp.Lastname == lastName) + { + MainConsole.Instance.OutputFormat( + "Data debug for {0} ({1}) set to {2} in {3}", + sp.Name, sp.IsChildAgent ? "child" : "root", level, m_udpServer.Scene.Name); + + ((LLClientView)sp.ControllingClient).UDPClient.DebugDataOutLevel = level; + } + }); + } + + private void HandleThrottleCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + bool all = args.Length == 5; + bool one = args.Length == 7; + + if (!all && !one) + { + MainConsole.Instance.OutputFormat( + "Usage: debug lludp throttles log [ ]"); + return; + } + + int level; + if (!ConsoleUtil.TryParseConsoleInt(MainConsole.Instance, args[4], out level)) + return; + + string firstName = null; + string lastName = null; + + if (one) + { + firstName = args[5]; + lastName = args[6]; + } + + m_udpServer.Scene.ForEachScenePresence(sp => + { + if (all || (sp.Firstname == firstName && sp.Lastname == lastName)) + { + MainConsole.Instance.OutputFormat( + "Throttle log level for {0} ({1}) set to {2} in {3}", + sp.Name, sp.IsChildAgent ? "child" : "root", level, m_udpServer.Scene.Name); + + ((LLClientView)sp.ControllingClient).UDPClient.ThrottleDebugLevel = level; + } + }); + } + + private void HandleThrottleSetCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + bool all = args.Length == 6; + bool one = args.Length == 8; + + if (!all && !one) + { + MainConsole.Instance.OutputFormat( + "Usage: debug lludp throttles set [ ]"); + return; + } + + string param = args[4]; + string rawValue = args[5]; + + string firstName = null; + string lastName = null; + + if (one) + { + firstName = args[6]; + lastName = args[7]; + } + + if (param == "adaptive") + { + bool newValue; + if (!ConsoleUtil.TryParseConsoleBool(MainConsole.Instance, rawValue, out newValue)) + return; + + m_udpServer.Scene.ForEachScenePresence(sp => + { + if (all || (sp.Firstname == firstName && sp.Lastname == lastName)) + { + MainConsole.Instance.OutputFormat( + "Setting param {0} to {1} for {2} ({3}) in {4}", + param, newValue, sp.Name, sp.IsChildAgent ? "child" : "root", m_udpServer.Scene.Name); + + LLUDPClient udpClient = ((LLClientView)sp.ControllingClient).UDPClient; + udpClient.FlowThrottle.AdaptiveEnabled = newValue; + // udpClient.FlowThrottle.MaxDripRate = 0; + // udpClient.FlowThrottle.AdjustedDripRate = 0; + } + }); + } + else if (param == "request") + { + int newValue; + if (!ConsoleUtil.TryParseConsoleInt(MainConsole.Instance, rawValue, out newValue)) + return; + + int newCurrentThrottleKbps = newValue * 1000 / 8; + + m_udpServer.Scene.ForEachScenePresence(sp => + { + if (all || (sp.Firstname == firstName && sp.Lastname == lastName)) + { + MainConsole.Instance.OutputFormat( + "Setting param {0} to {1} for {2} ({3}) in {4}", + param, newValue, sp.Name, sp.IsChildAgent ? "child" : "root", m_udpServer.Scene.Name); + + LLUDPClient udpClient = ((LLClientView)sp.ControllingClient).UDPClient; + udpClient.FlowThrottle.RequestedDripRate = newCurrentThrottleKbps; + } + }); + } + else if (param == "max") + { + int newValue; + if (!ConsoleUtil.TryParseConsoleInt(MainConsole.Instance, rawValue, out newValue)) + return; + + int newThrottleMaxKbps = newValue * 1000 / 8; + + m_udpServer.Scene.ForEachScenePresence(sp => + { + if (all || (sp.Firstname == firstName && sp.Lastname == lastName)) + { + MainConsole.Instance.OutputFormat( + "Setting param {0} to {1} for {2} ({3}) in {4}", + param, newValue, sp.Name, sp.IsChildAgent ? "child" : "root", m_udpServer.Scene.Name); + + LLUDPClient udpClient = ((LLClientView)sp.ControllingClient).UDPClient; + udpClient.FlowThrottle.MaxDripRate = newThrottleMaxKbps; + } + }); + } + } + + private void HandleThrottleGetCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + bool all = args.Length == 4; + bool one = args.Length == 6; + + if (!all && !one) + { + MainConsole.Instance.OutputFormat( + "Usage: debug lludp throttles get [ ]"); + return; + } + + string firstName = null; + string lastName = null; + + if (one) + { + firstName = args[4]; + lastName = args[5]; + } + + m_udpServer.Scene.ForEachScenePresence(sp => + { + if (all || (sp.Firstname == firstName && sp.Lastname == lastName)) + { + m_console.OutputFormat( + "Status for {0} ({1}) in {2}", + sp.Name, sp.IsChildAgent ? "child" : "root", m_udpServer.Scene.Name); + + LLUDPClient udpClient = ((LLClientView)sp.ControllingClient).UDPClient; + + ConsoleDisplayList cdl = new ConsoleDisplayList(); + cdl.AddRow("adaptive", udpClient.FlowThrottle.AdaptiveEnabled); + cdl.AddRow("current", string.Format("{0} kbps", udpClient.FlowThrottle.DripRate * 8 / 1000)); + cdl.AddRow("request", string.Format("{0} kbps", udpClient.FlowThrottle.RequestedDripRate * 8 / 1000)); + cdl.AddRow("max", string.Format("{0} kbps", udpClient.FlowThrottle.MaxDripRate * 8 / 1000)); + + m_console.Output(cdl.ToString()); + } + }); + } + + private void HandleGetCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + m_console.OutputFormat("Debug settings for {0}", m_udpServer.Scene.Name); + ConsoleDisplayList cdl = new ConsoleDisplayList(); + + long maxSceneDripRate = m_udpServer.Throttle.MaxDripRate; + cdl.AddRow( + "max-scene-throttle", + maxSceneDripRate != 0 ? string.Format("{0} kbps", maxSceneDripRate * 8 / 1000) : "unset"); + + int maxClientDripRate = m_udpServer.ThrottleRates.Total; + cdl.AddRow( + "max-new-client-throttle", + maxClientDripRate != 0 ? string.Format("{0} kbps", maxClientDripRate * 8 / 1000) : "unset"); + + m_console.Output(cdl.ToString()); + } + + private void HandleSetCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + if (args.Length != 5) + { + MainConsole.Instance.OutputFormat("Usage: debug lludp set "); + return; + } + + string param = args[3]; + string rawValue = args[4]; + + int newValue; + + if (param == "max-scene-throttle") + { + if (!ConsoleUtil.TryParseConsoleInt(MainConsole.Instance, rawValue, out newValue)) + return; + + m_udpServer.Throttle.MaxDripRate = newValue * 1000 / 8; + } + else if (param == "max-new-client-throttle") + { + if (!ConsoleUtil.TryParseConsoleInt(MainConsole.Instance, rawValue, out newValue)) + return; + + m_udpServer.ThrottleRates.Total = newValue * 1000 / 8; + } + else + { + return; + } + + m_console.OutputFormat("{0} set to {1} in {2}", param, rawValue, m_udpServer.Scene.Name); + } + + private void HandleClientGetCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + if (args.Length != 4 && args.Length != 6) + { + MainConsole.Instance.OutputFormat("Usage: debug lludp client get [ ]"); + return; + } + + string name = null; + + if (args.Length == 6) + name = string.Format("{0} {1}", args[4], args[5]); + + m_udpServer.Scene.ForEachScenePresence( + sp => + { + if ((name == null || sp.Name == name) && sp.ControllingClient is LLClientView) + { + LLUDPClient udpClient = ((LLClientView)sp.ControllingClient).UDPClient; + + m_console.OutputFormat( + "Client debug parameters for {0} ({1}) in {2}", + sp.Name, sp.IsChildAgent ? "child" : "root", m_udpServer.Scene.Name); + + ConsoleDisplayList cdl = new ConsoleDisplayList(); + cdl.AddRow("process-unacked-sends", udpClient.ProcessUnackedSends); + + m_console.Output(cdl.ToString()); + } + }); + } + + private void HandleClientSetCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + if (args.Length != 6 && args.Length != 8) + { + MainConsole.Instance.OutputFormat("Usage: debug lludp client set [ ]"); + return; + } + + string param = args[4]; + string rawValue = args[5]; + + string name = null; + + if (args.Length == 8) + name = string.Format("{0} {1}", args[6], args[7]); + + if (param == "process-unacked-sends") + { + bool newValue; + + if (!ConsoleUtil.TryParseConsoleBool(MainConsole.Instance, rawValue, out newValue)) + return; + + m_udpServer.Scene.ForEachScenePresence( + sp => + { + if ((name == null || sp.Name == name) && sp.ControllingClient is LLClientView) + { + LLUDPClient udpClient = ((LLClientView)sp.ControllingClient).UDPClient; + udpClient.ProcessUnackedSends = newValue; + + m_console.OutputFormat("{0} set to {1} for {2} in {3}", param, newValue, sp.Name, m_udpServer.Scene.Name); + } + }); + } + } + + private void HandlePacketCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + bool setAsDefaultLevel = false; + bool setAll = false; + OptionSet optionSet = new OptionSet() + .Add("default", o => setAsDefaultLevel = (o != null)) + .Add("all", o => setAll = (o != null)); + List filteredArgs = optionSet.Parse(args); + + string name = null; + + if (filteredArgs.Count == 6) + { + if (!(setAsDefaultLevel || setAll)) + { + name = string.Format("{0} {1}", filteredArgs[4], filteredArgs[5]); + } + else + { + MainConsole.Instance.OutputFormat("ERROR: Cannot specify a user name when setting default/all logging level"); + return; + } + } + + if (filteredArgs.Count > 3) + { + int newDebug; + if (int.TryParse(filteredArgs[3], out newDebug)) + { + if (setAsDefaultLevel || setAll) + { + m_udpServer.DefaultClientPacketDebugLevel = newDebug; + + MainConsole.Instance.OutputFormat( + "Packet debug for {0} clients set to {1} in {2}", + (setAll ? "all" : "future"), m_udpServer.DefaultClientPacketDebugLevel, m_udpServer.Scene.Name); + + if (setAll) + { + m_udpServer.Scene.ForEachScenePresence(sp => + { + MainConsole.Instance.OutputFormat( + "Packet debug for {0} ({1}) set to {2} in {3}", + sp.Name, sp.IsChildAgent ? "child" : "root", newDebug, m_udpServer.Scene.Name); + + sp.ControllingClient.DebugPacketLevel = newDebug; + }); + } + } + else + { + m_udpServer.Scene.ForEachScenePresence(sp => + { + if (name == null || sp.Name == name) + { + MainConsole.Instance.OutputFormat( + "Packet debug for {0} ({1}) set to {2} in {3}", + sp.Name, sp.IsChildAgent ? "child" : "root", newDebug, m_udpServer.Scene.Name); + + sp.ControllingClient.DebugPacketLevel = newDebug; + } + }); + } + } + else + { + MainConsole.Instance.Output("Usage: debug lludp packet [--default | --all] 0..255 [ ]"); + } + } + } + + private void HandleDropCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + if (args.Length != 6) + { + MainConsole.Instance.Output("Usage: debug lludp drop "); + return; + } + + string direction = args[3]; + string subCommand = args[4]; + string packetName = args[5]; + + if (subCommand == "add") + { + MainConsole.Instance.OutputFormat( + "Adding packet {0} to {1} drop list for all connections in {2}", + direction, packetName, m_udpServer.Scene.Name); + + m_udpServer.Scene.ForEachScenePresence( + sp => + { + LLClientView llcv = (LLClientView)sp.ControllingClient; + + if (direction == "in") + llcv.AddInPacketToDropSet(packetName); + else if (direction == "out") + llcv.AddOutPacketToDropSet(packetName); + } + ); + } + else if (subCommand == "remove") + { + MainConsole.Instance.OutputFormat( + "Removing packet {0} from {1} drop list for all connections in {2}", + direction, packetName, m_udpServer.Scene.Name); + + m_udpServer.Scene.ForEachScenePresence( + sp => + { + LLClientView llcv = (LLClientView)sp.ControllingClient; + + if (direction == "in") + llcv.RemoveInPacketFromDropSet(packetName); + else if (direction == "out") + llcv.RemoveOutPacketFromDropSet(packetName); + } + ); + } + } + + private void HandleStartCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + if (args.Length != 4) + { + MainConsole.Instance.Output("Usage: debug lludp start "); + return; + } + + string subCommand = args[3]; + + if (subCommand == "in" || subCommand == "all") + m_udpServer.StartInbound(); + + if (subCommand == "out" || subCommand == "all") + m_udpServer.StartOutbound(); + } + + private void HandleStopCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + if (args.Length != 4) + { + MainConsole.Instance.Output("Usage: debug lludp stop "); + return; + } + + string subCommand = args[3]; + + if (subCommand == "in" || subCommand == "all") + m_udpServer.StopInbound(); + + if (subCommand == "out" || subCommand == "all") + m_udpServer.StopOutbound(); + } + + private void HandlePoolCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + if (args.Length != 4) + { + MainConsole.Instance.Output("Usage: debug lludp pool "); + return; + } + + string enabled = args[3]; + + if (enabled == "on") + { + if (m_udpServer.EnablePools()) + { + m_udpServer.EnablePoolStats(); + MainConsole.Instance.OutputFormat("Packet pools enabled on {0}", m_udpServer.Scene.Name); + } + } + else if (enabled == "off") + { + if (m_udpServer.DisablePools()) + { + m_udpServer.DisablePoolStats(); + MainConsole.Instance.OutputFormat("Packet pools disabled on {0}", m_udpServer.Scene.Name); + } + } + else + { + MainConsole.Instance.Output("Usage: debug lludp pool "); + } + } + + private void HandleAgentUpdateCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + m_udpServer.DiscardInboundAgentUpdates = !m_udpServer.DiscardInboundAgentUpdates; + + MainConsole.Instance.OutputFormat( + "Discard AgentUpdates now {0} for {1}", m_udpServer.DiscardInboundAgentUpdates, m_udpServer.Scene.Name); + } + + private void HandleStatusCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + MainConsole.Instance.OutputFormat( + "IN LLUDP packet processing for {0} is {1}", m_udpServer.Scene.Name, m_udpServer.IsRunningInbound ? "enabled" : "disabled"); + + MainConsole.Instance.OutputFormat( + "OUT LLUDP packet processing for {0} is {1}", m_udpServer.Scene.Name, m_udpServer.IsRunningOutbound ? "enabled" : "disabled"); + + MainConsole.Instance.OutputFormat("LLUDP pools in {0} are {1}", m_udpServer.Scene.Name, m_udpServer.UsePools ? "on" : "off"); + + MainConsole.Instance.OutputFormat( + "Packet debug level for new clients is {0}", m_udpServer.DefaultClientPacketDebugLevel); + } + + private void HandleOqreCommand(string module, string[] args) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene) + return; + + if (args.Length != 4) + { + MainConsole.Instance.Output("Usage: debug lludp oqre "); + return; + } + + string subCommand = args[3]; + + if (subCommand == "stop") + { + m_udpServer.OqrEngine.Stop(); + MainConsole.Instance.OutputFormat("Stopped OQRE for {0}", m_udpServer.Scene.Name); + } + else if (subCommand == "start") + { + m_udpServer.OqrEngine.Start(); + MainConsole.Instance.OutputFormat("Started OQRE for {0}", m_udpServer.Scene.Name); + } + else if (subCommand == "status") + { + MainConsole.Instance.OutputFormat("OQRE in {0}", m_udpServer.Scene.Name); + MainConsole.Instance.OutputFormat("Running: {0}", m_udpServer.OqrEngine.IsRunning); + MainConsole.Instance.OutputFormat( + "Requests waiting: {0}", + m_udpServer.OqrEngine.IsRunning ? m_udpServer.OqrEngine.JobsWaiting.ToString() : "n/a"); + } + else + { + MainConsole.Instance.OutputFormat("Unrecognized OQRE subcommand {0}", subCommand); + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs b/OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs index f143c32..f62dc15 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs @@ -78,6 +78,92 @@ namespace OpenMetaverse public bool IsRunningOutbound { get; private set; } /// + /// Number of UDP receives. + /// + public int UdpReceives { get; private set; } + + /// + /// Number of UDP sends + /// + public int UdpSends { get; private set; } + + /// + /// Number of receives over which to establish a receive time average. + /// + private readonly static int s_receiveTimeSamples = 500; + + /// + /// Current number of samples taken to establish a receive time average. + /// + private int m_currentReceiveTimeSamples; + + /// + /// Cumulative receive time for the sample so far. + /// + private int m_receiveTicksInCurrentSamplePeriod; + + /// + /// The average time taken for each require receive in the last sample. + /// + public float AverageReceiveTicksForLastSamplePeriod { get; private set; } + + #region PacketDropDebugging + /// + /// For debugging purposes only... random number generator for dropping + /// outbound packets. + /// + private Random m_dropRandomGenerator = new Random(); + + /// + /// For debugging purposes only... parameters for a simplified + /// model of packet loss with bursts, overall drop rate should + /// be roughly 1 - m_dropLengthProbability / (m_dropProbabiliy + m_dropLengthProbability) + /// which is about 1% for parameters 0.0015 and 0.15 + /// + private double m_dropProbability = 0.0030; + private double m_dropLengthProbability = 0.15; + private bool m_dropState = false; + + /// + /// For debugging purposes only... parameters to control the time + /// duration over which packet loss bursts can occur, if no packets + /// have been sent for m_dropResetTicks milliseconds, then reset the + /// state of the packet dropper to its default. + /// + private int m_dropLastTick = 0; + private int m_dropResetTicks = 500; + + /// + /// Debugging code used to simulate dropped packets with bursts + /// + private bool DropOutgoingPacket() + { + double rnum = m_dropRandomGenerator.NextDouble(); + + // if the connection has been idle for awhile (more than m_dropResetTicks) then + // reset the state to the default state, don't continue a burst + int curtick = Util.EnvironmentTickCount(); + if (Util.EnvironmentTickCountSubtract(curtick, m_dropLastTick) > m_dropResetTicks) + m_dropState = false; + + m_dropLastTick = curtick; + + // if we are dropping packets, then the probability of dropping + // this packet is the probability that we stay in the burst + if (m_dropState) + { + m_dropState = (rnum < (1.0 - m_dropLengthProbability)) ? true : false; + } + else + { + m_dropState = (rnum < m_dropProbability) ? true : false; + } + + return m_dropState; + } + #endregion PacketDropDebugging + + /// /// Default constructor /// /// Local IP address to bind the server to @@ -87,6 +173,10 @@ namespace OpenMetaverse { m_localBindAddress = bindAddress; m_udpPort = port; + + // for debugging purposes only, initializes the random number generator + // used for simulating packet loss + // m_dropRandomGenerator = new Random(); } /// @@ -105,12 +195,14 @@ namespace OpenMetaverse /// manner (not throwing an exception when the remote side resets the /// connection). This call is ignored on Mono where the flag is not /// necessary - public void StartInbound(int recvBufferSize, bool asyncPacketHandling) + public virtual void StartInbound(int recvBufferSize, bool asyncPacketHandling) { m_asyncPacketHandling = asyncPacketHandling; if (!IsRunningInbound) { + m_log.DebugFormat("[UDPBASE]: Starting inbound UDP loop"); + const int SIO_UDP_CONNRESET = -1744830452; IPEndPoint ipep = new IPEndPoint(m_localBindAddress, m_udpPort); @@ -126,6 +218,17 @@ namespace OpenMetaverse try { + if (m_udpSocket.Ttl < 128) + { + m_udpSocket.Ttl = 128; + } + } + catch (SocketException) + { + m_log.Debug("[UDPBASE]: Failed to increase default TTL"); + } + try + { // This udp socket flag is not supported under mono, // so we'll catch the exception and continue m_udpSocket.IOControl(SIO_UDP_CONNRESET, new byte[] { 0 }, null); @@ -136,6 +239,12 @@ namespace OpenMetaverse m_log.Debug("[UDPBASE]: SIO_UDP_CONNRESET flag not supported on this platform, ignoring"); } + // On at least Mono 3.2.8, multiple UDP sockets can bind to the same port by default. At the moment + // we never want two regions to listen on the same port as they cannot demultiplex each other's messages, + // leading to a confusing bug. + // By default, Windows does not allow two sockets to bind to the same port. + m_udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); + if (recvBufferSize != 0) m_udpSocket.ReceiveBufferSize = recvBufferSize; @@ -153,30 +262,32 @@ namespace OpenMetaverse /// /// Start outbound UDP packet handling. /// - public void StartOutbound() + public virtual void StartOutbound() { + m_log.DebugFormat("[UDPBASE]: Starting outbound UDP loop"); + IsRunningOutbound = true; } - public void StopInbound() + public virtual void StopInbound() { if (IsRunningInbound) { - // wait indefinitely for a writer lock. Once this is called, the .NET runtime - // will deny any more reader locks, in effect blocking all other send/receive - // threads. Once we have the lock, we set IsRunningInbound = false to inform the other - // threads that the socket is closed. + m_log.DebugFormat("[UDPBASE]: Stopping inbound UDP loop"); + IsRunningInbound = false; m_udpSocket.Close(); } } - public void StopOutbound() + public virtual void StopOutbound() { + m_log.DebugFormat("[UDPBASE]: Stopping outbound UDP loop"); + IsRunningOutbound = false; } - protected virtual bool EnablePools() + public virtual bool EnablePools() { if (!UsePools) { @@ -190,7 +301,7 @@ namespace OpenMetaverse return false; } - protected virtual bool DisablePools() + public virtual bool DisablePools() { if (UsePools) { @@ -261,7 +372,16 @@ namespace OpenMetaverse m_log.Warn("[UDPBASE]: Salvaged the UDP listener on port " + m_udpPort); } } - catch (ObjectDisposedException) { } + catch (ObjectDisposedException e) + { + m_log.Error( + string.Format("[UDPBASE]: Error processing UDP begin receive {0}. Exception ", UdpReceives), e); + } + catch (Exception e) + { + m_log.Error( + string.Format("[UDPBASE]: Error processing UDP begin receive {0}. Exception ", UdpReceives), e); + } } } @@ -271,17 +391,21 @@ namespace OpenMetaverse // to AsyncBeginReceive if (IsRunningInbound) { + UdpReceives++; + // Asynchronous mode will start another receive before the // callback for this packet is even fired. Very parallel :-) if (m_asyncPacketHandling) AsyncBeginReceive(); - // get the buffer that was created in AsyncBeginReceive - // this is the received data - UDPPacketBuffer buffer = (UDPPacketBuffer)iar.AsyncState; - try { + // get the buffer that was created in AsyncBeginReceive + // this is the received data + UDPPacketBuffer buffer = (UDPPacketBuffer)iar.AsyncState; + + int startTick = Util.EnvironmentTickCount(); + // get the length of data actually read from the socket, store it with the // buffer buffer.DataLength = m_udpSocket.EndReceiveFrom(iar, ref buffer.RemoteEndPoint); @@ -289,9 +413,42 @@ namespace OpenMetaverse // call the abstract method PacketReceived(), passing the buffer that // has just been filled from the socket read. PacketReceived(buffer); + + // If more than one thread can be calling AsyncEndReceive() at once (e.g. if m_asyncPacketHandler) + // then a particular stat may be inaccurate due to a race condition. We won't worry about this + // since this should be rare and won't cause a runtime problem. + if (m_currentReceiveTimeSamples >= s_receiveTimeSamples) + { + AverageReceiveTicksForLastSamplePeriod + = (float)m_receiveTicksInCurrentSamplePeriod / s_receiveTimeSamples; + + m_receiveTicksInCurrentSamplePeriod = 0; + m_currentReceiveTimeSamples = 0; + } + else + { + m_receiveTicksInCurrentSamplePeriod += Util.EnvironmentTickCountSubtract(startTick); + m_currentReceiveTimeSamples++; + } + } + catch (SocketException se) + { + m_log.Error( + string.Format( + "[UDPBASE]: Error processing UDP end receive {0}, socket error code {1}. Exception ", + UdpReceives, se.ErrorCode), + se); + } + catch (ObjectDisposedException e) + { + m_log.Error( + string.Format("[UDPBASE]: Error processing UDP end receive {0}. Exception ", UdpReceives), e); + } + catch (Exception e) + { + m_log.Error( + string.Format("[UDPBASE]: Error processing UDP end receive {0}. Exception ", UdpReceives), e); } - catch (SocketException) { } - catch (ObjectDisposedException) { } finally { // if (UsePools) @@ -302,14 +459,19 @@ namespace OpenMetaverse if (!m_asyncPacketHandling) AsyncBeginReceive(); } - } } public void AsyncBeginSend(UDPPacketBuffer buf) { - if (IsRunningOutbound) - { +// if (IsRunningOutbound) +// { + + // This is strictly for debugging purposes to simulate dropped + // packets when testing throttles & retransmission code + // if (DropOutgoingPacket()) + // return; + try { m_udpSocket.BeginSendTo( @@ -323,7 +485,7 @@ namespace OpenMetaverse } catch (SocketException) { } catch (ObjectDisposedException) { } - } +// } } void AsyncEndSend(IAsyncResult result) @@ -332,6 +494,8 @@ namespace OpenMetaverse { // UDPPacketBuffer buf = (UDPPacketBuffer)result.AsyncState; m_udpSocket.EndSendTo(result); + + UdpSends++; } catch (SocketException) { } catch (ObjectDisposedException) { } diff --git a/OpenSim/Region/ClientStack/Linden/UDP/PacketPool.cs b/OpenSim/Region/ClientStack/Linden/UDP/PacketPool.cs index 1fdc410..5a2bcee 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/PacketPool.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/PacketPool.cs @@ -145,39 +145,32 @@ namespace OpenSim.Region.ClientStack.LindenUDP return packet; } - // private byte[] decoded_header = new byte[10]; private static PacketType GetType(byte[] bytes) { - byte[] decoded_header = new byte[10 + 8]; ushort id; PacketFrequency freq; + bool isZeroCoded = (bytes[0] & Helpers.MSG_ZEROCODED) != 0; - if ((bytes[0] & Helpers.MSG_ZEROCODED) != 0) + if (bytes[6] == 0xFF) { - Helpers.ZeroDecode(bytes, 16, decoded_header); - } - else - { - Buffer.BlockCopy(bytes, 0, decoded_header, 0, 10); - } - - if (decoded_header[6] == 0xFF) - { - if (decoded_header[7] == 0xFF) + if (bytes[7] == 0xFF) { - id = (ushort) ((decoded_header[8] << 8) + decoded_header[9]); freq = PacketFrequency.Low; + if (isZeroCoded && bytes[8] == 0) + id = bytes[10]; + else + id = (ushort)((bytes[8] << 8) + bytes[9]); } else { - id = decoded_header[7]; freq = PacketFrequency.Medium; + id = bytes[7]; } } else { - id = decoded_header[6]; freq = PacketFrequency.High; + id = bytes[6]; } return Packet.GetType(id, freq); diff --git a/OpenSim/Region/ClientStack/Linden/UDP/Properties/AssemblyInfo.cs b/OpenSim/Region/ClientStack/Linden/UDP/Properties/AssemblyInfo.cs index af2f6f8..bf505b4 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/Properties/AssemblyInfo.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/Properties/AssemblyInfo.cs @@ -1,6 +1,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Mono.Addins; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information @@ -29,5 +30,7 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.8.3.*")] + +[assembly: Addin("LindenUDP", OpenSim.VersionInfo.VersionNumber)] +[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)] diff --git a/OpenSim/Region/ClientStack/Linden/UDP/Tests/BasicCircuitTests.cs b/OpenSim/Region/ClientStack/Linden/UDP/Tests/BasicCircuitTests.cs index 556df30..a935dd2 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/Tests/BasicCircuitTests.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/Tests/BasicCircuitTests.cs @@ -33,9 +33,9 @@ using NUnit.Framework; using OpenMetaverse; using OpenMetaverse.Packets; using OpenSim.Framework; +using OpenSim.Framework.Monitoring; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.ClientStack.LindenUDP.Tests { @@ -46,7 +46,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests public class BasicCircuitTests : OpenSimTestCase { private Scene m_scene; - private TestLLUDPServer m_udpServer; [TestFixtureSetUp] public void FixtureInit() @@ -69,74 +68,26 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests { base.SetUp(); m_scene = new SceneHelpers().SetupScene(); + StatsManager.SimExtraStats = new SimExtraStatsCollector(); } - /// - /// Build an object name packet for test purposes - /// - /// - /// - private ObjectNamePacket BuildTestObjectNamePacket(uint objectLocalId, string objectName) - { - ObjectNamePacket onp = new ObjectNamePacket(); - ObjectNamePacket.ObjectDataBlock odb = new ObjectNamePacket.ObjectDataBlock(); - odb.LocalID = objectLocalId; - odb.Name = Utils.StringToBytes(objectName); - onp.ObjectData = new ObjectNamePacket.ObjectDataBlock[] { odb }; - onp.Header.Zerocoded = false; - - return onp; - } - - private void AddUdpServer() - { - AddUdpServer(new IniConfigSource()); - } - - private void AddUdpServer(IniConfigSource configSource) - { - uint port = 0; - AgentCircuitManager acm = m_scene.AuthenticateHandler; - - m_udpServer = new TestLLUDPServer(IPAddress.Any, ref port, 0, false, configSource, acm); - m_udpServer.AddScene(m_scene); - } - - /// - /// Used by tests that aren't testing this stage. - /// - private ScenePresence AddClient() - { - UUID myAgentUuid = TestHelpers.ParseTail(0x1); - UUID mySessionUuid = TestHelpers.ParseTail(0x2); - uint myCircuitCode = 123456; - IPEndPoint testEp = new IPEndPoint(IPAddress.Loopback, 999); - - UseCircuitCodePacket uccp = new UseCircuitCodePacket(); - - UseCircuitCodePacket.CircuitCodeBlock uccpCcBlock - = new UseCircuitCodePacket.CircuitCodeBlock(); - uccpCcBlock.Code = myCircuitCode; - uccpCcBlock.ID = myAgentUuid; - uccpCcBlock.SessionID = mySessionUuid; - uccp.CircuitCode = uccpCcBlock; - - byte[] uccpBytes = uccp.ToBytes(); - UDPPacketBuffer upb = new UDPPacketBuffer(testEp, uccpBytes.Length); - upb.DataLength = uccpBytes.Length; // God knows why this isn't set by the constructor. - Buffer.BlockCopy(uccpBytes, 0, upb.Data, 0, uccpBytes.Length); - - AgentCircuitData acd = new AgentCircuitData(); - acd.AgentID = myAgentUuid; - acd.SessionID = mySessionUuid; - - m_scene.AuthenticateHandler.AddNewCircuit(myCircuitCode, acd); - - m_udpServer.PacketReceived(upb); - - return m_scene.GetScenePresence(myAgentUuid); - } - +// /// +// /// Build an object name packet for test purposes +// /// +// /// +// /// +// private ObjectNamePacket BuildTestObjectNamePacket(uint objectLocalId, string objectName) +// { +// ObjectNamePacket onp = new ObjectNamePacket(); +// ObjectNamePacket.ObjectDataBlock odb = new ObjectNamePacket.ObjectDataBlock(); +// odb.LocalID = objectLocalId; +// odb.Name = Utils.StringToBytes(objectName); +// onp.ObjectData = new ObjectNamePacket.ObjectDataBlock[] { odb }; +// onp.Header.Zerocoded = false; +// +// return onp; +// } +// /// /// Test adding a client to the stack /// @@ -146,7 +97,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests TestHelpers.InMethod(); // TestHelpers.EnableLogging(); - AddUdpServer(); + TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(m_scene); UUID myAgentUuid = TestHelpers.ParseTail(0x1); UUID mySessionUuid = TestHelpers.ParseTail(0x2); @@ -167,7 +118,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests upb.DataLength = uccpBytes.Length; // God knows why this isn't set by the constructor. Buffer.BlockCopy(uccpBytes, 0, upb.Data, 0, uccpBytes.Length); - m_udpServer.PacketReceived(upb); + udpServer.PacketReceived(upb); // Presence shouldn't exist since the circuit manager doesn't know about this circuit for authentication yet Assert.That(m_scene.GetScenePresence(myAgentUuid), Is.Null); @@ -178,15 +129,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests m_scene.AuthenticateHandler.AddNewCircuit(myCircuitCode, acd); - m_udpServer.PacketReceived(upb); + udpServer.PacketReceived(upb); // Should succeed now ScenePresence sp = m_scene.GetScenePresence(myAgentUuid); Assert.That(sp.UUID, Is.EqualTo(myAgentUuid)); - Assert.That(m_udpServer.PacketsSent.Count, Is.EqualTo(1)); + Assert.That(udpServer.PacketsSent.Count, Is.EqualTo(1)); - Packet packet = m_udpServer.PacketsSent[0]; + Packet packet = udpServer.PacketsSent[0]; Assert.That(packet, Is.InstanceOf(typeof(PacketAckPacket))); PacketAckPacket ackPacket = packet as PacketAckPacket; @@ -203,15 +154,16 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests IniConfigSource ics = new IniConfigSource(); IConfig config = ics.AddConfig("ClientStack.LindenUDP"); config.Set("AckTimeout", -1); - AddUdpServer(ics); + TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(m_scene, ics); + + ScenePresence sp + = ClientStackHelpers.AddChildClient( + m_scene, udpServer, TestHelpers.ParseTail(0x1), TestHelpers.ParseTail(0x2), 123456); - ScenePresence sp = AddClient(); - m_udpServer.ClientOutgoingPacketHandler(sp.ControllingClient, true, false, false); + udpServer.ClientOutgoingPacketHandler(sp.ControllingClient, true, false, false); ScenePresence spAfterAckTimeout = m_scene.GetScenePresence(sp.UUID); Assert.That(spAfterAckTimeout, Is.Null); - -// TestHelpers.DisableLogging(); } // /// @@ -233,7 +185,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests // testLLUDPServer.RemoveClientCircuit(myCircuitCode); // Assert.IsFalse(testLLUDPServer.HasCircuit(myCircuitCode)); // -// // Check that removing a non-existant circuit doesn't have any bad effects +// // Check that removing a non-existent circuit doesn't have any bad effects // testLLUDPServer.RemoveClientCircuit(101); // Assert.IsFalse(testLLUDPServer.HasCircuit(101)); // } diff --git a/OpenSim/Region/ClientStack/Linden/UDP/Tests/LLImageManagerTests.cs b/OpenSim/Region/ClientStack/Linden/UDP/Tests/LLImageManagerTests.cs index 7d9f581..6c57e6d 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/Tests/LLImageManagerTests.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/Tests/LLImageManagerTests.cs @@ -29,6 +29,7 @@ using System; using System.IO; using System.Net; using System.Reflection; +using System.Threading; using log4net.Config; using Nini.Config; using NUnit.Framework; @@ -38,7 +39,6 @@ using OpenSim.Framework; using OpenSim.Region.CoreModules.Agent.TextureSender; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.ClientStack.LindenUDP.Tests { @@ -53,6 +53,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests [TestFixtureSetUp] public void FixtureInit() { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.None; + using ( Stream resource = GetType().Assembly.GetManifestResourceStream( @@ -72,9 +75,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests } } + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten not to worry about such things. + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + [SetUp] - public void SetUp() + public override void SetUp() { + base.SetUp(); + UUID userId = TestHelpers.ParseTail(0x3); J2KDecoderModule j2kdm = new J2KDecoderModule(); diff --git a/OpenSim/Region/ClientStack/Linden/UDP/Tests/MockScene.cs b/OpenSim/Region/ClientStack/Linden/UDP/Tests/MockScene.cs deleted file mode 100644 index 119a677..0000000 --- a/OpenSim/Region/ClientStack/Linden/UDP/Tests/MockScene.cs +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System.Net; -using OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Region.Framework.Scenes; -using GridRegion = OpenSim.Services.Interfaces.GridRegion; - -namespace OpenSim.Region.ClientStack.LindenUDP.Tests -{ - /// - /// Mock scene for unit tests - /// - public class MockScene : SceneBase - { - public int ObjectNameCallsReceived - { - get { return m_objectNameCallsReceived; } - } - protected int m_objectNameCallsReceived; - - public MockScene() : base(new RegionInfo(1000, 1000, null, null)) - { - m_regStatus = RegionStatus.Up; - } - - public override void Update(int frames) {} - public override void LoadWorldMap() {} - - public override ISceneAgent AddNewClient(IClientAPI client, PresenceType type) - { - client.OnObjectName += RecordObjectNameCall; - - // FIXME - return null; - } - - public override void RemoveClient(UUID agentID, bool someReason) {} -// public override void CloseAllAgents(uint circuitcode) {} - public override bool CheckClient(UUID clientId, IPEndPoint endPoint) { return true; } - public override void OtherRegionUp(GridRegion otherRegion) { } - - public override bool TryGetScenePresence(UUID uuid, out ScenePresence sp) { sp = null; return false; } - - /// - /// Doesn't really matter what the call is - we're using this to test that a packet has actually been received - /// - protected void RecordObjectNameCall(IClientAPI remoteClient, uint localID, string message) - { - m_objectNameCallsReceived++; - } - } -} diff --git a/OpenSim/Region/ClientStack/Linden/UDP/Tests/PacketHandlerTests.cs b/OpenSim/Region/ClientStack/Linden/UDP/Tests/PacketHandlerTests.cs index 5f73a94..92f1fc3 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/Tests/PacketHandlerTests.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/Tests/PacketHandlerTests.cs @@ -30,7 +30,6 @@ using NUnit.Framework; using OpenMetaverse; using OpenMetaverse.Packets; using OpenSim.Framework; -using OpenSim.Tests.Common.Mock; using OpenSim.Tests.Common; namespace OpenSim.Region.ClientStack.LindenUDP.Tests diff --git a/OpenSim/Region/ClientStack/Linden/UDP/Tests/TestLLUDPServer.cs b/OpenSim/Region/ClientStack/Linden/UDP/Tests/TestLLUDPServer.cs deleted file mode 100644 index 27b9e5b..0000000 --- a/OpenSim/Region/ClientStack/Linden/UDP/Tests/TestLLUDPServer.cs +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using Nini.Config; -using OpenMetaverse.Packets; -using OpenSim.Framework; - -namespace OpenSim.Region.ClientStack.LindenUDP.Tests -{ - /// - /// This class enables regression testing of the LLUDPServer by allowing us to intercept outgoing data. - /// - public class TestLLUDPServer : LLUDPServer - { - public List PacketsSent { get; private set; } - - public TestLLUDPServer(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager) - : base(listenIP, ref port, proxyPortOffsetParm, allow_alternate_port, configSource, circuitManager) - { - PacketsSent = new List(); - } - - public override void SendAckImmediate(IPEndPoint remoteEndpoint, PacketAckPacket ack) - { - PacketsSent.Add(ack); - } - - public override void SendPacket( - LLUDPClient udpClient, Packet packet, ThrottleOutPacketType category, bool allowSplitting, UnackedPacketMethod method) - { - PacketsSent.Add(packet); - } - - public void ClientOutgoingPacketHandler(IClientAPI client, bool resendUnacked, bool sendAcks, bool sendPing) - { - m_resendUnacked = resendUnacked; - m_sendAcks = sendAcks; - m_sendPing = sendPing; - - ClientOutgoingPacketHandler(client); - } - -//// /// -//// /// The chunks of data to pass to the LLUDPServer when it calls EndReceive -//// /// -//// protected Queue m_chunksToLoad = new Queue(); -// -//// protected override void BeginReceive() -//// { -//// if (m_chunksToLoad.Count > 0 && m_chunksToLoad.Peek().BeginReceiveException) -//// { -//// ChunkSenderTuple tuple = m_chunksToLoad.Dequeue(); -//// reusedEpSender = tuple.Sender; -//// throw new SocketException(); -//// } -//// } -// -//// protected override bool EndReceive(out int numBytes, IAsyncResult result, ref EndPoint epSender) -//// { -//// numBytes = 0; -//// -//// //m_log.Debug("Queue size " + m_chunksToLoad.Count); -//// -//// if (m_chunksToLoad.Count <= 0) -//// return false; -//// -//// ChunkSenderTuple tuple = m_chunksToLoad.Dequeue(); -//// RecvBuffer = tuple.Data; -//// numBytes = tuple.Data.Length; -//// epSender = tuple.Sender; -//// -//// return true; -//// } -// -//// public override void SendPacketTo(byte[] buffer, int size, SocketFlags flags, uint circuitcode) -//// { -//// // Don't do anything just yet -//// } -// -// /// -// /// Signal that this chunk should throw an exception on Socket.BeginReceive() -// /// -// /// -// public void LoadReceiveWithBeginException(EndPoint epSender) -// { -// ChunkSenderTuple tuple = new ChunkSenderTuple(epSender); -// tuple.BeginReceiveException = true; -// m_chunksToLoad.Enqueue(tuple); -// } -// -// /// -// /// Load some data to be received by the LLUDPServer on the next receive call -// /// -// /// -// /// -// public void LoadReceive(byte[] data, EndPoint epSender) -// { -// m_chunksToLoad.Enqueue(new ChunkSenderTuple(data, epSender)); -// } -// -// /// -// /// Load a packet to be received by the LLUDPServer on the next receive call -// /// -// /// -// public void LoadReceive(Packet packet, EndPoint epSender) -// { -// LoadReceive(packet.ToBytes(), epSender); -// } -// -// /// -// /// Calls the protected asynchronous result method. This fires out all data chunks currently queued for send -// /// -// /// -// public void ReceiveData(IAsyncResult result) -// { -// // Doesn't work the same way anymore -//// while (m_chunksToLoad.Count > 0) -//// OnReceivedData(result); -// } - } - - /// - /// Record the data and sender tuple - /// - public class ChunkSenderTuple - { - public byte[] Data; - public EndPoint Sender; - public bool BeginReceiveException; - - public ChunkSenderTuple(byte[] data, EndPoint sender) - { - Data = data; - Sender = sender; - } - - public ChunkSenderTuple(EndPoint sender) - { - Sender = sender; - } - } -} diff --git a/OpenSim/Region/ClientStack/Linden/UDP/Tests/ThrottleTests.cs b/OpenSim/Region/ClientStack/Linden/UDP/Tests/ThrottleTests.cs new file mode 100644 index 0000000..3c82a78 --- /dev/null +++ b/OpenSim/Region/ClientStack/Linden/UDP/Tests/ThrottleTests.cs @@ -0,0 +1,427 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse.Packets; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ClientStack.LindenUDP.Tests +{ + [TestFixture] + public class ThrottleTests : OpenSimTestCase + { + [TestFixtureSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [Test] + public void TestSetRequestDripRate() + { + TestHelpers.InMethod(); + + TokenBucket tb = new TokenBucket("tb", null, 5000, 0); + AssertRates(tb, 5000, 0, 5000, 0); + + tb.RequestedDripRate = 4000; + AssertRates(tb, 4000, 0, 4000, 0); + + tb.RequestedDripRate = 6000; + AssertRates(tb, 6000, 0, 6000, 0); + } + + [Test] + public void TestSetRequestDripRateWithMax() + { + TestHelpers.InMethod(); + + TokenBucket tb = new TokenBucket("tb", null, 5000, 10000); + AssertRates(tb, 5000, 0, 5000, 10000); + + tb.RequestedDripRate = 4000; + AssertRates(tb, 4000, 0, 4000, 10000); + + tb.RequestedDripRate = 6000; + AssertRates(tb, 6000, 0, 6000, 10000); + + tb.RequestedDripRate = 12000; + AssertRates(tb, 10000, 0, 10000, 10000); + } + + [Test] + public void TestSetRequestDripRateWithChildren() + { + TestHelpers.InMethod(); + + TokenBucket tbParent = new TokenBucket("tbParent", null, 0, 0); + TokenBucket tbChild1 = new TokenBucket("tbChild1", tbParent, 3000, 0); + TokenBucket tbChild2 = new TokenBucket("tbChild2", tbParent, 5000, 0); + + AssertRates(tbParent, 8000, 8000, 8000, 0); + AssertRates(tbChild1, 3000, 0, 3000, 0); + AssertRates(tbChild2, 5000, 0, 5000, 0); + + // Test: Setting a parent request greater than total children requests. + tbParent.RequestedDripRate = 10000; + + AssertRates(tbParent, 10000, 8000, 8000, 0); + AssertRates(tbChild1, 3000, 0, 3000, 0); + AssertRates(tbChild2, 5000, 0, 5000, 0); + + // Test: Setting a parent request lower than total children requests. + tbParent.RequestedDripRate = 6000; + + AssertRates(tbParent, 6000, 8000, 6000, 0); + AssertRates(tbChild1, 3000, 0, 6000 / 8 * 3, 0); + AssertRates(tbChild2, 5000, 0, 6000 / 8 * 5, 0); + } + + private void AssertRates( + TokenBucket tb, double requestedDripRate, double totalDripRequest, double dripRate, double maxDripRate) + { + Assert.AreEqual((int)requestedDripRate, tb.RequestedDripRate, "Requested drip rate"); + Assert.AreEqual((int)totalDripRequest, tb.TotalDripRequest, "Total drip request"); + Assert.AreEqual((int)dripRate, tb.DripRate, "Drip rate"); + Assert.AreEqual((int)maxDripRate, tb.MaxDripRate, "Max drip rate"); + } + + [Test] + public void TestClientThrottleSetNoLimit() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(scene); + + ScenePresence sp + = ClientStackHelpers.AddChildClient( + scene, udpServer, TestHelpers.ParseTail(0x1), TestHelpers.ParseTail(0x2), 123456); + + LLUDPClient udpClient = ((LLClientView)sp.ControllingClient).UDPClient; + + udpServer.Throttle.DebugLevel = 1; + udpClient.ThrottleDebugLevel = 1; + + int resendBytes = 1000; + int landBytes = 2000; + int windBytes = 3000; + int cloudBytes = 4000; + int taskBytes = 5000; + int textureBytes = 6000; + int assetBytes = 7000; + + SetThrottles( + udpClient, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); + + // We expect this to be lower because of the minimum bound set by MTU + int totalBytes = LLUDPServer.MTU + landBytes + windBytes + cloudBytes + taskBytes + textureBytes + assetBytes; + + AssertThrottles( + udpClient, + LLUDPServer.MTU, landBytes, windBytes, cloudBytes, taskBytes, + textureBytes, assetBytes, totalBytes, 0, 0); + } + + [Test] + public void TestClientThrottleAdaptiveNoLimit() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + + IniConfigSource ics = new IniConfigSource(); + IConfig config = ics.AddConfig("ClientStack.LindenUDP"); + config.Set("enable_adaptive_throttles", true); + config.Set("adaptive_throttle_min_bps", 32000); + + TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(scene, ics); + + ScenePresence sp + = ClientStackHelpers.AddChildClient( + scene, udpServer, TestHelpers.ParseTail(0x1), TestHelpers.ParseTail(0x2), 123456); + + LLUDPClient udpClient = ((LLClientView)sp.ControllingClient).UDPClient; + + udpServer.Throttle.DebugLevel = 1; + udpClient.ThrottleDebugLevel = 1; + + // Total is 275000 + int resendBytes = 5000; // this is set low to test the minimum throttle override + int landBytes = 20000; + int windBytes = 30000; + int cloudBytes = 40000; + int taskBytes = 50000; + int textureBytes = 60000; + int assetBytes = 70000; + int totalBytes = resendBytes + landBytes + windBytes + cloudBytes + taskBytes + textureBytes + assetBytes; + + SetThrottles( + udpClient, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); + + // Ratio of current adaptive drip rate to requested bytes, minimum rate is 32000 + double commitRatio = 32000.0 / totalBytes; + + AssertThrottles( + udpClient, + LLUDPServer.MTU, landBytes * commitRatio, windBytes * commitRatio, cloudBytes * commitRatio, taskBytes * commitRatio, + textureBytes * commitRatio, assetBytes * commitRatio, udpClient.FlowThrottle.AdjustedDripRate, totalBytes, 0); + + // Test an increase in target throttle, ack of 20 packets adds 20 * LLUDPServer.MTU bytes + // to the throttle, recompute commitratio from those numbers + udpClient.FlowThrottle.AcknowledgePackets(20); + commitRatio = (32000.0 + 20.0 * LLUDPServer.MTU) / totalBytes; + + AssertThrottles( + udpClient, + LLUDPServer.MTU, landBytes * commitRatio, windBytes * commitRatio, cloudBytes * commitRatio, taskBytes * commitRatio, + textureBytes * commitRatio, assetBytes * commitRatio, udpClient.FlowThrottle.AdjustedDripRate, totalBytes, 0); + + // Test a decrease in target throttle, adaptive throttle should cut the rate by 50% with a floor + // set by the minimum adaptive rate + udpClient.FlowThrottle.ExpirePackets(1); + commitRatio = (32000.0 + (20.0 * LLUDPServer.MTU)/Math.Pow(2,1)) / totalBytes; + + AssertThrottles( + udpClient, + LLUDPServer.MTU, landBytes * commitRatio, windBytes * commitRatio, cloudBytes * commitRatio, taskBytes * commitRatio, + textureBytes * commitRatio, assetBytes * commitRatio, udpClient.FlowThrottle.AdjustedDripRate, totalBytes, 0); + } + + /// + /// Test throttle setttings where max client throttle has been limited server side. + /// + [Test] + public void TestSingleClientThrottleRegionLimited() + { + TestHelpers.InMethod(); + // TestHelpers.EnableLogging(); + + int resendBytes = 6000; + int landBytes = 8000; + int windBytes = 10000; + int cloudBytes = 12000; + int taskBytes = 14000; + int textureBytes = 16000; + int assetBytes = 18000; + int totalBytes + = (int)((resendBytes + landBytes + windBytes + cloudBytes + taskBytes + textureBytes + assetBytes) / 2); + + Scene scene = new SceneHelpers().SetupScene(); + TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(scene); + udpServer.Throttle.RequestedDripRate = totalBytes; + + ScenePresence sp1 + = ClientStackHelpers.AddChildClient( + scene, udpServer, TestHelpers.ParseTail(0x1), TestHelpers.ParseTail(0x2), 123456); + + LLUDPClient udpClient1 = ((LLClientView)sp1.ControllingClient).UDPClient; + + SetThrottles( + udpClient1, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); + + AssertThrottles( + udpClient1, + resendBytes / 2, landBytes / 2, windBytes / 2, cloudBytes / 2, taskBytes / 2, + textureBytes / 2, assetBytes / 2, totalBytes, 0, 0); + + // Test: Now add another client + ScenePresence sp2 + = ClientStackHelpers.AddChildClient( + scene, udpServer, TestHelpers.ParseTail(0x10), TestHelpers.ParseTail(0x20), 123457); + + LLUDPClient udpClient2 = ((LLClientView)sp2.ControllingClient).UDPClient; + // udpClient.ThrottleDebugLevel = 1; + + SetThrottles( + udpClient2, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); + + AssertThrottles( + udpClient1, + resendBytes / 4, landBytes / 4, windBytes / 4, cloudBytes / 4, taskBytes / 4, + textureBytes / 4, assetBytes / 4, totalBytes / 2, 0, 0); + + AssertThrottles( + udpClient2, + resendBytes / 4, landBytes / 4, windBytes / 4, cloudBytes / 4, taskBytes / 4, + textureBytes / 4, assetBytes / 4, totalBytes / 2, 0, 0); + } + + /// + /// Test throttle setttings where max client throttle has been limited server side. + /// + [Test] + public void TestClientThrottlePerClientLimited() + { + TestHelpers.InMethod(); + // TestHelpers.EnableLogging(); + + int resendBytes = 4000; + int landBytes = 6000; + int windBytes = 8000; + int cloudBytes = 10000; + int taskBytes = 12000; + int textureBytes = 14000; + int assetBytes = 16000; + int totalBytes + = (int)((resendBytes + landBytes + windBytes + cloudBytes + taskBytes + textureBytes + assetBytes) / 2); + + Scene scene = new SceneHelpers().SetupScene(); + TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(scene); + udpServer.ThrottleRates.Total = totalBytes; + + ScenePresence sp + = ClientStackHelpers.AddChildClient( + scene, udpServer, TestHelpers.ParseTail(0x1), TestHelpers.ParseTail(0x2), 123456); + + LLUDPClient udpClient = ((LLClientView)sp.ControllingClient).UDPClient; + // udpClient.ThrottleDebugLevel = 1; + + SetThrottles( + udpClient, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); + + AssertThrottles( + udpClient, + resendBytes / 2, landBytes / 2, windBytes / 2, cloudBytes / 2, taskBytes / 2, + textureBytes / 2, assetBytes / 2, totalBytes, 0, totalBytes); + } + + [Test] + public void TestClientThrottlePerClientAndRegionLimited() + { + TestHelpers.InMethod(); + //TestHelpers.EnableLogging(); + + int resendBytes = 4000; + int landBytes = 6000; + int windBytes = 8000; + int cloudBytes = 10000; + int taskBytes = 12000; + int textureBytes = 14000; + int assetBytes = 16000; + + // current total 70000 + int totalBytes = resendBytes + landBytes + windBytes + cloudBytes + taskBytes + textureBytes + assetBytes; + + Scene scene = new SceneHelpers().SetupScene(); + TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(scene); + udpServer.ThrottleRates.Total = (int)(totalBytes * 1.1); + udpServer.Throttle.RequestedDripRate = (int)(totalBytes * 1.5); + + ScenePresence sp1 + = ClientStackHelpers.AddChildClient( + scene, udpServer, TestHelpers.ParseTail(0x1), TestHelpers.ParseTail(0x2), 123456); + + LLUDPClient udpClient1 = ((LLClientView)sp1.ControllingClient).UDPClient; + udpClient1.ThrottleDebugLevel = 1; + + SetThrottles( + udpClient1, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); + + AssertThrottles( + udpClient1, + resendBytes, landBytes, windBytes, cloudBytes, taskBytes, + textureBytes, assetBytes, totalBytes, 0, totalBytes * 1.1); + + // Now add another client + ScenePresence sp2 + = ClientStackHelpers.AddChildClient( + scene, udpServer, TestHelpers.ParseTail(0x10), TestHelpers.ParseTail(0x20), 123457); + + LLUDPClient udpClient2 = ((LLClientView)sp2.ControllingClient).UDPClient; + udpClient2.ThrottleDebugLevel = 1; + + SetThrottles( + udpClient2, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); + + AssertThrottles( + udpClient1, + resendBytes * 0.75, landBytes * 0.75, windBytes * 0.75, cloudBytes * 0.75, taskBytes * 0.75, + textureBytes * 0.75, assetBytes * 0.75, totalBytes * 0.75, 0, totalBytes * 1.1); + + AssertThrottles( + udpClient2, + resendBytes * 0.75, landBytes * 0.75, windBytes * 0.75, cloudBytes * 0.75, taskBytes * 0.75, + textureBytes * 0.75, assetBytes * 0.75, totalBytes * 0.75, 0, totalBytes * 1.1); + } + + private void AssertThrottles( + LLUDPClient udpClient, + double resendBytes, double landBytes, double windBytes, double cloudBytes, double taskBytes, double textureBytes, double assetBytes, + double totalBytes, double targetBytes, double maxBytes) + { + ClientInfo ci = udpClient.GetClientInfo(); + +// Console.WriteLine( +// "Resend={0}, Land={1}, Wind={2}, Cloud={3}, Task={4}, Texture={5}, Asset={6}, TOTAL = {7}", +// ci.resendThrottle, ci.landThrottle, ci.windThrottle, ci.cloudThrottle, ci.taskThrottle, ci.textureThrottle, ci.assetThrottle, ci.totalThrottle); + + Assert.AreEqual((int)resendBytes, ci.resendThrottle, "Resend"); + Assert.AreEqual((int)landBytes, ci.landThrottle, "Land"); + Assert.AreEqual((int)windBytes, ci.windThrottle, "Wind"); + Assert.AreEqual((int)cloudBytes, ci.cloudThrottle, "Cloud"); + Assert.AreEqual((int)taskBytes, ci.taskThrottle, "Task"); + Assert.AreEqual((int)textureBytes, ci.textureThrottle, "Texture"); + Assert.AreEqual((int)assetBytes, ci.assetThrottle, "Asset"); + Assert.AreEqual((int)totalBytes, ci.totalThrottle, "Total"); + Assert.AreEqual((int)targetBytes, ci.targetThrottle, "Target"); + Assert.AreEqual((int)maxBytes, ci.maxThrottle, "Max"); + } + + private void SetThrottles( + LLUDPClient udpClient, int resendBytes, int landBytes, int windBytes, int cloudBytes, int taskBytes, int textureBytes, int assetBytes) + { + byte[] throttles = new byte[28]; + + Array.Copy(BitConverter.GetBytes((float)resendBytes * 8), 0, throttles, 0, 4); + Array.Copy(BitConverter.GetBytes((float)landBytes * 8), 0, throttles, 4, 4); + Array.Copy(BitConverter.GetBytes((float)windBytes * 8), 0, throttles, 8, 4); + Array.Copy(BitConverter.GetBytes((float)cloudBytes * 8), 0, throttles, 12, 4); + Array.Copy(BitConverter.GetBytes((float)taskBytes * 8), 0, throttles, 16, 4); + Array.Copy(BitConverter.GetBytes((float)textureBytes * 8), 0, throttles, 20, 4); + Array.Copy(BitConverter.GetBytes((float)assetBytes * 8), 0, throttles, 24, 4); + + udpClient.SetThrottles(throttles); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs b/OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs index c9aac0b..7a2756b 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs @@ -58,7 +58,17 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// Flag used to enable adaptive throttles public bool AdaptiveThrottlesEnabled; - + + /// + /// Set the minimum rate that the adaptive throttles can set. The viewer + /// can still throttle lower than this, but the adaptive throttles will + /// never decrease rates below this no matter how many packets are dropped + /// + public Int64 MinimumAdaptiveThrottleRate; + + /// Amount of the texture throttle to steal for the task throttle + public double CannibalizeTextureRate; + /// /// Default constructor /// @@ -69,6 +79,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP { IConfig throttleConfig = config.Configs["ClientStack.LindenUDP"]; + // Current default total is 66750 Resend = throttleConfig.GetInt("resend_default", 6625); Land = throttleConfig.GetInt("land_default", 9125); Wind = throttleConfig.GetInt("wind_default", 1750); @@ -80,6 +91,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP Total = throttleConfig.GetInt("client_throttle_max_bps", 0); AdaptiveThrottlesEnabled = throttleConfig.GetBoolean("enable_adaptive_throttles", false); + MinimumAdaptiveThrottleRate = throttleConfig.GetInt("adaptive_throttle_min_bps", 32000); + + CannibalizeTextureRate = (double)throttleConfig.GetFloat("CannibalizeTextureRate", 0.0f); + CannibalizeTextureRate = Util.Clamp(CannibalizeTextureRate,0.0, 0.9); } catch (Exception) { } } diff --git a/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs b/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs index 4c33db5..4616203 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -42,9 +42,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP public class TokenBucket { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private static Int32 m_counter = 0; - -// private Int32 m_identifier; + + public string Identifier { get; private set; } + + public int DebugLevel { get; set; } /// /// Number of ticks (ms) per quantum, drip rate and max burst @@ -60,7 +61,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// /// - protected const Int32 m_minimumDripRate = 1400; + protected const Int32 m_minimumDripRate = LLUDPServer.MTU; /// Time of the last drip, in system ticks protected Int32 m_lastDrip; @@ -75,20 +76,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// Map of children buckets and their requested maximum burst rate /// protected Dictionary m_children = new Dictionary(); - -#region Properties /// /// The parent bucket of this bucket, or null if this bucket has no /// parent. The parent bucket will limit the aggregate bandwidth of all /// of its children buckets /// - protected TokenBucket m_parent; - public TokenBucket Parent - { - get { return m_parent; } - set { m_parent = value; } - } + public TokenBucket Parent { get; protected set; } /// /// Maximum burst rate in bytes per second. This is the maximum number @@ -114,77 +108,106 @@ namespace OpenSim.Region.ClientStack.LindenUDP } /// - /// The speed limit of this bucket in bytes per second. This is the - /// number of tokens that are added to the bucket per quantum + /// The requested drip rate for this particular bucket. /// - /// Tokens are added to the bucket any time + /// + /// 0 then TotalDripRequest is used instead. + /// Can never be above MaxDripRate. + /// Tokens are added to the bucket at any time /// is called, at the granularity of - /// the system tick interval (typically around 15-22ms) - protected Int64 m_dripRate; + /// the system tick interval (typically around 15-22ms) + /// FIXME: It is extremely confusing to be able to set a RequestedDripRate of 0 and then receive a positive + /// number on get if TotalDripRequest is set. This also stops us being able to retrieve the fact that + /// RequestedDripRate is set to 0. Really, this should always return m_dripRate and then we can get + /// (m_dripRate == 0 ? TotalDripRequest : m_dripRate) on some other properties. + /// public virtual Int64 RequestedDripRate { - get { return (m_dripRate == 0 ? m_totalDripRequest : m_dripRate); } - set { - m_dripRate = (value < 0 ? 0 : value); + get { return (m_dripRate == 0 ? TotalDripRequest : m_dripRate); } + set + { + if (value <= 0) + m_dripRate = 0; + else if (MaxDripRate > 0 && value > MaxDripRate) + m_dripRate = MaxDripRate; + else + m_dripRate = value; + m_burstRate = (Int64)((double)m_dripRate * m_quantumsPerBurst); - m_totalDripRequest = m_dripRate; - if (m_parent != null) - m_parent.RegisterRequest(this,m_dripRate); + + if (Parent != null) + Parent.RegisterRequest(this, m_dripRate); } } + /// + /// Gets the drip rate. + /// + /// + /// DripRate can never be above max drip rate or below min drip rate. + /// If we are a child bucket then the drip rate return is modifed by the total load on the capacity of the + /// parent bucket. + /// public virtual Int64 DripRate { - get { - if (m_parent == null) - return Math.Min(RequestedDripRate,TotalDripRequest); - - double rate = (double)RequestedDripRate * m_parent.DripRateModifier(); + get + { + double rate; + + // FIXME: This doesn't properly work if we have a parent and children and a requested drip rate set + // on ourselves which is not equal to the child drip rates. + if (Parent == null) + { + if (TotalDripRequest > 0) + rate = Math.Min(RequestedDripRate, TotalDripRequest); + else + rate = RequestedDripRate; + } + else + { + rate = (double)RequestedDripRate * Parent.DripRateModifier(); + } + if (rate < m_minimumDripRate) rate = m_minimumDripRate; + else if (MaxDripRate > 0 && rate > MaxDripRate) + rate = MaxDripRate; return (Int64)rate; } } + protected Int64 m_dripRate; + + // + // The maximum rate for flow control. Drip rate can never be greater than this. + // + public Int64 MaxDripRate { get; set; } /// - /// The current total of the requested maximum burst rates of - /// this bucket's children buckets. + /// The current total of the requested maximum burst rates of children buckets. /// - protected Int64 m_totalDripRequest; - public Int64 TotalDripRequest - { - get { return m_totalDripRequest; } - set { m_totalDripRequest = value; } - } - -#endregion Properties - -#region Constructor + public Int64 TotalDripRequest { get; protected set; } /// /// Default constructor /// + /// Identifier for this token bucket /// Parent bucket if this is a child bucket, or /// null if this is a root bucket - /// Maximum size of the bucket in bytes, or - /// zero if this bucket has no maximum capacity - /// Rate that the bucket fills, in bytes per - /// second. If zero, the bucket always remains full - public TokenBucket(TokenBucket parent, Int64 dripRate) + /// + /// Requested rate that the bucket fills, in bytes per + /// second. If zero, the bucket always remains full. + /// + public TokenBucket(string identifier, TokenBucket parent, Int64 requestedDripRate, Int64 maxDripRate) { -// m_identifier = m_counter++; - m_counter++; + Identifier = identifier; Parent = parent; - RequestedDripRate = dripRate; - // TotalDripRequest = dripRate; // this will be overwritten when a child node registers - // MaxBurst = (Int64)((double)dripRate * m_quantumsPerBurst); + RequestedDripRate = requestedDripRate; + MaxDripRate = maxDripRate; m_lastDrip = Util.EnvironmentTickCount(); } -#endregion Constructor - /// /// Compute a modifier for the MaxBurst rate. This is 1.0, meaning /// no modification if the requested bandwidth is less than the @@ -195,7 +218,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP protected double DripRateModifier() { Int64 driprate = DripRate; - return driprate >= TotalDripRequest ? 1.0 : (double)driprate / (double)TotalDripRequest; + double modifier = driprate >= TotalDripRequest ? 1.0 : (double)driprate / (double)TotalDripRequest; + +// if (DebugLevel > 0) +// m_log.DebugFormat( +// "[TOKEN BUCKET]: Returning drip modifier {0}/{1} = {2} from {3}", +// driprate, TotalDripRequest, modifier, Identifier); + + return modifier; } /// @@ -217,16 +247,24 @@ namespace OpenSim.Region.ClientStack.LindenUDP lock (m_children) { m_children[child] = request; - // m_totalDripRequest = m_children.Values.Sum(); - m_totalDripRequest = 0; + TotalDripRequest = 0; foreach (KeyValuePair cref in m_children) - m_totalDripRequest += cref.Value; + TotalDripRequest += cref.Value; } // Pass the new values up to the parent - if (m_parent != null) - m_parent.RegisterRequest(this,Math.Min(RequestedDripRate, TotalDripRequest)); + if (Parent != null) + { + Int64 effectiveDripRate; + + if (RequestedDripRate > 0) + effectiveDripRate = Math.Min(RequestedDripRate, TotalDripRequest); + else + effectiveDripRate = TotalDripRequest; + + Parent.RegisterRequest(this, effectiveDripRate); + } } /// @@ -238,17 +276,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP lock (m_children) { m_children.Remove(child); - // m_totalDripRequest = m_children.Values.Sum(); - m_totalDripRequest = 0; + TotalDripRequest = 0; foreach (KeyValuePair cref in m_children) - m_totalDripRequest += cref.Value; + TotalDripRequest += cref.Value; } - // Pass the new values up to the parent - if (m_parent != null) - m_parent.RegisterRequest(this,Math.Min(RequestedDripRate, TotalDripRequest)); + if (Parent != null) + Parent.RegisterRequest(this,Math.Min(RequestedDripRate, TotalDripRequest)); } /// @@ -301,7 +337,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP // with no drip rate... if (DripRate == 0) { - m_log.WarnFormat("[TOKENBUCKET] something odd is happening and drip rate is 0"); + m_log.WarnFormat("[TOKENBUCKET] something odd is happening and drip rate is 0 for {0}", Identifier); return; } @@ -321,74 +357,108 @@ namespace OpenSim.Region.ClientStack.LindenUDP public class AdaptiveTokenBucket : TokenBucket { -// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public bool AdaptiveEnabled { get; set; } /// - /// The minimum rate for flow control. Minimum drip rate is one - /// packet per second. Open the throttle to 15 packets per second - /// or about 160kbps. + /// Target drip rate for this bucket. /// - protected const Int64 m_minimumFlow = m_minimumDripRate * 15; - - // - // The maximum rate for flow control. Drip rate can never be - // greater than this. - // - protected Int64 m_maxDripRate = 0; - protected Int64 MaxDripRate - { - get { return (m_maxDripRate == 0 ? m_totalDripRequest : m_maxDripRate); } - set { m_maxDripRate = (value == 0 ? 0 : Math.Max(value,m_minimumFlow)); } + /// Usually set by the client. If adaptive is enabled then throttles will increase until we reach this. + public Int64 TargetDripRate + { + get { return m_targetDripRate; } + set + { + m_targetDripRate = Math.Max(value, m_minimumFlow); + } } - - private bool m_enabled = false; - + protected Int64 m_targetDripRate; + // - // + // Adjust drip rate in response to network conditions. // public virtual Int64 AdjustedDripRate { get { return m_dripRate; } - set { - m_dripRate = OpenSim.Framework.Util.Clamp(value,m_minimumFlow,MaxDripRate); + set + { + m_dripRate = OpenSim.Framework.Util.Clamp(value, m_minimumFlow, TargetDripRate); m_burstRate = (Int64)((double)m_dripRate * m_quantumsPerBurst); - if (m_parent != null) - m_parent.RegisterRequest(this,m_dripRate); + + if (Parent != null) + Parent.RegisterRequest(this, m_dripRate); } } + + /// + /// The minimum rate for adaptive flow control. + /// + protected Int64 m_minimumFlow = 32000; - // - // - // - public AdaptiveTokenBucket(TokenBucket parent, Int64 maxDripRate, bool enabled) : base(parent,maxDripRate) + /// + /// Constructor for the AdaptiveTokenBucket class + /// Unique identifier for the client + /// Parent bucket in the hierarchy + /// + /// The ceiling rate for adaptation + /// The floor rate for adaptation + /// + public AdaptiveTokenBucket(string identifier, TokenBucket parent, Int64 requestedDripRate, Int64 maxDripRate, Int64 minDripRate, bool enabled) + : base(identifier, parent, requestedDripRate, maxDripRate) { - m_enabled = enabled; + AdaptiveEnabled = enabled; - if (m_enabled) + if (AdaptiveEnabled) { - // m_log.DebugFormat("[TOKENBUCKET] Adaptive throttle enabled"); - MaxDripRate = maxDripRate; +// m_log.DebugFormat("[TOKENBUCKET]: Adaptive throttle enabled"); + m_minimumFlow = minDripRate; + TargetDripRate = m_minimumFlow; AdjustedDripRate = m_minimumFlow; } } - // - // - // - public void ExpirePackets(Int32 count) + /// + /// Reliable packets sent to the client for which we never received an ack adjust the drip rate down. + /// Number of packets that expired without successful delivery + /// + public void ExpirePackets(Int32 packets) { - // m_log.WarnFormat("[ADAPTIVEBUCKET] drop {0} by {1} expired packets",AdjustedDripRate,count); - if (m_enabled) - AdjustedDripRate = (Int64) (AdjustedDripRate / Math.Pow(2,count)); + if (AdaptiveEnabled) + { + if (DebugLevel > 0) + m_log.WarnFormat( + "[ADAPTIVEBUCKET] drop {0} by {1} expired packets for {2}", + AdjustedDripRate, packets, Identifier); + + // AdjustedDripRate = (Int64) (AdjustedDripRate / Math.Pow(2,packets)); + + // Compute the fallback solely on the rate allocated beyond the minimum, this + // should smooth out the fallback to the minimum rate + AdjustedDripRate = m_minimumFlow + (Int64) ((AdjustedDripRate - m_minimumFlow) / Math.Pow(2, packets)); + } } - // - // - // - public void AcknowledgePackets(Int32 count) + /// + /// Reliable packets acked by the client adjust the drip rate up. + /// Number of packets successfully acknowledged + /// + public void AcknowledgePackets(Int32 packets) + { + if (AdaptiveEnabled) + AdjustedDripRate = AdjustedDripRate + packets * LLUDPServer.MTU; + } + + /// + /// Adjust the minimum flow level for the adaptive throttle, this will drop adjusted + /// throttles back to the minimum levels + /// minDripRate--the new minimum flow + /// + public void ResetMinimumAdaptiveFlow(Int64 minDripRate) { - if (m_enabled) - AdjustedDripRate = AdjustedDripRate + count; + m_minimumFlow = minDripRate; + TargetDripRate = m_minimumFlow; + AdjustedDripRate = m_minimumFlow; } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/ClientStack/Linden/UDP/UnackedPacketCollection.cs b/OpenSim/Region/ClientStack/Linden/UDP/UnackedPacketCollection.cs index 9d6c09e..b546a99 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/UnackedPacketCollection.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/UnackedPacketCollection.cs @@ -31,6 +31,9 @@ using System.Net; using System.Threading; using OpenMetaverse; +//using System.Reflection; +//using log4net; + namespace OpenSim.Region.ClientStack.LindenUDP { /// @@ -60,6 +63,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } + //private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + /// Holds the actual unacked packet data, sorted by sequence number private Dictionary m_packets = new Dictionary(); /// Holds packets that need to be added to the unacknowledged list @@ -164,8 +169,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } - //if (expiredPackets != null) - // m_log.DebugFormat("[UNACKED PACKET COLLECTION]: Found {0} expired packets on timeout of {1}", expiredPackets.Count, timeoutMS); + // if (expiredPackets != null) + // m_log.DebugFormat("[UNACKED PACKET COLLECTION]: Found {0} expired packets on timeout of {1}", expiredPackets.Count, timeoutMS); return expiredPackets; } @@ -192,7 +197,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP // As with other network applications, assume that an acknowledged packet is an // indication that the network can handle a little more load, speed up the transmission - ackedPacket.Client.FlowThrottle.AcknowledgePackets(ackedPacket.Buffer.DataLength); + ackedPacket.Client.FlowThrottle.AcknowledgePackets(1); // Update stats Interlocked.Add(ref ackedPacket.Client.UnackedBytes, -ackedPacket.Buffer.DataLength); @@ -207,9 +212,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP } else { - //m_log.WarnFormat("[UNACKED PACKET COLLECTION]: Could not find packet with sequence number {0} to ack", pendingAcknowledgement.SequenceNumber); + // m_log.WarnFormat("[UNACKED PACKET COLLECTION]: found null packet for sequence number {0} to ack", + // pendingAcknowledgement.SequenceNumber); } } + else + { + // m_log.WarnFormat("[UNACKED PACKET COLLECTION]: Could not find packet with sequence number {0} to ack", + // pendingAcknowledgement.SequenceNumber); + } } uint pendingRemove; -- cgit v1.1