/*
* Copyright (c) 2006, Second Life Reverse Engineering Team
* 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 Second Life Reverse Engineering Team 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.Timers;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Globalization;
using System.IO;
using Nwc.XmlRpc;
using Nii.JSON;
using libsecondlife.Packets;
namespace libsecondlife
{
///
/// This exception is thrown whenever a network operation is attempted
/// without a network connection.
///
public class NotConnectedException : ApplicationException { }
///
/// NetworkManager is responsible for managing the network layer of
/// libsecondlife. It tracks all the server connections, serializes
/// outgoing traffic and deserializes incoming traffic, and provides
/// instances of delegates for network-related events.
///
public class NetworkManager
{
///
/// Explains why a simulator or the grid disconnected from us
///
public enum DisconnectType
{
/// The client requested the logout or simulator disconnect
ClientInitiated,
/// The server notified us that it is disconnecting
ServerInitiated,
/// Either a socket was closed or network traffic timed out
NetworkTimeout
}
///
/// Coupled with RegisterCallback(), this is triggered whenever a packet
/// of a registered type is received
///
///
///
public delegate void PacketCallback(Packet packet, Simulator simulator);
///
/// Triggered when a simulator other than the simulator that is currently
/// being occupied disconnects for whatever reason
///
/// The simulator that disconnected, which will become a null
/// reference after the callback is finished
/// Enumeration explaining the reason for the disconnect
public delegate void SimDisconnectedCallback(Simulator simulator, DisconnectType reason);
///
/// Triggered when we are logged out of the grid due to a simulator request,
/// client request, network timeout, or any other cause
///
/// Enumeration explaining the reason for the disconnect
/// If we were logged out by the simulator, this
/// is a message explaining why
public delegate void DisconnectedCallback(DisconnectType reason, string message);
///
/// Triggered when CurrentSim changes
///
/// A reference to the old value of CurrentSim
public delegate void CurrentSimChangedCallback(Simulator PreviousSimulator);
///
/// An event for the connection to a simulator other than the currently
/// occupied one disconnecting
///
public event SimDisconnectedCallback OnSimDisconnected;
///
/// An event for being logged out either through client request, server
/// forced, or network error
///
public event DisconnectedCallback OnDisconnected;
///
/// An event for when CurrentSim changes
///
public event CurrentSimChangedCallback OnCurrentSimChanged;
/// The permanent UUID for the logged in avatar
public LLUUID AgentID = LLUUID.Zero;
/// Temporary UUID assigned to this session, used for
/// verifying our identity in packets
public LLUUID SessionID = LLUUID.Zero;
/// Shared secret UUID that is never sent over the wire
public LLUUID SecureSessionID = LLUUID.Zero;
/// Uniquely identifier associated with our connections to
/// simulators
public uint CircuitCode;
/// String holding a descriptive error on login failure, empty
/// otherwise
public string LoginError = String.Empty;
/// The simulator that the logged in avatar is currently
/// occupying
public Simulator CurrentSim = null;
/// The capabilities for the current simulator
public Caps CurrentCaps = null;
/// The complete dictionary of all the login values returned
/// by the RPC login server, converted to native data types wherever
/// possible
public Dictionary LoginValues = new Dictionary();
///
/// Shows whether the network layer is logged in to the grid or not
///
public bool Connected { get { return connected; } }
///
internal Dictionary> Callbacks = new Dictionary>();
///
internal List EventQueueCallbacks = new List();
private SecondLife Client;
private List Simulators = new List();
private System.Timers.Timer DisconnectTimer;
private System.Timers.Timer LogoutTimer;
private bool connected = false;
private ManualResetEvent LogoutReplyEvent = new ManualResetEvent(false);
///
/// Default constructor
///
/// Reference to the SecondLife client
public NetworkManager(SecondLife client)
{
Client = client;
CurrentSim = null;
// Register the internal callbacks
RegisterCallback(PacketType.RegionHandshake, new PacketCallback(RegionHandshakeHandler));
RegisterCallback(PacketType.StartPingCheck, new PacketCallback(StartPingCheckHandler));
RegisterCallback(PacketType.ParcelOverlay, new PacketCallback(ParcelOverlayHandler));
RegisterCallback(PacketType.EnableSimulator, new PacketCallback(EnableSimulatorHandler));
RegisterCallback(PacketType.KickUser, new PacketCallback(KickUserHandler));
RegisterCallback(PacketType.LogoutReply, new PacketCallback(LogoutReplyHandler));
RegisterCallback(PacketType.CompletePingCheck, new PacketCallback(PongHandler));
// The proper timeout for this will get set at Login
DisconnectTimer = new System.Timers.Timer();
DisconnectTimer.Elapsed += new ElapsedEventHandler(DisconnectTimer_Elapsed);
}
///
/// Register an event handler for a packet. This is a low level event
/// interface and should only be used if you are doing something not
/// supported in libsecondlife
///
/// Packet type to trigger events for
/// Callback to fire when a packet of this type
/// is received
public void RegisterCallback(PacketType type, PacketCallback callback)
{
if (!Callbacks.ContainsKey(type))
{
Callbacks[type] = new List();
}
List callbackArray = Callbacks[type];
callbackArray.Add(callback);
}
///
/// Unregister an event handler for a packet. This is a low level event
/// interface and should only be used if you are doing something not
/// supported in libsecondlife
///
/// Packet type this callback is registered with
/// Callback to stop firing events for
public void UnregisterCallback(PacketType type, PacketCallback callback)
{
if (!Callbacks.ContainsKey(type))
{
Client.Log("Trying to unregister a callback for packet " + type.ToString() +
" when no callbacks are setup for that packet", Helpers.LogLevel.Info);
return;
}
List callbackArray = Callbacks[type];
if (callbackArray.Contains(callback))
{
callbackArray.Remove(callback);
}
else
{
Client.Log("Trying to unregister a non-existant callback for packet " + type.ToString(),
Helpers.LogLevel.Info);
}
}
///
/// Register a CAPS event handler
///
/// Callback to fire when a CAPS event is received
public void RegisterEventCallback(Caps.EventQueueCallback callback)
{
lock (EventQueueCallbacks) EventQueueCallbacks.Add(callback);
}
///
/// Send a packet to the simulator the avatar is currently occupying
///
/// Packet to send
public void SendPacket(Packet packet)
{
if (CurrentSim != null && CurrentSim.Connected)
CurrentSim.SendPacket(packet, true);
}
///
/// Send a packet to a specified simulator
///
/// Packet to send
/// Simulator to send the packet to
public void SendPacket(Packet packet, Simulator simulator)
{
if (simulator != null)
simulator.SendPacket(packet, true);
}
///
/// Send a raw byte array as a packet to the current simulator
///
/// Byte array containing a packet
/// Whether to set the second, third, and fourth
/// bytes of the payload to the current sequence number
public void SendPacket(byte[] payload, bool setSequence)
{
if (CurrentSim != null)
CurrentSim.SendPacket(payload, setSequence);
}
///
/// Send a raw byte array as a packet to the specified simulator
///
/// Byte array containing a packet
/// Simulator to send the packet to
/// Whether to set the second, third, and fourth
/// bytes of the payload to the current sequence number
public void SendPacket(byte[] payload, Simulator simulator, bool setSequence)
{
if (simulator != null)
simulator.SendPacket(payload, setSequence);
}
///
/// Build a start location URI for passing to the Login function
///
/// Name of the simulator to start in
/// X coordinate to start at
/// Y coordinate to start at
/// Z coordinate to start at
/// String with a URI that can be used to login to a specified
/// location
public static string StartLocation(string sim, int x, int y, int z)
{
// uri:sim name&x&y&z
return "uri:" + sim.ToLower() + "&" + x + "&" + y + "&" + z;
}
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
public Dictionary DefaultLoginValues(string firstName, string lastName,
string password, string mac, string startLocation, int major, int minor, int patch,
int build, string platform, string viewerDigest, string userAgent, string author,
bool md5pass)
{
Dictionary values = new Dictionary();
values["first"] = firstName;
values["last"] = lastName;
values["passwd"] = md5pass ? password : Helpers.MD5(password);
values["start"] = startLocation;
values["major"] = major;
values["minor"] = minor;
values["patch"] = patch;
values["build"] = build;
values["platform"] = platform;
values["mac"] = mac;
values["agree_to_tos"] = "true";
values["read_critical"] = "true";
values["viewer_digest"] = viewerDigest;
values["user-agent"] = userAgent + " (" + Client.Settings.VERSION + ")";
values["author"] = author;
// Build the options array
List