diff --git a/OpenMetaverse/Capabilities/EventQueueListener.cs b/OpenMetaverse/Capabilities/EventQueueListener.cs
deleted file mode 100644
index ce1c0e6d..00000000
--- a/OpenMetaverse/Capabilities/EventQueueListener.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (c) 2007-2008, openmetaverse.org
- * All rights reserved.
- *
- * - Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- * - Neither the name of the openmetaverse.org nor the names
- * of its contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-using System;
-using System.Net;
-
-#if !PocketPC
-
-namespace OpenMetaverse.Capabilities
-{
- public class EventQueueListener
- {
- }
-}
-
-#endif
diff --git a/OpenMetaverse/Capabilities/CapsListener.cs b/OpenMetaverse/Capabilities/EventQueueServer.cs
similarity index 92%
rename from OpenMetaverse/Capabilities/CapsListener.cs
rename to OpenMetaverse/Capabilities/EventQueueServer.cs
index 8bc0199f..ea1cfccc 100644
--- a/OpenMetaverse/Capabilities/CapsListener.cs
+++ b/OpenMetaverse/Capabilities/EventQueueServer.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2007-2008, openmetaverse.org
+ * Copyright (c) 2008, openmetaverse.org
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
@@ -31,7 +31,7 @@ using System.Net;
namespace OpenMetaverse.Capabilities
{
- public class CapsListener
+ public class EventQueueServer
{
}
}
diff --git a/OpenMetaverse/Capabilities/HttpRequestSignature.cs b/OpenMetaverse/Capabilities/HttpRequestSignature.cs
new file mode 100644
index 00000000..3214a60a
--- /dev/null
+++ b/OpenMetaverse/Capabilities/HttpRequestSignature.cs
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2008, openmetaverse.org
+ * All rights reserved.
+ *
+ * - Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Neither the name of the openmetaverse.org nor the names
+ * of its contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+using System;
+using System.Net;
+using System.Text.RegularExpressions;
+
+namespace OpenMetaverse.Capabilities
+{
+ ///
+ /// Used to match incoming HTTP requests against registered handlers.
+ /// Matches based on any combination of HTTP Method, Content-Type header,
+ /// and URL path. URL path matching supports the * wildcard character
+ ///
+ public struct HttpRequestSignature : IEquatable
+ {
+ private string method;
+ private string contentType;
+ private string path;
+ private bool pathIsWildcard;
+
+ public string Method
+ {
+ get { return method; }
+ set
+ {
+ if (!String.IsNullOrEmpty(value)) method = value.ToLower();
+ else method = String.Empty;
+ }
+ }
+ public string ContentType
+ {
+ get { return contentType; }
+ set
+ {
+ if (!String.IsNullOrEmpty(value)) contentType = value.ToLower();
+ else contentType = String.Empty;
+ }
+ }
+ public string Path
+ {
+ get { return path; }
+ set
+ {
+ pathIsWildcard = false;
+
+ if (!String.IsNullOrEmpty(value))
+ {
+ // Regex to tear apart URLs, used to extract just a URL
+ // path from any data we're given
+ string regexPattern =
+ @"^(?(?[^:/\?#]+):)?(?"
+ + @"//(?[^/\?#]*))?(?[^\?#]*)"
+ + @"(?\?(?[^#]*))?"
+ + @"(?#(?.*))?";
+ Regex re = new Regex(regexPattern, RegexOptions.ExplicitCapture);
+ Match m = re.Match(value);
+ string newPath = m.Groups["p0"].Value.ToLower();
+
+ // Remove any trailing forward-slashes
+ if (newPath.EndsWith("/"))
+ newPath = newPath.Substring(0, newPath.Length - 1);
+
+ // Check if this path contains a wildcard. If so, convert it to a regular expression
+ if (newPath.Contains("*"))
+ {
+ pathIsWildcard = true;
+ newPath = String.Format("^{0}$", newPath.Replace("\\*", ".*"));
+ }
+
+ path = newPath;
+ }
+ else
+ {
+ path = String.Empty;
+ }
+ }
+ }
+ public bool PathIsWildcard
+ {
+ get { return pathIsWildcard; }
+ }
+
+ public HttpRequestSignature(HttpListenerContext context)
+ {
+ method = contentType = path = String.Empty;
+ pathIsWildcard = false;
+
+ Method = context.Request.HttpMethod;
+ ContentType = context.Request.ContentType;
+ Path = context.Request.RawUrl;
+ }
+
+ public bool ExactlyEquals(HttpRequestSignature signature)
+ {
+ return (method.Equals(signature.Method) && contentType.Equals(signature.ContentType) && path.Equals(signature.Path));
+ }
+
+ public override bool Equals(object obj)
+ {
+ return (obj is HttpRequestSignature) ? this == (HttpRequestSignature)obj : false;
+ }
+
+ public bool Equals(HttpRequestSignature signature)
+ {
+ return (this == signature);
+ }
+
+ public override int GetHashCode()
+ {
+ int hash = (method != null) ? method.GetHashCode() : 0;
+ hash ^= (contentType != null) ? contentType.GetHashCode() : 0;
+ hash ^= (path != null) ? path.GetHashCode() : 0;
+ return hash;
+ }
+
+ public static bool operator ==(HttpRequestSignature lhs, HttpRequestSignature rhs)
+ {
+ bool methodMatch = (String.IsNullOrEmpty(lhs.Method) || String.IsNullOrEmpty(rhs.Method) || lhs.Method.Equals(rhs.Method));
+ bool contentTypeMatch = (String.IsNullOrEmpty(lhs.ContentType) || String.IsNullOrEmpty(rhs.ContentType) || lhs.ContentType.Equals(rhs.ContentType));
+ bool pathMatch = false;
+
+ if (methodMatch && contentTypeMatch)
+ {
+ if (!String.IsNullOrEmpty(lhs.Path) && !String.IsNullOrEmpty(rhs.Path))
+ {
+ // Do wildcard matching if there is any to be done
+ if (lhs.PathIsWildcard)
+ pathMatch = Regex.IsMatch(rhs.Path, lhs.Path);
+ else if (rhs.PathIsWildcard)
+ pathMatch = Regex.IsMatch(lhs.Path, rhs.Path);
+ else
+ pathMatch = lhs.Path.Equals(rhs.Path);
+ }
+ else
+ {
+ pathMatch = true;
+ }
+ }
+
+ return (methodMatch && contentTypeMatch && pathMatch);
+ }
+
+ public static bool operator !=(HttpRequestSignature lhs, HttpRequestSignature rhs)
+ {
+ return !(lhs == rhs);
+ }
+ }
+}
diff --git a/OpenMetaverse/Capabilities/HttpServer.cs b/OpenMetaverse/Capabilities/HttpServer.cs
new file mode 100644
index 00000000..221ae406
--- /dev/null
+++ b/OpenMetaverse/Capabilities/HttpServer.cs
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2008, openmetaverse.org
+ * All rights reserved.
+ *
+ * - Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Neither the name of the openmetaverse.org nor the names
+ * of its contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+namespace OpenMetaverse.Capabilities
+{
+ public class HttpServer
+ {
+ public delegate void HttpRequestCallback(ref HttpListenerContext context);
+
+ public struct HttpRequestHandler : IEquatable
+ {
+ public HttpRequestSignature Signature;
+ public HttpRequestCallback Callback;
+
+ public HttpRequestHandler(HttpRequestSignature signature, HttpRequestCallback callback)
+ {
+ Signature = signature;
+ Callback = callback;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return (obj is HttpRequestHandler) ? this.Signature == ((HttpRequestHandler)obj).Signature : false;
+ }
+
+ public override int GetHashCode()
+ {
+ return Signature.GetHashCode();
+ }
+
+ public bool Equals(HttpRequestHandler handler)
+ {
+ return this.Signature == handler.Signature;
+ }
+ }
+
+ HttpListener server;
+ AsyncCallback serverCallback;
+ int serverPort;
+ bool sslEnabled;
+ List requestHandlers;
+
+ bool isRunning;
+
+ public HttpServer(int port, bool ssl)
+ {
+ serverPort = port;
+ sslEnabled = ssl;
+ server = new HttpListener();
+ serverCallback = new AsyncCallback(BeginGetContextCallback);
+
+ if (ssl)
+ server.Prefixes.Add(String.Format("https://+:{0}/", port));
+ else
+ server.Prefixes.Add(String.Format("http://+:{0}/", port));
+
+ requestHandlers = new List();
+
+ isRunning = false;
+ }
+
+ public void AddHandler(HttpRequestHandler handler)
+ {
+ if (!isRunning)
+ requestHandlers.Add(handler);
+ else
+ throw new InvalidOperationException("Cannot add HTTP request handlers while the server is running");
+ }
+
+ public void RemoveHandler(HttpRequestHandler handler)
+ {
+ if (!isRunning)
+ requestHandlers.Remove(handler);
+ else
+ throw new InvalidOperationException("Cannot add HTTP request handlers while the server is running");
+ }
+
+ public void Start()
+ {
+ server.Start();
+ server.BeginGetContext(serverCallback, server);
+ isRunning = true;
+ }
+
+ public void Stop()
+ {
+ isRunning = false;
+ try { server.Stop(); }
+ catch (ObjectDisposedException) { }
+ }
+
+ protected void BeginGetContextCallback(IAsyncResult result)
+ {
+ HttpListenerContext context = null;
+
+ // Retrieve the incoming request
+ try { context = server.EndGetContext(result); }
+ catch (Exception)
+ {
+ }
+
+ if (isRunning)
+ {
+ // Immediately start listening again
+ try { server.BeginGetContext(serverCallback, server); }
+ catch (Exception)
+ {
+ // Something went wrong, can't resume listening. Bail out now
+ // since this is a shutdown (whether it was meant to be or not)
+ return;
+ }
+
+ // Process the incoming request
+ if (context != null)
+ {
+ // Create a request signature
+ HttpRequestSignature signature = new HttpRequestSignature(context);
+
+ // Look for a signature match in our handlers
+ for (int i = 0; i < requestHandlers.Count; i++)
+ {
+ HttpRequestHandler handler = requestHandlers[i];
+
+ if (handler.Signature == signature)
+ {
+ // Request signature matched, handle it
+ try
+ {
+ handler.Callback(ref context);
+ }
+ catch (Exception e)
+ {
+ context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+ context.Response.StatusDescription = e.ToString();
+ }
+
+ context.Response.Close();
+ return;
+ }
+ }
+
+ // No registered handler matched this request's signature. Send a 404
+ context.Response.StatusCode = (int)HttpStatusCode.NotFound;
+ context.Response.StatusDescription = String.Format(
+ "No request handler registered for Method=\"{0}\", Content-Type=\"{1}\", Path=\"{2}\"",
+ signature.Method, signature.ContentType, signature.Path);
+ context.Response.Close();
+ }
+ }
+ }
+ }
+}
diff --git a/OpenMetaverse/EventDictionary.cs b/OpenMetaverse/EventDictionary.cs
index 0288003f..c724fbc6 100644
--- a/OpenMetaverse/EventDictionary.cs
+++ b/OpenMetaverse/EventDictionary.cs
@@ -110,18 +110,28 @@ namespace OpenMetaverse
{
NetworkManager.PacketCallback callback;
- if (_EventTable.TryGetValue(packetType, out callback))
+ // Default handler first, if one exists
+ if (_EventTable.TryGetValue(PacketType.Default, out callback))
{
- try
- {
- callback(packet, simulator);
- }
+ try { callback(packet, simulator); }
catch (Exception ex)
{
- Logger.Log("Packet Event Handler: " + ex.ToString(), Helpers.LogLevel.Error, Client);
+ Logger.Log("Default packet event handler: " + ex.ToString(), Helpers.LogLevel.Error, Client);
}
}
- else if (packetType != PacketType.Default && packetType != PacketType.PacketAck)
+
+ if (_EventTable.TryGetValue(packetType, out callback))
+ {
+ try { callback(packet, simulator); }
+ catch (Exception ex)
+ {
+ Logger.Log("Packet event handler: " + ex.ToString(), Helpers.LogLevel.Error, Client);
+ }
+
+ return;
+ }
+
+ if (packetType != PacketType.Default && packetType != PacketType.PacketAck)
{
Logger.DebugLog("No handler registered for packet event " + packetType, Client);
}
@@ -136,12 +146,24 @@ namespace OpenMetaverse
internal void BeginRaiseEvent(PacketType packetType, Packet packet, Simulator simulator)
{
NetworkManager.PacketCallback callback;
+ PacketCallbackWrapper wrapper;
+
+ // Default handler first, if one exists
+ if (_EventTable.TryGetValue(PacketType.Default, out callback))
+ {
+ if (callback != null)
+ {
+ wrapper.Callback = callback;
+ wrapper.Packet = packet;
+ wrapper.Simulator = simulator;
+ ThreadPool.QueueUserWorkItem(_ThreadPoolCallback, wrapper);
+ }
+ }
if (_EventTable.TryGetValue(packetType, out callback))
{
if (callback != null)
{
- PacketCallbackWrapper wrapper;
wrapper.Callback = callback;
wrapper.Packet = packet;
wrapper.Simulator = simulator;
diff --git a/OpenMetaverse/Login.cs b/OpenMetaverse/Login.cs
index 297aaaa4..51b0739c 100644
--- a/OpenMetaverse/Login.cs
+++ b/OpenMetaverse/Login.cs
@@ -29,6 +29,7 @@ using System.Collections.Generic;
using System.Threading;
using System.IO;
using System.Net;
+using System.Xml;
using System.Security.Cryptography.X509Certificates;
using OpenMetaverse.StructuredData;
using OpenMetaverse.Capabilities;
@@ -100,6 +101,9 @@ namespace OpenMetaverse
public struct LoginResponseData
{
+ public bool Success;
+ public string Reason;
+ public string Message;
public UUID AgentID;
public UUID SessionID;
public UUID SecureSessionID;
@@ -140,9 +144,7 @@ namespace OpenMetaverse
}
catch (LLSDException e)
{
- // FIXME: sometimes look_at comes back with invalid values e.g: 'look_at':'[r1,r2.0193899999999998204e-06,r0]'
- // need to handle that somehow
- Logger.DebugLog("login server returned (some) invalid data: " + e.Message);
+ Logger.DebugLog("Login server returned (some) invalid data: " + e.Message);
}
// Home
@@ -209,6 +211,167 @@ namespace OpenMetaverse
LibraryFolders = ParseInventoryFolders("inventory-skel-lib", LibraryOwner, reply);
}
+ public void ToXmlRpc(XmlWriter writer)
+ {
+ writer.WriteStartElement("methodResponse");
+ {
+ writer.WriteStartElement("params");
+ writer.WriteStartElement("param");
+ writer.WriteStartElement("value");
+ writer.WriteStartElement("struct");
+ {
+ if (Success)
+ {
+ // session_id
+ WriteXmlRpcStringMember(writer, false, "session_id", SessionID.ToString());
+
+ // ui-config
+ WriteXmlRpcArrayStart(writer, "ui-config");
+ WriteXmlRpcStringMember(writer, true, "allow_first_life", "Y");
+ WriteXmlRpcArrayEnd(writer);
+
+ // inventory-lib-owner
+ WriteXmlRpcArrayStart(writer, "inventory-lib-owner");
+ WriteXmlRpcStringMember(writer, true, "agent_id", LibraryOwner.ToString());
+ WriteXmlRpcArrayEnd(writer);
+
+ // start_location
+ WriteXmlRpcStringMember(writer, false, "start_location", StartLocation);
+
+ // seconds_since_epoch
+ WriteXmlRpcIntMember(writer, false, "seconds_since_epoch", Utils.DateTimeToUnixTime(SecondsSinceEpoch));
+
+ // event_categories
+ WriteXmlRpcArrayStart(writer, "event_categories");
+ writer.WriteStartElement("struct"); writer.WriteEndElement();
+ WriteXmlRpcArrayEnd(writer);
+
+ // classified_categories
+ WriteXmlRpcArrayStart(writer, "classified_categories");
+ writer.WriteStartElement("struct"); writer.WriteEndElement();
+ WriteXmlRpcArrayEnd(writer);
+
+ // inventory-root
+ WriteXmlRpcArrayStart(writer, "inventory-root");
+ WriteXmlRpcStringMember(writer, true, "folder_id", InventoryRoot.ToString());
+ WriteXmlRpcArrayEnd(writer);
+
+ // sim_port
+ WriteXmlRpcIntMember(writer, false, "sim_port", SimPort);
+
+ // agent_id
+ WriteXmlRpcStringMember(writer, false, "agent_id", AgentID.ToString());
+
+ // agent_access
+ WriteXmlRpcStringMember(writer, false, "agent_access", AgentAccess);
+
+ // inventory-skeleton
+ WriteXmlRpcArrayStart(writer, "inventory-skeleton");
+ writer.WriteStartElement("struct"); writer.WriteEndElement();
+ WriteXmlRpcArrayEnd(writer);
+
+ // buddy-list
+ WriteXmlRpcArrayStart(writer, "buddy-list");
+ WriteXmlRpcArrayEnd(writer);
+
+ // first_name
+ WriteXmlRpcStringMember(writer, false, "first_name", FirstName);
+
+ // global-textures
+ WriteXmlRpcArrayStart(writer, "global-textures");
+ writer.WriteStartElement("struct"); writer.WriteEndElement();
+ WriteXmlRpcArrayEnd(writer);
+
+ // inventory-skel-lib
+ WriteXmlRpcArrayStart(writer, "inventory-skel-lib");
+ writer.WriteStartElement("struct"); writer.WriteEndElement();
+ WriteXmlRpcArrayEnd(writer);
+
+ // seed_capability
+ WriteXmlRpcStringMember(writer, false, "seed_capability", SeedCapability);
+
+ // gestures
+ WriteXmlRpcArrayStart(writer, "gestures");
+ writer.WriteStartElement("struct"); writer.WriteEndElement();
+ WriteXmlRpcArrayEnd(writer);
+
+ // sim_ip
+ WriteXmlRpcStringMember(writer, false, "sim_ip", SimIP.ToString());
+
+ // inventory-lib-root
+ WriteXmlRpcArrayStart(writer, "inventory-lib-root");
+ WriteXmlRpcStringMember(writer, true, "folder_id", LibraryRoot.ToString());
+ WriteXmlRpcArrayEnd(writer);
+
+ // login-flags
+ WriteXmlRpcArrayStart(writer, "login-flags");
+ writer.WriteStartElement("struct"); writer.WriteEndElement();
+ WriteXmlRpcArrayEnd(writer);
+
+ // inventory_host
+ WriteXmlRpcStringMember(writer, false, "inventory_host", String.Empty);
+
+ // home
+ LLSDMap home = new LLSDMap(3);
+
+ LLSDArray homeRegionHandle = new LLSDArray(2);
+ uint homeRegionX, homeRegionY;
+ Helpers.LongToUInts(HomeRegion, out homeRegionX, out homeRegionY);
+ homeRegionHandle.Add(LLSD.FromReal((double)homeRegionX));
+ homeRegionHandle.Add(LLSD.FromReal((double)homeRegionY));
+
+ home["region_handle"] = homeRegionHandle;
+ home["position"] = LLSD.FromVector3(HomePosition);
+ home["look_at"] = LLSD.FromVector3(HomeLookAt);
+
+ WriteXmlRpcStringMember(writer, false, "home", LLSDParser.SerializeNotation(home));
+
+ // message
+ WriteXmlRpcStringMember(writer, false, "message", Message);
+
+ // look_at
+ WriteXmlRpcStringMember(writer, false, "look_at", LLSDParser.SerializeNotation(LLSD.FromVector3(LookAt)));
+
+ // login
+ WriteXmlRpcStringMember(writer, false, "login", "true");
+
+ // event_notifications
+ WriteXmlRpcArrayStart(writer, "event_notifications");
+ WriteXmlRpcArrayEnd(writer);
+
+ // secure_session_id
+ WriteXmlRpcStringMember(writer, false, "secure_session_id", SecureSessionID.ToString());
+
+ // region_x
+ WriteXmlRpcIntMember(writer, false, "region_x", RegionX);
+
+ // last_name
+ WriteXmlRpcStringMember(writer, false, "last_name", LastName);
+
+ // region_y
+ WriteXmlRpcIntMember(writer, false, "region_y", RegionY);
+
+ // circuit_code
+ WriteXmlRpcIntMember(writer, false, "circuit_code", CircuitCode);
+
+ // initial-outfit
+ WriteXmlRpcArrayStart(writer, "initial-outfit");
+ WriteXmlRpcArrayEnd(writer);
+ }
+ else
+ {
+ // Login failure
+ }
+ }
+ writer.WriteEndElement();
+ writer.WriteEndElement();
+ writer.WriteEndElement();
+ writer.WriteEndElement();
+ }
+ writer.WriteEndElement();
+ writer.Close();
+ }
+
#region Parsing Helpers
public static uint ParseUInt(string key, LLSDMap reply)
@@ -305,6 +468,75 @@ namespace OpenMetaverse
}
#endregion Parsing Helpers
+
+ #region XmlRpc Serializing Helpers
+
+ public static void WriteXmlRpcStringMember(XmlWriter writer, bool wrapWithValueStruct, string name, string value)
+ {
+ if (wrapWithValueStruct)
+ {
+ writer.WriteStartElement("value");
+ writer.WriteStartElement("struct");
+ }
+ writer.WriteStartElement("member");
+ {
+ writer.WriteElementString("name", name);
+ writer.WriteStartElement("value");
+ {
+ writer.WriteElementString("string", value);
+ }
+ writer.WriteEndElement();
+ }
+ writer.WriteEndElement();
+ if (wrapWithValueStruct)
+ {
+ writer.WriteEndElement();
+ writer.WriteEndElement();
+ }
+ }
+
+ public static void WriteXmlRpcIntMember(XmlWriter writer, bool wrapWithValueStruct, string name, uint value)
+ {
+ if (wrapWithValueStruct)
+ {
+ writer.WriteStartElement("value");
+ writer.WriteStartElement("struct");
+ }
+ writer.WriteStartElement("member");
+ {
+ writer.WriteElementString("name", name);
+ writer.WriteStartElement("value");
+ {
+ writer.WriteElementString("i4", value.ToString());
+ }
+ writer.WriteEndElement();
+ }
+ writer.WriteEndElement();
+ if (wrapWithValueStruct)
+ {
+ writer.WriteEndElement();
+ writer.WriteEndElement();
+ }
+ }
+
+ public static void WriteXmlRpcArrayStart(XmlWriter writer, string name)
+ {
+ writer.WriteStartElement("member");
+ writer.WriteElementString("name", name);
+ writer.WriteStartElement("value");
+ writer.WriteStartElement("array");
+ writer.WriteStartElement("data");
+ }
+
+ public static void WriteXmlRpcArrayEnd(XmlWriter writer)
+ {
+ writer.WriteEndElement();
+ writer.WriteEndElement();
+ writer.WriteEndElement();
+ writer.WriteEndElement();
+ }
+
+ #endregion XmlRpc Serializing Helpers
}
#endregion Structs
@@ -656,6 +888,8 @@ namespace OpenMetaverse
bool loginSuccess = llsd.AsBoolean();
bool redirect = (llsd.AsString() == "indeterminate");
LoginResponseData data = new LoginResponseData();
+ data.Reason = reason;
+ data.Message = message;
if (redirect)
{
diff --git a/OpenMetaverse/NetworkManager.cs b/OpenMetaverse/NetworkManager.cs
index 9b649e72..0fcdaf9f 100644
--- a/OpenMetaverse/NetworkManager.cs
+++ b/OpenMetaverse/NetworkManager.cs
@@ -776,20 +776,14 @@ namespace OpenMetaverse
#endregion ACK handling
}
- #region FireCallbacks
+ #region Fire callbacks
if (Client.Settings.SYNC_PACKETCALLBACKS)
- {
- PacketEvents.RaiseEvent(PacketType.Default, packet, simulator);
PacketEvents.RaiseEvent(packet.Type, packet, simulator);
- }
else
- {
- PacketEvents.BeginRaiseEvent(PacketType.Default, packet, simulator);
PacketEvents.BeginRaiseEvent(packet.Type, packet, simulator);
- }
- #endregion FireCallbacks
+ #endregion Fire callbacks
}
}
}
diff --git a/OpenMetaverse/Simulator.cs b/OpenMetaverse/Simulator.cs
index d254f0eb..f6d9a228 100644
--- a/OpenMetaverse/Simulator.cs
+++ b/OpenMetaverse/Simulator.cs
@@ -855,7 +855,7 @@ namespace OpenMetaverse
}
Stats.RecvBytes += (ulong)buffer.DataLength;
- Stats.RecvPackets++;
+ ++Stats.RecvPackets;
#endregion Packet Decoding
diff --git a/Programs/Simian/Agent.cs b/Programs/Simian/Agent.cs
new file mode 100644
index 00000000..9ad06067
--- /dev/null
+++ b/Programs/Simian/Agent.cs
@@ -0,0 +1,273 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading;
+using OpenMetaverse;
+using OpenMetaverse.Packets;
+
+namespace Simian
+{
+ public class Agent
+ {
+ public UUID AgentID;
+ public UUID SessionID;
+ public UUID SecureSessionID;
+ public uint CircuitCode;
+
+ /// Sequence numbers of packets we've received (for duplicate checking)
+ internal Queue packetArchive = new Queue();
+ /// Packets we have sent that need to be ACKed by the client
+ internal Dictionary needAcks = new Dictionary();
+
+ UDPServer udpServer;
+ IPEndPoint address;
+ /// ACKs that are queued up, waiting to be sent to the client
+ SortedList pendingAcks = new SortedList();
+ int currentSequence = 0;
+ Timer ackTimer;
+
+ public IPEndPoint Address
+ {
+ get { return address; }
+ set { address = value; }
+ }
+
+ public Agent(UDPServer udpServer, UUID agentID, UUID sessionID, UUID secureSessionID, uint circuitCode)
+ {
+ AgentID = agentID;
+ SessionID = sessionID;
+ SecureSessionID = secureSessionID;
+ CircuitCode = circuitCode;
+
+ this.udpServer = udpServer;
+ }
+
+ public void Initialize(IPEndPoint address)
+ {
+ this.address = address;
+
+ ackTimer = new Timer(new TimerCallback(AckTimer_Elapsed), null, Settings.NETWORK_TICK_INTERVAL,
+ Settings.NETWORK_TICK_INTERVAL);
+ }
+
+ public void SendPacket(Packet packet)
+ {
+ SendPacket(packet, true);
+ }
+
+ public void SendPacket(Packet packet, bool setSequence)
+ {
+ byte[] buffer;
+ int bytes;
+
+ // Keep track of when this packet was sent out
+ packet.TickCount = Environment.TickCount;
+
+ if (setSequence)
+ {
+ // Reset to zero if we've hit the upper sequence number limit
+ Interlocked.CompareExchange(ref currentSequence, 0, 0xFFFFFF);
+ // Increment and fetch the current sequence number
+ uint sequence = (uint)Interlocked.Increment(ref currentSequence);
+ packet.Header.Sequence = sequence;
+
+ if (packet.Header.Reliable)
+ {
+ // Add this packet to the list of ACK responses we are waiting on from the client
+ lock (needAcks)
+ needAcks[sequence] = packet;
+
+ if (packet.Header.Resent)
+ {
+ // This packet has already been sent out once, strip any appended ACKs
+ // off it and reinsert them into the outgoing ACK queue under the
+ // assumption that this packet will continually be rejected from the
+ // client or that the appended ACKs are possibly making the delivery fail
+ if (packet.Header.AckList.Length > 0)
+ {
+ Logger.DebugLog(String.Format("Purging ACKs from packet #{0} ({1}) which will be resent.",
+ packet.Header.Sequence, packet.GetType()));
+
+ lock (pendingAcks)
+ {
+ foreach (uint ack in packet.Header.AckList)
+ {
+ if (!pendingAcks.ContainsKey(ack))
+ pendingAcks[ack] = ack;
+ }
+ }
+
+ packet.Header.AppendedAcks = false;
+ packet.Header.AckList = new uint[0];
+ }
+ }
+ else
+ {
+ // This packet is not a resend, check if the conditions are favorable
+ // to ACK appending
+ if (packet.Type != PacketType.PacketAck)
+ {
+ lock (pendingAcks)
+ {
+ if (pendingAcks.Count > 0 &&
+ pendingAcks.Count < 10)
+ {
+ // Append all of the queued up outgoing ACKs to this packet
+ packet.Header.AckList = new uint[pendingAcks.Count];
+
+ for (int i = 0; i < pendingAcks.Count; i++)
+ packet.Header.AckList[i] = pendingAcks.Values[i];
+
+ pendingAcks.Clear();
+ packet.Header.AppendedAcks = true;
+ }
+ }
+ }
+ }
+ }
+ else if (packet.Header.AckList.Length > 0)
+ {
+ // Sanity check for ACKS appended on an unreliable packet, this is bad form
+ Logger.Log("Sending appended ACKs on an unreliable packet", Helpers.LogLevel.Warning);
+ }
+ }
+
+ // Serialize the packet
+ buffer = packet.ToBytes();
+ bytes = buffer.Length;
+ //Stats.SentBytes += (ulong)bytes;
+ //++Stats.SentPackets;
+
+ UDPPacketBuffer buf;
+
+ // Zerocode if needed
+ if (packet.Header.Zerocoded)
+ {
+ buf = new UDPPacketBuffer(address, true, false);
+
+ bytes = Helpers.ZeroEncode(buffer, bytes, buf.Data);
+ buf.DataLength = bytes;
+ }
+ else
+ {
+ buf = new UDPPacketBuffer(address, false, false);
+
+ buf.Data = buffer;
+ buf.DataLength = bytes;
+ }
+
+ udpServer.AsyncBeginSend(buf);
+ }
+
+ public void QueueAck(uint ack)
+ {
+ // Add this packet to the list of ACKs that need to be sent out
+ lock (pendingAcks)
+ pendingAcks[ack] = ack;
+
+ // Send out ACKs if we have a lot of them
+ if (pendingAcks.Count >= 10)
+ SendAcks();
+ }
+
+ public void ProcessAcks(List acks)
+ {
+ lock (needAcks)
+ {
+ foreach (uint ack in acks)
+ needAcks.Remove(ack);
+ }
+ }
+
+ public void SendAck(uint ack)
+ {
+ PacketAckPacket acks = new PacketAckPacket();
+ acks.Header.Reliable = false;
+ acks.Packets = new PacketAckPacket.PacketsBlock[1];
+ acks.Packets[0] = new PacketAckPacket.PacketsBlock();
+ acks.Packets[0].ID = ack;
+
+ SendPacket(acks, true);
+ }
+
+ void SendAcks()
+ {
+ PacketAckPacket acks = null;
+
+ lock (pendingAcks)
+ {
+ if (pendingAcks.Count > 0)
+ {
+ if (pendingAcks.Count > 250)
+ {
+ Logger.Log("Too many ACKs queued up!", Helpers.LogLevel.Error);
+ return;
+ }
+
+ acks = new PacketAckPacket();
+ acks.Header.Reliable = false;
+ acks.Packets = new PacketAckPacket.PacketsBlock[pendingAcks.Count];
+
+ for (int i = 0; i < pendingAcks.Count; i++)
+ {
+ acks.Packets[i] = new PacketAckPacket.PacketsBlock();
+ acks.Packets[i].ID = pendingAcks.Values[i];
+ }
+
+ pendingAcks.Clear();
+ }
+ }
+
+ if (acks != null)
+ SendPacket(acks, true);
+ }
+
+ void ResendUnacked()
+ {
+ lock (needAcks)
+ {
+ List dropAck = new List();
+ int now = Environment.TickCount;
+
+ // Resend packets
+ foreach (Packet packet in needAcks.Values)
+ {
+ if (packet.TickCount != 0 && now - packet.TickCount > 4000)
+ {
+ if (packet.ResendCount < 3)
+ {
+ Logger.DebugLog(String.Format("Resending packet #{0} ({1}), {2}ms have passed",
+ packet.Header.Sequence, packet.GetType(), now - packet.TickCount));
+
+ packet.TickCount = 0;
+ packet.Header.Resent = true;
+ //++Stats.ResentPackets;
+ ++packet.ResendCount;
+
+ SendPacket(packet, false);
+ }
+ else
+ {
+ Logger.Log(String.Format("Dropping packet #{0} ({1}) after {2} failed attempts",
+ packet.Header.Sequence, packet.GetType(), packet.ResendCount), Helpers.LogLevel.Warning);
+
+ dropAck.Add(packet.Header.Sequence);
+ }
+ }
+ }
+
+ if (dropAck.Count != 0)
+ {
+ foreach (uint seq in dropAck)
+ needAcks.Remove(seq);
+ }
+ }
+ }
+
+ private void AckTimer_Elapsed(object obj)
+ {
+ SendAcks();
+ ResendUnacked();
+ }
+ }
+}
diff --git a/Programs/Simian/EventDictionary.cs b/Programs/Simian/EventDictionary.cs
new file mode 100644
index 00000000..6941e5b4
--- /dev/null
+++ b/Programs/Simian/EventDictionary.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using OpenMetaverse;
+using OpenMetaverse.Packets;
+
+namespace Simian
+{
+ ///
+ /// Registers, unregisters, and fires events generated by incoming packets
+ ///
+ public class PacketEventDictionary
+ {
+ ///
+ /// Object that is passed to worker threads in the ThreadPool for
+ /// firing packet callbacks
+ ///
+ private struct PacketCallbackWrapper
+ {
+ /// Callback to fire for this packet
+ public UDPServer.PacketCallback Callback;
+ /// Reference to the agent that this packet came from
+ public Agent Agent;
+ /// The packet that needs to be processed
+ public Packet Packet;
+ }
+
+ private Dictionary _EventTable = new Dictionary();
+ private WaitCallback _ThreadPoolCallback;
+
+ ///
+ /// Default constructor
+ ///
+ public PacketEventDictionary()
+ {
+ _ThreadPoolCallback = new WaitCallback(ThreadPoolDelegate);
+ }
+
+ ///
+ /// Register an event handler
+ ///
+ /// Use PacketType.Default to fire this event on every
+ /// incoming packet
+ /// Packet type to register the handler for
+ /// Callback to be fired
+ public void RegisterEvent(PacketType packetType, UDPServer.PacketCallback eventHandler)
+ {
+ lock (_EventTable)
+ {
+ if (_EventTable.ContainsKey(packetType))
+ _EventTable[packetType] += eventHandler;
+ else
+ _EventTable[packetType] = eventHandler;
+ }
+ }
+
+ ///
+ /// Unregister an event handler
+ ///
+ /// Packet type to unregister the handler for
+ /// Callback to be unregistered
+ public void UnregisterEvent(PacketType packetType, UDPServer.PacketCallback eventHandler)
+ {
+ lock (_EventTable)
+ {
+ if (_EventTable.ContainsKey(packetType) && _EventTable[packetType] != null)
+ _EventTable[packetType] -= eventHandler;
+ }
+ }
+
+ ///
+ /// Fire the events registered for this packet type asynchronously
+ ///
+ /// Incoming packet type
+ /// Incoming packet
+ /// Agent this packet was received from
+ internal void BeginRaiseEvent(PacketType packetType, Packet packet, Agent agent)
+ {
+ UDPServer.PacketCallback callback;
+ PacketCallbackWrapper wrapper;
+
+ // Default handler first, if one exists
+ if (_EventTable.TryGetValue(PacketType.Default, out callback))
+ {
+ if (callback != null)
+ {
+ wrapper.Callback = callback;
+ wrapper.Packet = packet;
+ wrapper.Agent = agent;
+ ThreadPool.QueueUserWorkItem(_ThreadPoolCallback, wrapper);
+ }
+ }
+
+ if (_EventTable.TryGetValue(packetType, out callback))
+ {
+ if (callback != null)
+ {
+ wrapper.Callback = callback;
+ wrapper.Packet = packet;
+ wrapper.Agent = agent;
+ ThreadPool.QueueUserWorkItem(_ThreadPoolCallback, wrapper);
+
+ return;
+ }
+ }
+
+ if (packetType != PacketType.Default && packetType != PacketType.PacketAck)
+ {
+ Logger.DebugLog("No handler registered for packet event " + packetType);
+ }
+ }
+
+ private void ThreadPoolDelegate(Object state)
+ {
+ PacketCallbackWrapper wrapper = (PacketCallbackWrapper)state;
+
+ try
+ {
+ wrapper.Callback(wrapper.Packet, wrapper.Agent);
+ }
+ catch (Exception ex)
+ {
+ Logger.Log("Async Packet Event Handler: " + ex.ToString(), Helpers.LogLevel.Error);
+ }
+ }
+ }
+}
diff --git a/Programs/Simian/Main.cs b/Programs/Simian/Main.cs
new file mode 100644
index 00000000..bd64e1cd
--- /dev/null
+++ b/Programs/Simian/Main.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Simian
+{
+ class MainEntry
+ {
+ static void Main(string[] args)
+ {
+ Simian simulator = new Simian();
+ simulator.Start(9000, false);
+
+ Console.WriteLine("Simulator is running. Press ENTER to quit");
+
+ Console.ReadLine();
+
+ simulator.Stop();
+ }
+ }
+}
diff --git a/Programs/Simian/PacketHandlers.cs b/Programs/Simian/PacketHandlers.cs
new file mode 100644
index 00000000..783245d5
--- /dev/null
+++ b/Programs/Simian/PacketHandlers.cs
@@ -0,0 +1,75 @@
+using System;
+using OpenMetaverse;
+using OpenMetaverse.Packets;
+
+namespace Simian
+{
+ public partial class Simian
+ {
+ void UseCircuitCodeHandler(Packet packet, Agent agent)
+ {
+ RegionHandshakePacket handshake = new RegionHandshakePacket();
+ handshake.RegionInfo.BillableFactor = 0f;
+ handshake.RegionInfo.CacheID = UUID.Random();
+ handshake.RegionInfo.IsEstateManager = false;
+ handshake.RegionInfo.RegionFlags = 1;
+ handshake.RegionInfo.SimOwner = UUID.Random();
+ handshake.RegionInfo.SimAccess = 1;
+ handshake.RegionInfo.SimName = Utils.StringToBytes("Simian");
+ handshake.RegionInfo.WaterHeight = 20.0f;
+ handshake.RegionInfo.TerrainBase0 = UUID.Zero;
+ handshake.RegionInfo.TerrainBase1 = UUID.Zero;
+ handshake.RegionInfo.TerrainBase2 = UUID.Zero;
+ handshake.RegionInfo.TerrainBase3 = UUID.Zero;
+ handshake.RegionInfo.TerrainDetail0 = UUID.Zero;
+ handshake.RegionInfo.TerrainDetail1 = UUID.Zero;
+ handshake.RegionInfo.TerrainDetail2 = UUID.Zero;
+ handshake.RegionInfo.TerrainDetail3 = UUID.Zero;
+ handshake.RegionInfo.TerrainHeightRange00 = 0f;
+ handshake.RegionInfo.TerrainHeightRange01 = 20f;
+ handshake.RegionInfo.TerrainHeightRange10 = 0f;
+ handshake.RegionInfo.TerrainHeightRange11 = 20f;
+ handshake.RegionInfo.TerrainStartHeight00 = 0f;
+ handshake.RegionInfo.TerrainStartHeight01 = 40f;
+ handshake.RegionInfo.TerrainStartHeight10 = 0f;
+ handshake.RegionInfo.TerrainStartHeight11 = 40f;
+ handshake.RegionInfo2.RegionID = UUID.Random();
+
+ agent.SendPacket(handshake);
+ }
+
+ void StartPingCheckHandler(Packet packet, Agent agent)
+ {
+ StartPingCheckPacket start = (StartPingCheckPacket)packet;
+
+ CompletePingCheckPacket complete = new CompletePingCheckPacket();
+ complete.Header.Reliable = false;
+ complete.PingID.PingID = start.PingID.PingID;
+
+ agent.SendPacket(complete);
+ }
+
+ void CompleteAgentMovementHandler(Packet packet, Agent agent)
+ {
+ uint regionX = 256000;
+ uint regionY = 256000;
+
+ CompleteAgentMovementPacket request = (CompleteAgentMovementPacket)packet;
+
+ AgentMovementCompletePacket complete = new AgentMovementCompletePacket();
+ complete.AgentData.AgentID = agent.AgentID;
+ complete.AgentData.SessionID = agent.SessionID;
+ complete.Data.LookAt = Vector3.UnitX;
+ complete.Data.Position = new Vector3(128f, 128f, 25f);
+ complete.Data.RegionHandle = Helpers.UIntsToLong(regionX, regionY);
+ complete.Data.Timestamp = Utils.DateTimeToUnixTime(DateTime.Now);
+ complete.SimData.ChannelVersion = Utils.StringToBytes("Simian");
+
+ agent.SendPacket(complete);
+ }
+
+ void AgentUpdateHandler(Packet packet, Agent agent)
+ {
+ }
+ }
+}
diff --git a/Programs/Simian/Simian.cs b/Programs/Simian/Simian.cs
new file mode 100644
index 00000000..b5b39675
--- /dev/null
+++ b/Programs/Simian/Simian.cs
@@ -0,0 +1,281 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.IO;
+using System.Text;
+using System.Xml;
+using System.Threading;
+using OpenMetaverse;
+using OpenMetaverse.Capabilities;
+using OpenMetaverse.Packets;
+
+namespace Simian
+{
+ public partial class Simian
+ {
+ HttpServer httpServer;
+ UDPServer udpServer;
+ Dictionary unassociatedAgents;
+ int currentCircuitCode;
+ int tcpPort;
+ int udpPort;
+
+ public Simian()
+ {
+ unassociatedAgents = new Dictionary();
+ currentCircuitCode = 0;
+ }
+
+ public void Start(int port, bool ssl)
+ {
+ // Put UDP listening on the same port number as the HTTP server for simplicity
+ tcpPort = port;
+ udpPort = port;
+
+ InitUDPServer(udpPort);
+ InitHttpServer(tcpPort, ssl);
+ }
+
+ public void Stop()
+ {
+ udpServer.Stop();
+ httpServer.Stop();
+ }
+
+ public bool TryGetUnassociatedAgent(uint circuitCode, out Agent agent)
+ {
+ if (unassociatedAgents.TryGetValue(circuitCode, out agent))
+ {
+ lock (unassociatedAgents)
+ unassociatedAgents.Remove(circuitCode);
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ void InitUDPServer(int port)
+ {
+ udpServer = new UDPServer(port, this);
+
+ udpServer.RegisterPacketCallback(PacketType.UseCircuitCode, new UDPServer.PacketCallback(UseCircuitCodeHandler));
+ udpServer.RegisterPacketCallback(PacketType.StartPingCheck, new UDPServer.PacketCallback(StartPingCheckHandler));
+ udpServer.RegisterPacketCallback(PacketType.CompleteAgentMovement, new UDPServer.PacketCallback(CompleteAgentMovementHandler));
+ udpServer.RegisterPacketCallback(PacketType.AgentUpdate, new UDPServer.PacketCallback(AgentUpdateHandler));
+ }
+
+ void InitHttpServer(int port, bool ssl)
+ {
+ httpServer = new HttpServer(tcpPort, ssl);
+
+ // Login webpage HEAD request, used to check if the login webpage is alive
+ HttpRequestSignature signature = new HttpRequestSignature();
+ signature.Method = "head";
+ signature.ContentType = String.Empty;
+ signature.Path = "/loginpage";
+ HttpServer.HttpRequestCallback callback = new HttpServer.HttpRequestCallback(LoginWebpageHeadHandler);
+ HttpServer.HttpRequestHandler handler = new HttpServer.HttpRequestHandler(signature, callback);
+ httpServer.AddHandler(handler);
+
+ // Login webpage GET request, gets the login webpage data (purely aesthetic)
+ signature.Method = "get";
+ signature.ContentType = String.Empty;
+ signature.Path = "/loginpage";
+ callback = new HttpServer.HttpRequestCallback(LoginWebpageGetHandler);
+ handler.Signature = signature;
+ handler.Callback = callback;
+ httpServer.AddHandler(handler);
+
+ // Client XML-RPC login
+ signature.Method = "post";
+ signature.ContentType = "text/xml";
+ signature.Path = "/login";
+ callback = new HttpServer.HttpRequestCallback(LoginXmlRpcPostHandler);
+ handler.Signature = signature;
+ handler.Callback = callback;
+ httpServer.AddHandler(handler);
+
+ // Client LLSD login
+ signature.Method = "post";
+ signature.ContentType = "application/xml";
+ signature.Path = "/login";
+ callback = new HttpServer.HttpRequestCallback(LoginLLSDPostHandler);
+ handler.Signature = signature;
+ handler.Callback = callback;
+ httpServer.AddHandler(handler);
+
+ httpServer.Start();
+ }
+
+ void LoginWebpageHeadHandler(ref HttpListenerContext context)
+ {
+ context.Response.StatusCode = (int)HttpStatusCode.OK;
+ context.Response.StatusDescription = "OK";
+ }
+
+ void LoginWebpageGetHandler(ref HttpListenerContext context)
+ {
+ string pageContent = "Simian
Welcome to Simian
";
+ byte[] pageData = Encoding.UTF8.GetBytes(pageContent);
+ context.Response.OutputStream.Write(pageData, 0, pageData.Length);
+ context.Response.Close();
+ }
+
+ void LoginXmlRpcPostHandler(ref HttpListenerContext context)
+ {
+ string
+ firstName = String.Empty,
+ lastName = String.Empty,
+ password = String.Empty,
+ start = String.Empty,
+ version = String.Empty,
+ channel = String.Empty;
+
+ try
+ {
+ // Parse the incoming XML
+ XmlReader reader = XmlReader.Create(context.Request.InputStream);
+
+ reader.ReadStartElement("methodCall");
+ {
+ string methodName = reader.ReadElementContentAsString("methodName", String.Empty);
+
+ if (methodName == "login_to_simulator")
+ {
+ 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 "first":
+ firstName = reader.ReadElementContentAsString("string", String.Empty);
+ break;
+ case "last":
+ lastName = reader.ReadElementContentAsString("string", String.Empty);
+ break;
+ case "passwd":
+ password = reader.ReadElementContentAsString("string", String.Empty);
+ break;
+ case "start":
+ start = reader.ReadElementContentAsString("string", String.Empty);
+ break;
+ case "version":
+ version = reader.ReadElementContentAsString("string", String.Empty);
+ break;
+ case "channel":
+ channel = reader.ReadElementContentAsString("string", String.Empty);
+ break;
+ default:
+ if (reader.Name == "string")
+ Console.WriteLine(String.Format("Ignore login xml value: name={0}, value={1}", name, reader.ReadInnerXml()));
+ else
+ Console.WriteLine(String.Format("Unknown login xml: name={0}, value={1}", name, reader.ReadInnerXml()));
+ break;
+ }
+ }
+ reader.ReadEndElement();
+ }
+ reader.ReadEndElement();
+ }
+ }
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ }
+ }
+ reader.ReadEndElement();
+ reader.Close();
+
+ LoginResponseData responseData = HandleLogin(firstName, lastName, password, start, version, channel);
+ XmlWriter writer = XmlWriter.Create(context.Response.OutputStream);
+ responseData.ToXmlRpc(writer);
+ writer.Close();
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.ToString());
+ }
+ }
+
+ void LoginLLSDPostHandler(ref HttpListenerContext context)
+ {
+ string body = String.Empty;
+
+ using (StreamReader reader = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding))
+ {
+ body = reader.ReadToEnd();
+ }
+
+ Console.WriteLine(body);
+ }
+
+ LoginResponseData HandleLogin(string firstName, string lastName, string password, string start, string version, string channel)
+ {
+ uint regionX = 256000;
+ uint regionY = 256000;
+
+ // Setup default login response values
+ LoginResponseData response;
+
+ response.AgentID = UUID.Random();
+ response.SecureSessionID = UUID.Random();
+ response.SessionID = UUID.Random();
+ response.CircuitCode = CreateAgentCircuit(response.AgentID, response.SessionID, response.SecureSessionID);
+ response.AgentAccess = "M";
+ response.BuddyList = null;
+ response.FirstName = firstName;
+ response.HomeLookAt = Vector3.UnitX;
+ response.HomePosition = new Vector3(128f, 128f, 25f);
+ response.HomeRegion = Helpers.UIntsToLong(regionX, regionY);
+ response.InventoryFolders = null;
+ response.InventoryRoot = UUID.Random();
+ response.LastName = lastName;
+ response.LibraryFolders = null;
+ response.LibraryOwner = response.AgentID;
+ response.LibraryRoot = UUID.Random();
+ response.LookAt = Vector3.UnitX;
+ response.Message = "Welcome to Simian";
+ response.Reason = String.Empty;
+ response.RegionX = regionX;
+ response.RegionY = regionY;
+ response.SecondsSinceEpoch = DateTime.Now;
+ // FIXME: Actually generate a seed capability
+ response.SeedCapability = String.Format("http://{0}:{1}/seed_caps", IPAddress.Loopback, tcpPort);
+ response.SimIP = IPAddress.Loopback;
+ response.SimPort = (ushort)udpPort;
+ response.StartLocation = "last";
+ response.Success = true;
+
+ return response;
+ }
+
+ uint CreateAgentCircuit(UUID agentID, UUID sessionID, UUID secureSessionID)
+ {
+ uint circuitCode = (uint)Interlocked.Increment(ref currentCircuitCode);
+
+ Agent agent = new Agent(udpServer, agentID, sessionID, secureSessionID, circuitCode);
+
+ // Put this client in the list of clients that have not been associated with an IPEndPoint yet
+ lock (unassociatedAgents)
+ unassociatedAgents[circuitCode] = agent;
+
+ Logger.Log("Created a circuit for agent " + agentID.ToString(), Helpers.LogLevel.Info);
+
+ return circuitCode;
+ }
+ }
+}
diff --git a/Programs/Simian/UDPServer.cs b/Programs/Simian/UDPServer.cs
new file mode 100644
index 00000000..ec3527ab
--- /dev/null
+++ b/Programs/Simian/UDPServer.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading;
+using OpenMetaverse;
+using OpenMetaverse.Packets;
+
+namespace Simian
+{
+ public struct IncomingPacket
+ {
+ public Agent Agent;
+ public Packet Packet;
+ }
+
+ public class UDPServer : UDPBase
+ {
+ ///
+ /// Coupled with RegisterCallback(), this is triggered whenever a packet
+ /// of a registered type is received
+ ///
+ public delegate void PacketCallback(Packet packet, Agent agent);
+
+ /// This is only used to fetch unassociated agents, which will
+ /// be exposed through a login interface at some point
+ Simian server;
+ /// Handlers for incoming packets
+ PacketEventDictionary packetEvents = new PacketEventDictionary();
+ /// All of the agents currently connected to this UDP server
+ Dictionary agents = new Dictionary();
+ /// Incoming packets that are awaiting handling
+ BlockingQueue packetInbox = new BlockingQueue(Settings.PACKET_INBOX_SIZE);
+
+ public UDPServer(int port, Simian server)
+ : base(port)
+ {
+ this.server = server;
+
+ Start();
+
+ // Start the incoming packet processing thread
+ Thread incomingThread = new Thread(new ThreadStart(IncomingPacketHandler));
+ incomingThread.Start();
+ }
+
+ public void RegisterPacketCallback(PacketType type, PacketCallback callback)
+ {
+ packetEvents.RegisterEvent(type, callback);
+ }
+
+ protected override void PacketReceived(UDPPacketBuffer buffer)
+ {
+ Agent agent = null;
+ Packet packet = null;
+ int packetEnd = buffer.DataLength - 1;
+
+ // Decoding
+ try
+ {
+ packet = Packet.BuildPacket(buffer.Data, ref packetEnd, buffer.ZeroData);
+ }
+ catch (MalformedDataException)
+ {
+ Logger.Log(String.Format("Malformed data, cannot parse packet:\n{0}",
+ Utils.BytesToHexString(buffer.Data, buffer.DataLength, null)), Helpers.LogLevel.Error);
+ }
+
+ // Fail-safe check
+ if (packet == null)
+ {
+ Logger.Log("Couldn't build a message from the incoming data", Helpers.LogLevel.Warning);
+ return;
+ }
+
+ //Stats.RecvBytes += (ulong)buffer.DataLength;
+ //++Stats.RecvPackets;
+
+ if (packet.Type == PacketType.UseCircuitCode)
+ {
+ UseCircuitCodePacket useCircuitCode = (UseCircuitCodePacket)packet;
+
+ if (server.TryGetUnassociatedAgent(useCircuitCode.CircuitCode.Code, out agent))
+ {
+ agent.Initialize((IPEndPoint)buffer.RemoteEndPoint);
+
+ lock (agents)
+ agents[(IPEndPoint)buffer.RemoteEndPoint] = agent;
+
+ Logger.Log("Activated UDP circuit " + useCircuitCode.CircuitCode.Code, Helpers.LogLevel.Info);
+
+ //agent.SendAck(useCircuitCode.Header.Sequence);
+ }
+ else
+ {
+ Logger.Log("Received a UseCircuitCode packet for an unrecognized circuit: " + useCircuitCode.CircuitCode.Code.ToString(),
+ Helpers.LogLevel.Warning);
+ return;
+ }
+ }
+ else
+ {
+ // Determine which agent this packet came from
+ if (!agents.TryGetValue((IPEndPoint)buffer.RemoteEndPoint, out agent))
+ {
+ Logger.Log("Received UDP packet from an unrecognized source: " + ((IPEndPoint)buffer.RemoteEndPoint).ToString(),
+ Helpers.LogLevel.Warning);
+ return;
+ }
+ }
+
+ // Reliable handling
+ if (packet.Header.Reliable)
+ {
+ // Queue up this sequence number for acknowledgement
+ agent.QueueAck((uint)packet.Header.Sequence);
+
+ //if (packet.Header.Resent) ++Stats.ReceivedResends;
+ }
+
+ // Inbox insertion
+ IncomingPacket incomingPacket;
+ incomingPacket.Agent = agent;
+ incomingPacket.Packet = packet;
+
+ // TODO: Prioritize the queue
+ packetInbox.Enqueue(incomingPacket);
+ }
+
+ protected override void PacketSent(UDPPacketBuffer buffer, int bytesSent)
+ {
+ Logger.DebugLog("Sent " + buffer.DataLength + " byte packet");
+ }
+
+ private void IncomingPacketHandler()
+ {
+ IncomingPacket incomingPacket = new IncomingPacket();
+ Packet packet = null;
+ Agent agent = null;
+
+ while (IsRunning)
+ {
+ // Reset packet to null for the check below
+ packet = null;
+
+ if (packetInbox.Dequeue(100, ref incomingPacket))
+ {
+ packet = incomingPacket.Packet;
+ agent = incomingPacket.Agent;
+
+ if (packet != null)
+ {
+ #region ACK accounting
+
+ // Check the archives to see whether we already received this packet
+ lock (agent.packetArchive)
+ {
+ if (agent.packetArchive.Contains(packet.Header.Sequence))
+ {
+ if (packet.Header.Resent)
+ {
+ Logger.DebugLog("Received resent packet #" + packet.Header.Sequence);
+ }
+ else
+ {
+ Logger.Log(String.Format("Received a duplicate of packet #{0}, current type: {1}",
+ packet.Header.Sequence, packet.Type), Helpers.LogLevel.Warning);
+ }
+
+ // Avoid firing a callback twice for the same packet
+ continue;
+ }
+ else
+ {
+ // Keep the PacketArchive size within a certain capacity
+ while (agent.packetArchive.Count >= Settings.PACKET_ARCHIVE_SIZE)
+ {
+ agent.packetArchive.Dequeue(); agent.packetArchive.Dequeue();
+ agent.packetArchive.Dequeue(); agent.packetArchive.Dequeue();
+ }
+
+ agent.packetArchive.Enqueue(packet.Header.Sequence);
+ }
+ }
+
+ #endregion ACK accounting
+
+ #region ACK handling
+
+ // Handle appended ACKs
+ if (packet.Header.AppendedAcks)
+ {
+ lock (agent.needAcks)
+ {
+ for (int i = 0; i < packet.Header.AckList.Length; i++)
+ agent.needAcks.Remove(packet.Header.AckList[i]);
+ }
+ }
+
+ // Handle PacketAck packets
+ if (packet.Type == PacketType.PacketAck)
+ {
+ PacketAckPacket ackPacket = (PacketAckPacket)packet;
+
+ lock (agent.needAcks)
+ {
+ for (int i = 0; i < ackPacket.Packets.Length; i++)
+ agent.needAcks.Remove(ackPacket.Packets[i].ID);
+ }
+ }
+
+ #endregion ACK handling
+
+ packetEvents.BeginRaiseEvent(packet.Type, packet, agent);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/prebuild.xml b/prebuild.xml
index 905b2f24..cf7388a0 100644
--- a/prebuild.xml
+++ b/prebuild.xml
@@ -664,6 +664,29 @@
+
+
+
+
+ ../../bin/
+
+
+
+
+ ../../bin/
+
+
+
+ ../../bin/
+
+
+
+
+
+
+
+
+