diff --git a/OpenMetaverse.Http/CapsServer.cs b/OpenMetaverse.Http/CapsServer.cs index 71b81af0..26eb21f2 100644 --- a/OpenMetaverse.Http/CapsServer.cs +++ b/OpenMetaverse.Http/CapsServer.cs @@ -34,19 +34,31 @@ using HttpServer; namespace OpenMetaverse.Http { + /// + /// Delegate for handling incoming HTTP requests through a capability + /// + /// Client context + /// HTTP request + /// HTTP response + /// User-defined state object + /// True to send the response and close the connection, false to leave the connection open + public delegate bool CapsRequestCallback(IHttpClientContext context, IHttpRequest request, IHttpResponse response, object state); + public class CapsServer { struct CapsRedirector { - public HttpRequestCallback LocalCallback; + public CapsRequestCallback LocalCallback; public Uri RemoteHandler; public bool ClientCertRequired; + public object State; - public CapsRedirector(HttpRequestCallback localCallback, Uri remoteHandler, bool clientCertRequired) + public CapsRedirector(CapsRequestCallback localCallback, Uri remoteHandler, bool clientCertRequired, object state) { LocalCallback = localCallback; RemoteHandler = remoteHandler; ClientCertRequired = clientCertRequired; + State = state; } } @@ -60,7 +72,7 @@ namespace OpenMetaverse.Http public CapsServer(IPAddress address, int port) { serverOwned = true; - capsHandler = BuildCapsHandler("^/"); + capsHandler = BuildCapsHandler(@"^/caps/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"); server = new WebServer(address, port); server.LogWriter = new log4netLogWriter(Logger.Log); } @@ -68,7 +80,7 @@ namespace OpenMetaverse.Http public CapsServer(IPAddress address, int port, X509Certificate sslCertificate, X509Certificate rootCA, bool requireClientCertificate) { serverOwned = true; - capsHandler = BuildCapsHandler("^/"); + capsHandler = BuildCapsHandler(@"^/caps/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"); server = new WebServer(address, port, sslCertificate, rootCA, requireClientCertificate); server.LogWriter = new log4netLogWriter(Logger.Log); } @@ -96,10 +108,10 @@ namespace OpenMetaverse.Http server.RemoveHandler(capsHandler); } - public UUID CreateCapability(HttpRequestCallback localHandler, bool clientCertRequired) + public UUID CreateCapability(CapsRequestCallback localHandler, bool clientCertRequired, object state) { UUID id = UUID.Random(); - CapsRedirector redirector = new CapsRedirector(localHandler, null, clientCertRequired); + CapsRedirector redirector = new CapsRedirector(localHandler, null, clientCertRequired, state); lock (syncRoot) fixedCaps.Add(id, redirector); @@ -110,7 +122,7 @@ namespace OpenMetaverse.Http public UUID CreateCapability(Uri remoteHandler, bool clientCertRequired) { UUID id = UUID.Random(); - CapsRedirector redirector = new CapsRedirector(null, remoteHandler, clientCertRequired); + CapsRedirector redirector = new CapsRedirector(null, remoteHandler, clientCertRequired, null); lock (syncRoot) fixedCaps.Add(id, redirector); @@ -118,10 +130,10 @@ namespace OpenMetaverse.Http return id; } - public UUID CreateCapability(HttpRequestCallback localHandler, bool clientCertRequired, double ttlSeconds) + public UUID CreateCapability(CapsRequestCallback localHandler, bool clientCertRequired, object state, double ttlSeconds) { UUID id = UUID.Random(); - CapsRedirector redirector = new CapsRedirector(localHandler, null, clientCertRequired); + CapsRedirector redirector = new CapsRedirector(localHandler, null, clientCertRequired, state); lock (syncRoot) expiringCaps.Add(id, redirector, DateTime.Now + TimeSpan.FromSeconds(ttlSeconds)); @@ -129,10 +141,10 @@ namespace OpenMetaverse.Http return id; } - public UUID CreateCapability(Uri remoteHandler, bool clientCertRequired, double ttlSeconds) + public UUID CreateCapability(Uri remoteHandler, bool clientCertRequired, object state, double ttlSeconds) { UUID id = UUID.Random(); - CapsRedirector redirector = new CapsRedirector(null, remoteHandler, clientCertRequired); + CapsRedirector redirector = new CapsRedirector(null, remoteHandler, clientCertRequired, state); lock (syncRoot) expiringCaps.Add(id, redirector, DateTime.Now + TimeSpan.FromSeconds(ttlSeconds)); @@ -156,9 +168,10 @@ namespace OpenMetaverse.Http UUID capsID; CapsRedirector redirector; bool success; - string uuidString = request.UriParts[request.UriParts.Length - 1]; - if (UUID.TryParse(uuidString, out capsID)) + string path = request.Uri.PathAndQuery.TrimEnd('/'); + + if (UUID.TryParse(path.Substring(path.Length - 36), out capsID)) { lock (syncRoot) success = (expiringCaps.TryGetValue(capsID, out redirector) || fixedCaps.TryGetValue(capsID, out redirector)); @@ -179,7 +192,7 @@ namespace OpenMetaverse.Http if (redirector.RemoteHandler != null) ProxyCapCallback(client, request, response, redirector.RemoteHandler); else - return redirector.LocalCallback(client, request, response); + return redirector.LocalCallback(client, request, response, redirector.State); return true; } @@ -234,7 +247,7 @@ namespace OpenMetaverse.Http } } - HttpServer.HttpRequestHandler BuildCapsHandler(string path) + HttpRequestHandler BuildCapsHandler(string path) { HttpRequestSignature signature = new HttpRequestSignature(); signature.ContentType = "application/xml"; diff --git a/OpenMetaverse.Http/EventQueueServer.cs b/OpenMetaverse.Http/EventQueueServer.cs index 57d1d7ba..6e092bb0 100644 --- a/OpenMetaverse.Http/EventQueueServer.cs +++ b/OpenMetaverse.Http/EventQueueServer.cs @@ -108,15 +108,15 @@ namespace OpenMetaverse.Http eventQueue.Enqueue(events[i]); } - public bool EventQueueHandler(ref HttpListenerContext context) + public bool EventQueueHandler(IHttpClientContext context, IHttpRequest request, IHttpResponse response) { // Decode the request - OSD request = null; + OSD osdRequest = null; - try { request = OSDParser.DeserializeLLSDXml(context.Request.InputStream); } + try { osdRequest = OSDParser.DeserializeLLSDXml(request.Body); } catch (Exception) { } - if (request != null && request.Type == OSDType.Map) + if (request != null && osdRequest.Type == OSDType.Map) { OSDMap requestMap = (OSDMap)request; int ack = requestMap["ack"].AsInteger(); @@ -130,7 +130,7 @@ namespace OpenMetaverse.Http if (!done) { - StartEventQueueThread(context); + StartEventQueueThread(context, request, response); // Tell HttpServer to leave the connection open return false; @@ -138,25 +138,25 @@ namespace OpenMetaverse.Http else { Logger.Log.InfoFormat("[EventQueue] Shutting down the event queue {0} at the client's request", - context.Request.Url); + request.Uri); Stop(); - context.Response.KeepAlive = context.Request.KeepAlive; + response.Connection = request.Connection; return true; } } else { Logger.Log.WarnFormat("[EventQueue] Received a request with invalid or missing LLSD at {0}, closing the connection", - context.Request.Url); + request.Uri); - context.Response.KeepAlive = context.Request.KeepAlive; - context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + response.Connection = request.Connection; + response.Status = HttpStatusCode.BadRequest; return true; } } - void StartEventQueueThread(HttpListenerContext httpContext) + void StartEventQueueThread(IHttpClientContext context, IHttpRequest request, IHttpResponse response) { // Spawn a new thread to hold the connection open and return from our precious IOCP thread Thread thread = new Thread(new ThreadStart( @@ -188,7 +188,7 @@ namespace OpenMetaverse.Http batchMsPassed = (int)(DateTime.Now - start).TotalMilliseconds; } - SendResponse(httpContext, eventsToSend); + SendResponse(context, request, response, eventsToSend); return; } else @@ -202,7 +202,7 @@ namespace OpenMetaverse.Http Logger.Log.DebugFormat( "[EventQueue] {0}ms passed without an event, timing out the event queue", totalMsPassed); - SendResponse(httpContext, null); + SendResponse(context, request, response, null); return; } } @@ -215,9 +215,9 @@ namespace OpenMetaverse.Http thread.Start(); } - void SendResponse(HttpListenerContext httpContext, List eventsToSend) + void SendResponse(IHttpClientContext context, IHttpRequest request, IHttpResponse response, List eventsToSend) { - httpContext.Response.KeepAlive = httpContext.Request.KeepAlive; + response.Connection = request.Connection; if (eventsToSend != null) { @@ -241,19 +241,19 @@ namespace OpenMetaverse.Http // Serialize the events and send the response byte[] buffer = OSDParser.SerializeLLSDXmlBytes(responseMap); - httpContext.Response.ContentType = "application/xml"; - httpContext.Response.ContentLength64 = buffer.Length; - httpContext.Response.OutputStream.Write(buffer, 0, buffer.Length); - httpContext.Response.OutputStream.Close(); - httpContext.Response.Close(); + response.ContentType = "application/xml"; + response.ContentLength = buffer.Length; + response.Body.Write(buffer, 0, buffer.Length); + response.Body.Flush(); } else { // The 502 response started as a bug in the LL event queue server implementation, // but is now hardcoded into the protocol as the code to use for a timeout - httpContext.Response.StatusCode = (int)HttpStatusCode.BadGateway; - httpContext.Response.Close(); + response.Status = HttpStatusCode.BadGateway; } + + response.Send(); } } } diff --git a/OpenMetaverse.StructuredData/JSON/OSDJson.cs b/OpenMetaverse.StructuredData/JSON/OSDJson.cs index 0dcda91d..5323f62b 100644 --- a/OpenMetaverse.StructuredData/JSON/OSDJson.cs +++ b/OpenMetaverse.StructuredData/JSON/OSDJson.cs @@ -132,12 +132,14 @@ namespace OpenMetaverse.StructuredData return new JsonData("b64::" + Convert.ToBase64String(osd.AsBinary())); case OSDType.Array: JsonData jsonarray = new JsonData(); + jsonarray.SetJsonType(JsonType.Array); OSDArray array = (OSDArray)osd; for (int i = 0; i < array.Count; i++) jsonarray.Add(SerializeJson(array[i])); return jsonarray; case OSDType.Map: JsonData jsonmap = new JsonData(); + jsonmap.SetJsonType(JsonType.Object); OSDMap map = (OSDMap)osd; foreach (KeyValuePair kvp in map) jsonmap[kvp.Key] = SerializeJson(kvp.Value); diff --git a/Programs/Simian/Extensions/AssetManager.cs b/Programs/Simian/Extensions/AssetManager.cs index 453ad841..22c9a40f 100644 --- a/Programs/Simian/Extensions/AssetManager.cs +++ b/Programs/Simian/Extensions/AssetManager.cs @@ -23,12 +23,12 @@ namespace Simian.Extensions { this.server = server; - UploadDir = Path.Combine(server.DataDir, UPLOAD_DIR); + UploadDir = Path.Combine(Simian.DATA_DIR, UPLOAD_DIR); // Try to create the data directories if they don't already exist - if (!Directory.Exists(server.DataDir)) + if (!Directory.Exists(Simian.DATA_DIR)) { - try { Directory.CreateDirectory(server.DataDir); } + try { Directory.CreateDirectory(Simian.DATA_DIR); } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Warning, ex); } } if (!Directory.Exists(UploadDir)) @@ -37,7 +37,7 @@ namespace Simian.Extensions catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Warning, ex); } } - LoadAssets(server.DataDir); + LoadAssets(Simian.DATA_DIR); LoadAssets(UploadDir); } diff --git a/Programs/Simian/Extensions/AvatarManager.cs b/Programs/Simian/Extensions/AvatarManager.cs index 601fe106..edcad733 100644 --- a/Programs/Simian/Extensions/AvatarManager.cs +++ b/Programs/Simian/Extensions/AvatarManager.cs @@ -106,26 +106,6 @@ namespace Simian.Extensions server.UDP.BroadcastPacket(sound, PacketCategory.State); } - public void Disconnect(Agent agent) - { - // Remove the avatar from the scene - SimulationObject obj; - if (server.Scene.TryGetObject(agent.Avatar.ID, out obj)) - server.Scene.ObjectRemove(this, obj.Prim.LocalID); - else - Logger.Log("Disconnecting an agent that is not in the scene", Helpers.LogLevel.Warning); - - // Remove the UDP client - server.UDP.RemoveClient(agent); - - // HACK: Notify everyone when someone disconnects - OfflineNotificationPacket offline = new OfflineNotificationPacket(); - offline.AgentBlock = new OfflineNotificationPacket.AgentBlockBlock[1]; - offline.AgentBlock[0] = new OfflineNotificationPacket.AgentBlockBlock(); - offline.AgentBlock[0].AgentID = agent.Avatar.ID; - server.UDP.BroadcastPacket(offline, PacketCategory.State); - } - public void SendAlert(Agent agent, string message) { AlertMessagePacket alert = new AlertMessagePacket(); @@ -450,8 +430,16 @@ namespace Simian.Extensions update.Index.Prey = -1; update.Index.You = 0; - update.AgentData = new CoarseLocationUpdatePacket.AgentDataBlock[agentDatas.Count - 1]; - update.Location = new CoarseLocationUpdatePacket.LocationBlock[agentDatas.Count - 1]; + // Count the number of blocks to send out + int count = 0; + for (int i = 0; i < agentDatas.Count; i++) + { + if (agentDatas[i].AgentID != agent.Avatar.ID) + ++count; + } + + update.AgentData = new CoarseLocationUpdatePacket.AgentDataBlock[count]; + update.Location = new CoarseLocationUpdatePacket.LocationBlock[count]; int j = 0; for (int i = 0; i < agentDatas.Count; i++) diff --git a/Programs/Simian/Extensions/CapsManager.cs b/Programs/Simian/Extensions/CapsManager.cs index 20a16716..b0314cf6 100644 --- a/Programs/Simian/Extensions/CapsManager.cs +++ b/Programs/Simian/Extensions/CapsManager.cs @@ -13,7 +13,6 @@ namespace Simian.Extensions { Simian server; CapsServer capsServer; - Dictionary eventQueues = new Dictionary(); public CapsManager() { @@ -22,47 +21,45 @@ namespace Simian.Extensions public void Start(Simian server) { this.server = server; - capsServer = new CapsServer(server.HttpServer, @"^/caps/"); + capsServer = new CapsServer(server.HttpServer, @"^/caps/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"); + capsServer.Start(); } public void Stop() { - lock (eventQueues) - { - foreach (EventQueueServer eventQueue in eventQueues.Values) - eventQueue.Stop(); - } - capsServer.Stop(); } - public UUID CreateCapability(HttpServer.HttpRequestCallback localHandler, bool clientCertRequired) + public Uri CreateCapability(CapsRequestCallback localHandler, bool clientCertRequired, object state) { - return capsServer.CreateCapability(localHandler, clientCertRequired); + UUID capID = capsServer.CreateCapability(localHandler, clientCertRequired, state); + return new Uri( + (server.SSL ? "https://" : "http://") + + server.HostName + + (server.HttpPort == 80 ? String.Empty : ":" + server.HttpPort) + + "/caps/" + capID.ToString()); } - public UUID CreateCapability(Uri remoteHandler, bool clientCertRequired) + public Uri CreateCapability(Uri remoteHandler, bool clientCertRequired) { - return capsServer.CreateCapability(remoteHandler, clientCertRequired); + UUID capID = capsServer.CreateCapability(remoteHandler, clientCertRequired); + return new Uri( + (server.SSL ? "https://" : "http://") + + server.HostName + + (server.HttpPort == 80 ? String.Empty : ":" + server.HttpPort) + + "/caps/" + capID.ToString()); } - public bool RemoveCapability(UUID capID) + public bool RemoveCapability(Uri cap) { - return capsServer.RemoveCapability(capID); - } + string path = cap.PathAndQuery.TrimEnd('/'); + UUID capID; - public void SendEvent(Agent agent, string name, OSDMap body) - { - EventQueueServer eventQueue; - if (eventQueues.TryGetValue(agent.Avatar.ID, out eventQueue)) - { - eventQueue.SendEvent(name, body); - } + // Parse the capability UUID out of the URI + if (UUID.TryParse(path.Substring(path.Length - 36), out capID)) + return capsServer.RemoveCapability(capID); else - { - Logger.Log(String.Format("Cannot send the event {0} to agent {1} {2}, no event queue for that avatar", - name, agent.FirstName, agent.LastName), Helpers.LogLevel.Warning); - } + return false; } } } diff --git a/Programs/Simian/Extensions/ConnectionManagement.cs b/Programs/Simian/Extensions/ConnectionManagement.cs index 055962da..5cd906e8 100644 --- a/Programs/Simian/Extensions/ConnectionManagement.cs +++ b/Programs/Simian/Extensions/ConnectionManagement.cs @@ -82,7 +82,7 @@ namespace Simian.Extensions server.UDP.SendPacket(agent.Avatar.ID, reply, PacketCategory.Transaction); - server.Avatars.Disconnect(agent); + server.Scene.ObjectRemove(this, agent.Avatar.ID); } } } diff --git a/Programs/Simian/Extensions/LindenLogin.cs b/Programs/Simian/Extensions/LindenLogin.cs index e9e49bbe..293e0927 100644 --- a/Programs/Simian/Extensions/LindenLogin.cs +++ b/Programs/Simian/Extensions/LindenLogin.cs @@ -141,10 +141,16 @@ namespace Simian.Extensions if (responseData.Success) responseData.InventorySkeleton = server.Inventory.CreateInventorySkeleton(responseData.AgentID); - XmlWriter writer = XmlWriter.Create(response.Body); - responseData.ToXmlRpc(writer); - writer.Flush(); - writer.Close(); + MemoryStream memoryStream = new MemoryStream(); + using (XmlWriter writer = XmlWriter.Create(memoryStream)) + { + responseData.ToXmlRpc(writer); + writer.Flush(); + } + + response.ContentLength = memoryStream.Length; + response.Body.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); + response.Body.Flush(); } catch (Exception ex) { @@ -178,6 +184,9 @@ namespace Simian.Extensions // Authentication successful, create a login instance of this agent agent = server.Accounts.CreateInstance(agentID); + // Create a seed capability for this agent + Uri seedCap = server.Capabilities.CreateCapability(server.Scene.SeedCapabilityHandler, false, agentID); + if (agent != null) { // Assign a circuit code and insert the agent into the unassociatedAgents dictionary @@ -217,8 +226,7 @@ namespace Simian.Extensions response.RegionY = regionY; response.SecondsSinceEpoch = DateTime.Now; - // FIXME: Actually generate a seed capability - response.SeedCapability = String.Format("http://{0}:{1}/seed_caps", simIP, server.HttpPort); + response.SeedCapability = seedCap.ToString(); response.SimIP = simIP; response.SimPort = (ushort)server.UDPPort; response.StartLocation = "last"; // FIXME: diff --git a/Programs/Simian/Extensions/MapLocal.cs b/Programs/Simian/Extensions/MapLocal.cs index 1664b4e6..fa669a7f 100644 --- a/Programs/Simian/Extensions/MapLocal.cs +++ b/Programs/Simian/Extensions/MapLocal.cs @@ -1,10 +1,24 @@ using System; +using System.IO; +using System.Net; +using System.Xml; using ExtensionLoader; using OpenMetaverse; using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; namespace Simian.Extensions { + class HyperGridLink + { + public string RegionName; + public ulong RegionHandle; + public UUID RegionID; + public UUID RegionImage; + public int UDPPort; + public int RemotingPort; + } + public class MapLocal : IExtension { Simian server; @@ -84,6 +98,8 @@ namespace Simian.Extensions { TeleportRequestPacket request = (TeleportRequestPacket)packet; + // TODO: Stand the avatar up first + if (request.Info.RegionID == server.Scene.RegionID) { // Local teleport @@ -102,7 +118,11 @@ namespace Simian.Extensions } else { - Logger.Log("Ignoring teleport request to " + request.Info.RegionID, Helpers.LogLevel.Warning); + TeleportFailedPacket reply = new TeleportFailedPacket(); + reply.Info.AgentID = agent.Avatar.ID; + reply.Info.Reason = Utils.StringToBytes("Unknown region"); + + server.UDP.SendPacket(agent.Avatar.ID, reply, PacketCategory.Transaction); } } @@ -110,6 +130,8 @@ namespace Simian.Extensions { TeleportLocationRequestPacket request = (TeleportLocationRequestPacket)packet; + // TODO: Stand the avatar up first + if (request.Info.RegionHandle == server.Scene.RegionHandle) { // Local teleport @@ -126,10 +148,410 @@ namespace Simian.Extensions server.UDP.SendPacket(agent.Avatar.ID, reply, PacketCategory.Transaction); } + else if (request.Info.RegionHandle == Utils.UIntsToLong((server.Scene.RegionX + 1) * 256, server.Scene.RegionY * 256)) + { + // Special case: adjacent simulator is the HyperGrid portal + HyperGridTeleport(agent, new Uri(/*"http://192.168.1.2:9010/"*/ "http://osl2.nac.uci.edu:9006/"), request.Info.Position); + } else { - Logger.Log("Ignoring teleport request to " + request.Info.RegionHandle, Helpers.LogLevel.Warning); + TeleportFailedPacket reply = new TeleportFailedPacket(); + reply.Info.AgentID = agent.Avatar.ID; + reply.Info.Reason = Utils.StringToBytes("Unknown region"); + + server.UDP.SendPacket(agent.Avatar.ID, reply, PacketCategory.Transaction); } } + + bool HyperGridTeleport(Agent agent, Uri destination, Vector3 destPos) + { + HyperGridLink link; + + TeleportProgress(agent, "Resolving destination IP address", TeleportFlags.ViaLocation); + + IPHostEntry entry = Dns.GetHostEntry(destination.DnsSafeHost); + if (entry.AddressList != null && entry.AddressList.Length >= 1) + { + TeleportProgress(agent, "Retrieving destination details", TeleportFlags.ViaLocation); + + if (LinkRegion(destination, out link)) + { + TeleportProgress(agent, "Creating foreign agent", TeleportFlags.ViaLocation); + + if (ExpectHyperGridUser(agent, destination, destPos, link)) + { + TeleportProgress(agent, "Establishing foreign agent presence", TeleportFlags.ViaLocation); + + // This is a crufty part of the HyperGrid protocol. We need to generate a fragment of a UUID + // (missing the last four digits) and send that as the caps_path variable. Locally, we expand + // that out to http://foreignsim:httpport/CAPS/fragment0000/ and use it as the seed caps path + // that is sent to the client + UUID seedID = UUID.Random(); + string seedCapFragment = seedID.ToString().Substring(0, 32); + Uri seedCap = new Uri(destination, "/CAPS/" + seedCapFragment + "0000/"); + + if (CreateChildAgent(agent, destination, destPos, link, seedCapFragment)) + { + // Send the final teleport packet to the client + TeleportFinishPacket teleport = new TeleportFinishPacket(); + teleport.Info.AgentID = agent.Avatar.ID; + teleport.Info.LocationID = 0; // Unused by the client + teleport.Info.RegionHandle = link.RegionHandle; + teleport.Info.SeedCapability = Utils.StringToBytes(seedCap.ToString()); + teleport.Info.SimAccess = (byte)SimAccess.Min; + teleport.Info.SimIP = Utils.BytesToUInt(entry.AddressList[0].GetAddressBytes()); + teleport.Info.SimPort = (ushort)link.UDPPort; + teleport.Info.TeleportFlags = (uint)TeleportFlags.ViaLocation; + + server.UDP.SendPacket(agent.Avatar.ID, teleport, PacketCategory.Transaction); + + // Remove the agent from the local scene (will also tear down the UDP connection) + //server.Scene.ObjectRemove(this, agent.Avatar.ID); + + return true; + } + } + } + } + + return false; + } + + bool LinkRegion(Uri destination, out HyperGridLink link) + { + try + { + #region Build Request + + HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(destination); + request.Method = "POST"; + request.ContentType = "text/xml"; + + MemoryStream memoryStream = new MemoryStream(); + using (XmlWriter writer = XmlWriter.Create(memoryStream)) + { + writer.WriteStartElement("methodCall"); + { + writer.WriteElementString("methodName", "link_region"); + + writer.WriteStartElement("params"); + writer.WriteStartElement("param"); + writer.WriteStartElement("value"); + writer.WriteStartElement("struct"); + { + WriteStringMember(writer, "region_name", String.Empty); + } + writer.WriteEndElement(); + writer.WriteEndElement(); + writer.WriteEndElement(); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + + writer.Flush(); + } + + request.ContentLength = memoryStream.Length; + + using (Stream writeStream = request.GetRequestStream()) + { + writeStream.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); + } + + #endregion Build Request + + #region Parse Response + + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + + XmlReaderSettings settings = new XmlReaderSettings(); + settings.IgnoreComments = true; + settings.IgnoreWhitespace = true; + + using (XmlReader reader = XmlReader.Create(response.GetResponseStream(), settings)) + { + link = new HyperGridLink(); + + reader.ReadStartElement("methodResponse"); + { + reader.ReadStartElement("params"); + reader.ReadStartElement("param"); + reader.ReadStartElement("value"); + reader.ReadStartElement("struct"); + { + while (reader.Name == "member") + { + reader.ReadStartElement("member"); + { + string name = reader.ReadElementContentAsString("name", String.Empty); + + reader.ReadStartElement("value"); + { + switch (name) + { + case "region_name": + link.RegionName = reader.ReadElementContentAsString("string", String.Empty); + break; + case "handle": + string handle = reader.ReadElementContentAsString("string", String.Empty); + link.RegionHandle = UInt64.Parse(handle); + break; + case "uuid": + string uuid = reader.ReadElementContentAsString("string", String.Empty); + link.RegionID = UUID.Parse(uuid); + break; + case "internal_port": + link.UDPPort = reader.ReadElementContentAsInt("string", String.Empty); + break; + case "region_image": + string imageuuid = reader.ReadElementContentAsString("string", String.Empty); + link.RegionImage = UUID.Parse(imageuuid); + break; + case "remoting_port": + link.RemotingPort = reader.ReadElementContentAsInt("string", String.Empty); + break; + default: + Logger.Log("[HyperGrid] Unrecognized response XML chunk: " + reader.ReadInnerXml(), + Helpers.LogLevel.Warning); + break; + } + } + reader.ReadEndElement(); + } + reader.ReadEndElement(); + } + } + reader.ReadEndElement(); + reader.ReadEndElement(); + reader.ReadEndElement(); + reader.ReadEndElement(); + } + reader.ReadEndElement(); + + return true; + } + + #endregion Parse Response + } + catch (Exception ex) + { + Logger.Log(ex.Message, Helpers.LogLevel.Error, ex); + } + + link = null; + return false; + } + + bool ExpectHyperGridUser(Agent agent, Uri destination, Vector3 destPos, HyperGridLink link) + { + try + { + #region Build Request + + HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(destination); + request.Method = "POST"; + request.ContentType = "text/xml"; + + MemoryStream memoryStream = new MemoryStream(); + using (XmlWriter writer = XmlWriter.Create(memoryStream)) + { + writer.WriteStartElement("methodCall"); + { + writer.WriteElementString("methodName", "expect_hg_user"); + + writer.WriteStartElement("params"); + writer.WriteStartElement("param"); + writer.WriteStartElement("value"); + writer.WriteStartElement("struct"); + { + WriteStringMember(writer, "session_id", agent.SessionID.ToString()); + WriteStringMember(writer, "secure_session_id", agent.SecureSessionID.ToString()); + WriteStringMember(writer, "firstname", agent.FirstName); + WriteStringMember(writer, "lastname", agent.LastName); + WriteStringMember(writer, "agent_id", agent.Avatar.ID.ToString()); + WriteStringMember(writer, "circuit_code", agent.CircuitCode.ToString()); + WriteStringMember(writer, "startpos_x", destPos.X.ToString(Utils.EnUsCulture)); + WriteStringMember(writer, "startpos_y", destPos.Y.ToString(Utils.EnUsCulture)); + WriteStringMember(writer, "startpos_z", destPos.Z.ToString(Utils.EnUsCulture)); + WriteStringMember(writer, "caps_path", String.Empty); + + WriteStringMember(writer, "region_uuid", link.RegionID.ToString()); + //WriteStringMember(writer, "userserver_id", ""); + //WriteStringMember(writer, "assetserver_id", ""); + //WriteStringMember(writer, "inventoryserver_id", ""); + WriteStringMember(writer, "root_folder_id", agent.InventoryRoot.ToString()); + + WriteStringMember(writer, "internal_port", server.HttpPort.ToString()); + WriteStringMember(writer, "regionhandle", link.RegionHandle.ToString()); + WriteStringMember(writer, "home_address", IPAddress.Loopback.ToString()); + WriteStringMember(writer, "home_port", server.HttpPort.ToString()); + WriteStringMember(writer, "home_remoting", server.HttpPort.ToString()); + } + writer.WriteEndElement(); + writer.WriteEndElement(); + writer.WriteEndElement(); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + + writer.Flush(); + } + + request.ContentLength = memoryStream.Length; + + using (Stream writeStream = request.GetRequestStream()) + { + writeStream.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); + } + + #endregion Build Request + + #region Parse Response + + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + + XmlReaderSettings settings = new XmlReaderSettings(); + settings.IgnoreComments = true; + settings.IgnoreWhitespace = true; + + using (XmlReader reader = XmlReader.Create(response.GetResponseStream(), settings)) + { + bool success = false; + string reason = String.Empty; + + reader.ReadStartElement("methodResponse"); + { + reader.ReadStartElement("params"); + reader.ReadStartElement("param"); + reader.ReadStartElement("value"); + reader.ReadStartElement("struct"); + { + while (reader.Name == "member") + { + reader.ReadStartElement("member"); + { + string name = reader.ReadElementContentAsString("name", String.Empty); + + reader.ReadStartElement("value"); + { + switch (name) + { + case "success": + success = (reader.ReadElementContentAsString("string", String.Empty).ToUpper() == "TRUE"); + break; + case "reason": + reason = reader.ReadElementContentAsString("string", String.Empty); + break; + default: + Logger.Log("[HyperGrid] Unrecognized response XML chunk: " + reader.ReadInnerXml(), + Helpers.LogLevel.Warning); + break; + } + } + reader.ReadEndElement(); + } + reader.ReadEndElement(); + } + } + reader.ReadEndElement(); + reader.ReadEndElement(); + reader.ReadEndElement(); + reader.ReadEndElement(); + } + reader.ReadEndElement(); + + if (!success) + Logger.Log("[HyperGrid] Teleport failed, reason: " + reason, Helpers.LogLevel.Warning); + + return success; + } + + #endregion Parse Response + } + catch (Exception ex) + { + Logger.Log(ex.Message, Helpers.LogLevel.Error, ex); + } + + return false; + } + + bool CreateChildAgent(Agent agent, Uri destination, Vector3 destPos, HyperGridLink link, string seedCapFragment) + { + try + { + destination = new Uri(destination, "/agent/" + agent.Avatar.ID.ToString() + "/"); + + OSDMap args = new OSDMap(); + args["agent_id"] = OSD.FromUUID(agent.Avatar.ID); + args["base_folder"] = OSD.FromUUID(UUID.Zero); + args["caps_path"] = OSD.FromString(seedCapFragment); + args["children_seeds"] = OSD.FromBoolean(false); + args["child"] = OSD.FromBoolean(false); + args["circuit_code"] = OSD.FromString(agent.CircuitCode.ToString()); + args["first_name"] = OSD.FromString(agent.FirstName); + args["last_name"] = OSD.FromString(agent.LastName); + args["inventory_folder"] = OSD.FromUUID(agent.InventoryRoot); + args["secure_session_id"] = OSD.FromUUID(agent.SecureSessionID); + args["session_id"] = OSD.FromUUID(agent.SessionID); + args["start_pos"] = OSD.FromString(destPos.ToString()); + args["destination_handle"] = OSD.FromString(link.RegionHandle.ToString()); + + LitJson.JsonData jsonData = OSDParser.SerializeJson(args); + byte[] data = System.Text.Encoding.UTF8.GetBytes(jsonData.ToJson()); + + HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(destination); + request.Method = "POST"; + request.ContentType = "application/json"; + request.ContentLength = data.Length; + + using (Stream requestStream = request.GetRequestStream()) + { + requestStream.Write(data, 0, data.Length); + requestStream.Flush(); + } + + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + + bool success = false; + using (StreamReader reader = new StreamReader(response.GetResponseStream())) + { + Boolean.TryParse(reader.ReadToEnd(), out success); + } + + return success; + } + catch (Exception ex) + { + Logger.Log(ex.Message, Helpers.LogLevel.Error, ex); + } + + return false; + } + + void TeleportProgress(Agent agent, string message, TeleportFlags flags) + { + TeleportProgressPacket progress = new TeleportProgressPacket(); + progress.AgentData.AgentID = agent.Avatar.ID; + progress.Info.Message = Utils.StringToBytes(message); + progress.Info.TeleportFlags = (uint)flags; + + server.UDP.SendPacket(agent.Avatar.ID, progress, PacketCategory.Transaction); + } + + static void WriteStringMember(XmlWriter writer, string name, string value) + { + writer.WriteStartElement("member"); + { + writer.WriteElementString("name", name); + + writer.WriteStartElement("value"); + { + writer.WriteElementString("string", value); + } + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } } } diff --git a/Programs/Simian/Extensions/SceneManager.cs b/Programs/Simian/Extensions/SceneManager.cs index 0019ecec..5ee7cf6c 100644 --- a/Programs/Simian/Extensions/SceneManager.cs +++ b/Programs/Simian/Extensions/SceneManager.cs @@ -3,22 +3,37 @@ using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; +using System.Net; using System.Runtime.InteropServices; using System.Threading; using ExtensionLoader; +using HttpServer; using OpenMetaverse; using OpenMetaverse.Imaging; using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; +using OpenMetaverse.Http; namespace Simian.Extensions { - + class EventQueueServerCap + { + public EventQueueServer Server; + public Uri Capability; + + public EventQueueServerCap(EventQueueServer server, Uri capability) + { + Server = server; + Capability = capability; + } + } public class SceneManager : IExtension, ISceneProvider { Simian server; DoubleDictionary sceneObjects = new DoubleDictionary(); DoubleDictionary sceneAgents = new DoubleDictionary(); + Dictionary eventQueues = new Dictionary(); int currentLocalID = 1; ulong regionHandle; UUID regionID = UUID.Random(); @@ -57,11 +72,19 @@ namespace Simian.Extensions this.server = server; server.UDP.RegisterPacketCallback(PacketType.CompleteAgentMovement, new PacketCallback(CompleteAgentMovementHandler)); - LoadTerrain(server.DataDir + "heightmap.tga"); + LoadTerrain(Simian.DATA_DIR + "heightmap.tga"); } public void Stop() { + lock (eventQueues) + { + foreach (EventQueueServerCap eventQueue in eventQueues.Values) + { + server.Capabilities.RemoveCapability(eventQueue.Capability); + eventQueue.Server.Stop(); + } + } } public float[,] GetTerrainPatch(uint x, uint y) @@ -150,14 +173,7 @@ namespace Simian.Extensions if (OnAgentRemove != null) OnAgentRemove(sender, agent); - sceneAgents.Remove(agent.Avatar.LocalID, agent.Avatar.ID); - - KillObjectPacket kill = new KillObjectPacket(); - kill.ObjectData = new KillObjectPacket.ObjectDataBlock[1]; - kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock(); - kill.ObjectData[0].ID = agent.Avatar.LocalID; - - server.UDP.BroadcastPacket(kill, PacketCategory.State); + AgentRemove(agent); return true; } else @@ -193,14 +209,7 @@ namespace Simian.Extensions if (OnAgentRemove != null) OnAgentRemove(sender, agent); - sceneAgents.Remove(agent.Avatar.LocalID, agent.Avatar.ID); - - KillObjectPacket kill = new KillObjectPacket(); - kill.ObjectData = new KillObjectPacket.ObjectDataBlock[1]; - kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock(); - kill.ObjectData[0].ID = agent.Avatar.LocalID; - - server.UDP.BroadcastPacket(kill, PacketCategory.State); + AgentRemove(agent); return true; } else @@ -210,6 +219,33 @@ namespace Simian.Extensions } } + void AgentRemove(Agent agent) + { + Logger.Log("Removing agent " + agent.FullName + " from the scene", Helpers.LogLevel.Info); + + sceneAgents.Remove(agent.Avatar.LocalID, agent.Avatar.ID); + + KillObjectPacket kill = new KillObjectPacket(); + kill.ObjectData = new KillObjectPacket.ObjectDataBlock[1]; + kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock(); + kill.ObjectData[0].ID = agent.Avatar.LocalID; + + server.UDP.BroadcastPacket(kill, PacketCategory.State); + + // Kill the EventQueue + RemoveEventQueue(agent.Avatar.ID); + + // Remove the UDP client + server.UDP.RemoveClient(agent); + + // Notify everyone in the scene that this agent has gone offline + OfflineNotificationPacket offline = new OfflineNotificationPacket(); + offline.AgentBlock = new OfflineNotificationPacket.AgentBlockBlock[1]; + offline.AgentBlock[0] = new OfflineNotificationPacket.AgentBlockBlock(); + offline.AgentBlock[0].AgentID = agent.Avatar.ID; + server.UDP.BroadcastPacket(offline, PacketCategory.State); + } + public void ObjectTransform(object sender, uint localID, Vector3 position, Quaternion rotation, Vector3 velocity, Vector3 acceleration, Vector3 angularVelocity) { @@ -420,6 +456,87 @@ namespace Simian.Extensions sceneAgents.ForEach(action); } + public bool SeedCapabilityHandler(IHttpClientContext context, IHttpRequest request, IHttpResponse response, object state) + { + UUID agentID = (UUID)state; + + OSDArray array = OSDParser.DeserializeLLSDXml(request.Body) as OSDArray; + if (array != null) + { + OSDMap osdResponse = new OSDMap(); + + for (int i = 0; i < array.Count; i++) + { + string capName = array[i].AsString(); + + switch (capName) + { + case "EventQueueGet": + Uri eqCap = null; + + // Check if this agent already has an event queue + EventQueueServerCap eqServer; + if (eventQueues.TryGetValue(agentID, out eqServer)) + eqCap = eqServer.Capability; + + // If not, create one + if (eqCap == null) + eqCap = CreateEventQueue(agentID); + + osdResponse.Add("EventQueueGet", OSD.FromUri(eqCap)); + break; + } + } + + byte[] responseData = OSDParser.SerializeLLSDXmlBytes(osdResponse); + response.ContentLength = responseData.Length; + response.Body.Write(responseData, 0, responseData.Length); + response.Body.Flush(); + } + else + { + response.Status = HttpStatusCode.BadRequest; + } + + return true; + } + + public Uri CreateEventQueue(UUID agentID) + { + EventQueueServer eqServer = new EventQueueServer(server.HttpServer); + EventQueueServerCap eqServerCap = new EventQueueServerCap(eqServer, + server.Capabilities.CreateCapability(EventQueueHandler, false, eqServer)); + + eventQueues.Add(agentID, eqServerCap); + + return eqServerCap.Capability; + } + + public bool RemoveEventQueue(UUID agentID) + { + return eventQueues.Remove(agentID); + } + + public void SendEvent(Agent agent, string name, OSDMap body) + { + EventQueueServerCap eventQueue; + if (eventQueues.TryGetValue(agent.Avatar.ID, out eventQueue)) + { + eventQueue.Server.SendEvent(name, body); + } + else + { + Logger.Log(String.Format("Cannot send the event {0} to agent {1}, no event queue for that avatar", + name, agent.FullName), Helpers.LogLevel.Warning); + } + } + + bool EventQueueHandler(IHttpClientContext context, IHttpRequest request, IHttpResponse response, object state) + { + EventQueueServer eqServer = (EventQueueServer)state; + return eqServer.EventQueueHandler(context, request, response); + } + void BroadcastObjectUpdate(Primitive prim) { ObjectUpdatePacket update = diff --git a/Programs/Simian/Extensions/UDPManager.cs b/Programs/Simian/Extensions/UDPManager.cs index ad08a93e..8c4132bc 100644 --- a/Programs/Simian/Extensions/UDPManager.cs +++ b/Programs/Simian/Extensions/UDPManager.cs @@ -186,7 +186,7 @@ namespace Simian lock (unassociatedAgents) unassociatedAgents[circuitCode] = agent; - Logger.Log("Created a circuit for " + agent.FirstName, Helpers.LogLevel.Info); + Logger.Log("Created circuit " + circuitCode + " for " + agent.FirstName, Helpers.LogLevel.Info); return circuitCode; } @@ -411,7 +411,7 @@ namespace Simian Logger.Log(String.Format("Ack timeout for {0}, disconnecting", client.Agent.Avatar.Name), Helpers.LogLevel.Warning); - server.Avatars.Disconnect(client.Agent); + server.Scene.ObjectRemove(this, client.Agent.Avatar.ID); return; } } diff --git a/Programs/Simian/Extensions/XMLPersistence.cs b/Programs/Simian/Extensions/XMLPersistence.cs index f3b60f65..62fc5c1c 100644 --- a/Programs/Simian/Extensions/XMLPersistence.cs +++ b/Programs/Simian/Extensions/XMLPersistence.cs @@ -24,7 +24,7 @@ namespace Simian.Extensions try { - XmlTextReader reader = new XmlTextReader(File.OpenRead(server.DataDir + "simiandata.xml")); + XmlTextReader reader = new XmlTextReader(File.OpenRead(Simian.DATA_DIR + "simiandata.xml")); osd = OSDParser.DeserializeLLSDXml(reader); reader.Close(); } @@ -76,7 +76,7 @@ namespace Simian.Extensions try { - XmlTextWriter writer = new XmlTextWriter(server.DataDir + "simiandata.xml", System.Text.Encoding.UTF8); + XmlTextWriter writer = new XmlTextWriter(Simian.DATA_DIR + "simiandata.xml", System.Text.Encoding.UTF8); writer.Formatting = Formatting.Indented; writer.WriteStartElement("llsd"); OSDParser.SerializeLLSDXmlElement(writer, dictionary); diff --git a/Programs/Simian/Interfaces/IAvatarProvider.cs b/Programs/Simian/Interfaces/IAvatarProvider.cs index 05acfccf..e6046acd 100644 --- a/Programs/Simian/Interfaces/IAvatarProvider.cs +++ b/Programs/Simian/Interfaces/IAvatarProvider.cs @@ -10,7 +10,6 @@ namespace Simian bool RemoveAnimation(Agent agent, UUID animID); bool ClearAnimations(Agent agent); void SendAnimations(Agent agent); - void Disconnect(Agent agent); void SendAlert(Agent agent, string message); } diff --git a/Programs/Simian/Interfaces/ICapabilitiesProvider.cs b/Programs/Simian/Interfaces/ICapabilitiesProvider.cs index d37b7f59..63525464 100644 --- a/Programs/Simian/Interfaces/ICapabilitiesProvider.cs +++ b/Programs/Simian/Interfaces/ICapabilitiesProvider.cs @@ -8,10 +8,8 @@ namespace Simian { public interface ICapabilitiesProvider { - UUID CreateCapability(HttpRequestCallback localHandler, bool clientCertRequired); - UUID CreateCapability(Uri remoteHandler, bool clientCertRequired); - bool RemoveCapability(UUID capID); - - void SendEvent(Agent agent, string name, OSDMap body); + Uri CreateCapability(CapsRequestCallback localHandler, bool clientCertRequired, object state); + Uri CreateCapability(Uri remoteHandler, bool clientCertRequired); + bool RemoveCapability(Uri cap); } } diff --git a/Programs/Simian/Interfaces/ISceneProvider.cs b/Programs/Simian/Interfaces/ISceneProvider.cs index a44891d7..23a6bb8b 100644 --- a/Programs/Simian/Interfaces/ISceneProvider.cs +++ b/Programs/Simian/Interfaces/ISceneProvider.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using HttpServer; using OpenMetaverse; +using OpenMetaverse.StructuredData; namespace Simian { @@ -76,5 +78,8 @@ namespace Simian bool TryGetAgent(uint localID, out Agent agent); bool TryGetAgent(UUID id, out Agent agent); void ForEachAgent(Action action); + + void SendEvent(Agent agent, string name, OSDMap body); + bool SeedCapabilityHandler(IHttpClientContext context, IHttpRequest request, IHttpResponse response, object state); } } diff --git a/Programs/Simian/Simian.cs b/Programs/Simian/Simian.cs index 67dcf3e4..eaeff543 100644 --- a/Programs/Simian/Simian.cs +++ b/Programs/Simian/Simian.cs @@ -6,6 +6,7 @@ using System.Text; using System.Xml; using System.Threading; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using ExtensionLoader; using ExtensionLoader.Config; using HttpServer; @@ -17,10 +18,12 @@ namespace Simian public partial class Simian { public const string CONFIG_FILE = "Simian.ini"; + public const string DATA_DIR = "SimianData/"; - public int UDPPort = 9000; - public int HttpPort = 8002; - public string DataDir = "SimianData/"; + public int UDPPort { get { return 9000; } } + public int HttpPort { get { return 8002; } } + public bool SSL { get { return false; } } + public string HostName { get { return Dns.GetHostName(); } } public WebServer HttpServer; public IniConfigSource ConfigFile; @@ -35,7 +38,7 @@ namespace Simian public IInventoryProvider Inventory; public IParcelProvider Parcels; public IMeshingProvider Mesher; - //public ICapabilitiesProvider Capabilities; + public ICapabilitiesProvider Capabilities; // Persistent extensions public List PersistentExtensions = new List(); @@ -61,7 +64,9 @@ namespace Simian return false; } - InitHttpServer(HttpPort, true); + // TODO: SSL support + HttpServer = new WebServer(IPAddress.Any, HttpPort); + HttpServer.Start(); try { @@ -123,12 +128,5 @@ namespace Simian HttpServer.Stop(); } - - void InitHttpServer(int port, bool ssl) - { - HttpServer = new WebServer(IPAddress.Any, port); - - HttpServer.Start(); - } } } diff --git a/bin/Simian.ini b/bin/Simian.ini index 91afc55c..47f6d92f 100644 --- a/bin/Simian.ini +++ b/bin/Simian.ini @@ -36,6 +36,10 @@ InventoryManager ; ---End Local Simulator Stores--- ; +; Manages creation and deletion of capabilities (see +; http://wiki.secondlife.com/wiki/Capabilities for more information). +CapsManager + ; Various avatar-related functions including appearance, animations and sounds AvatarManager