This commit is contained in:
2024-02-23 06:57:07 -05:00
commit 8fb7082f56
104 changed files with 284139 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tor.Events
{
/// <summary>
/// Specifies that a dispatcher class is associated with an event.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
internal sealed class EventAssocAttribute : Attribute
{
private readonly Event evt;
/// <summary>
/// Initializes a new instance of the <see cref="EventAssocAttribute"/> class.
/// </summary>
/// <param name="evt">The event the class is associated with.</param>
public EventAssocAttribute(Event evt)
{
this.evt = evt;
}
#region Properties
/// <summary>
/// Gets the event the class is associated with.
/// </summary>
public Event Event
{
get { return evt; }
}
#endregion
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tor.Events
{
/// <summary>
/// A class containing the skeleton structure for processing an event response received from a control connection.
/// </summary>
internal abstract class Dispatcher
{
private Client client;
private Event currentEvent;
private Events events;
#region Properties
/// <summary>
/// Gets or sets the client which owns the dispatcher.
/// </summary>
public Client Client
{
get { return client; }
set { client = value; }
}
/// <summary>
/// Gets or sets the event being processed by this dispatcher.
/// </summary>
public Event CurrentEvent
{
get { return currentEvent; }
set { currentEvent = value; }
}
/// <summary>
/// Gets or sets the event handler object instance.
/// </summary>
public Events Events
{
get { return events; }
set { events = value; }
}
#endregion
/// <summary>
/// Dispatches the event, parsing the content of the line and raising the relevant event.
/// </summary>
/// <param name="line">The line which was received from the control connection.</param>
/// <returns><c>true</c> if the event is parsed and dispatched successfully; otherwise, <c>false</c>.</returns>
public abstract bool Dispatch(string line);
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tor.Helpers;
namespace Tor.Events
{
/// <summary>
/// A class containing the logic for parsing a bandwidth event.
/// </summary>
[EventAssoc(Event.Bandwidth)]
internal sealed class BandwidthDispatcher : Dispatcher
{
#region Tor.Events.Dispatcher
/// <summary>
/// Dispatches the event, parsing the content of the line and raising the relevant event.
/// </summary>
/// <param name="line">The line which was received from the control connection.</param>
/// <returns>
/// <c>true</c> if the event is parsed and dispatched successfully; otherwise, <c>false</c>.
/// </returns>
public override bool Dispatch(string line)
{
string[] parts = StringHelper.GetAll(line, ' ');
if (parts.Length < 2)
return false;
double downloaded;
double uploaded;
if (!double.TryParse(parts[0], out downloaded))
return false;
if (!double.TryParse(parts[1], out uploaded))
return false;
Events.OnBandwidthChanged(new BandwidthEventArgs(new Bytes(downloaded).Normalize(), new Bytes(uploaded).Normalize()));
return true;
}
#endregion
}
}

View File

@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tor.Helpers;
using System.ComponentModel;
namespace Tor.Events
{
/// <summary>
/// A class containing the logic for parsing a circuit-status event.
/// </summary>
[EventAssoc(Event.Circuits)]
internal sealed class CircuitDispatcher : Dispatcher
{
#region Tor.Events.Dispatcher
/// <summary>
/// Dispatches the event, parsing the content of the line and raising the relevant event.
/// </summary>
/// <param name="line">The line which was received from the control connection.</param>
/// <returns>
/// <c>true</c> if the event is parsed and dispatched successfully; otherwise, <c>false</c>.
/// </returns>
public override bool Dispatch(string line)
{
int index = 0;
int circuitID = 0;
string[] parts = StringHelper.GetAll(line, ' ');
Circuit circuit = null;
List<string> routers = new List<string>();
if (parts == null || parts.Length < 2)
return false;
if (!int.TryParse(parts[0], out circuitID))
return false;
circuit = new Circuit(Client, circuitID);
circuit.Status = ReflectionHelper.GetEnumerator<CircuitStatus, DescriptionAttribute>(attr => parts[1].Equals(attr.Description, StringComparison.CurrentCultureIgnoreCase));
for (int i = 2, length = parts.Length; i < length; i++)
{
string data = parts[i];
index += data.Length + 1;
data = data.Trim();
if (!data.Contains("="))
{
routers.AddRange(data.Split(','));
continue;
}
string[] values = data.Split(new[] { '=' }, 2);
string name = values[0].Trim();
string value = values[1].Trim();
if (name.Equals("BUILD_FLAGS"))
{
string[] flags = value.Split(',');
foreach (string flag in flags)
circuit.BuildFlags |= ReflectionHelper.GetEnumerator<CircuitBuildFlags, DescriptionAttribute>(attr => flag.Equals(attr.Description, StringComparison.CurrentCultureIgnoreCase));
}
else
{
switch (name)
{
case "HS_STATE":
circuit.HSState = ReflectionHelper.GetEnumerator<CircuitHSState, DescriptionAttribute>(attr => value.Equals(attr.Description, StringComparison.CurrentCultureIgnoreCase));
break;
case "PURPOSE":
circuit.Purpose = ReflectionHelper.GetEnumerator<CircuitPurpose, DescriptionAttribute>(attr => value.Equals(attr.Description, StringComparison.CurrentCultureIgnoreCase));
break;
case "REASON":
circuit.Reason = ReflectionHelper.GetEnumerator<CircuitReason, DescriptionAttribute>(attr => value.Equals(attr.Description, StringComparison.CurrentCultureIgnoreCase));
break;
case "TIME_CREATED":
DateTime timeCreated;
if (DateTime.TryParse(value, out timeCreated))
circuit.TimeCreated = timeCreated;
else
circuit.TimeCreated = DateTime.MinValue;
break;
}
}
}
circuit.Paths = routers;
Events.OnCircuitChanged(new CircuitEventArgs(circuit));
return true;
}
#endregion
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tor.Events
{
/// <summary>
/// A class containing the logic for parsing a config-changed event.
/// </summary>
[EventAssoc(Event.ConfigChanged)]
internal sealed class ConfigChangedDispatcher : Dispatcher
{
#region Tor.Events.Dispatcher
/// <summary>
/// Dispatches the event, parsing the content of the line and raising the relevant event.
/// </summary>
/// <param name="line">The line which was received from the control connection.</param>
/// <returns>
/// <c>true</c> if the event is parsed and dispatched successfully; otherwise, <c>false</c>.
/// </returns>
public override bool Dispatch(string line)
{
string[] lines = line.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
if (lines.Length == 0)
return false;
Dictionary<string, string> configurations = new Dictionary<string,string>(lines.Length);
foreach (string part in lines)
{
string trimmed = part.Trim();
if (trimmed.Length == 0)
continue;
if (trimmed.StartsWith("650"))
trimmed = trimmed.Substring(3);
if (trimmed.Length > 0 && trimmed[0] == '-')
trimmed = trimmed.Substring(1);
if (trimmed.Length == 0)
continue;
string[] values = trimmed.Split(new[] { '=' }, 2);
if (values.Length == 1)
configurations[values[0].Trim()] = null;
else
configurations[values[0].Trim()] = values[1].Trim();
}
Client.Events.OnConfigurationChanged(new ConfigurationChangedEventArgs(configurations));
return false;
}
#endregion
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tor.Events
{
/// <summary>
/// A class containing the logic for parsing a log message event.
/// </summary>
[EventAssoc(Event.LogDebug | Event.LogError | Event.LogInfo | Event.LogNotice | Event.LogWarn)]
internal sealed class LogDispatcher : Dispatcher
{
#region Tor.Events.Dispatcher
/// <summary>
/// Dispatches the event, parsing the content of the line and raising the relevant event.
/// </summary>
/// <param name="line">The line which was received from the control connection.</param>
/// <returns>
/// <c>true</c> if the event is parsed and dispatched successfully; otherwise, <c>false</c>.
/// </returns>
public override bool Dispatch(string line)
{
string[] lines = line.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
if (lines.Length == 0)
return false;
foreach (string value in lines)
{
string trimmed = value.Trim();
if (trimmed.Length == 0 || trimmed.Equals("."))
continue;
if (trimmed.StartsWith("250"))
continue;
Client.Events.OnLogReceived(new LogEventArgs(trimmed), CurrentEvent);
}
return true;
}
#endregion
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tor.Helpers;
using System.ComponentModel;
namespace Tor.Events
{
/// <summary>
/// A class containing the logic for dispatching an OR connection changed event.
/// </summary>
[EventAssoc(Event.ORConnections)]
internal sealed class ORConnectionDispatcher : Dispatcher
{
#region Tor.Events.Dispatcher
/// <summary>
/// Dispatches the event, parsing the content of the line and raising the relevant event.
/// </summary>
/// <param name="line">The line which was received from the control connection.</param>
/// <returns>
/// <c>true</c> if the event is parsed and dispatched successfully; otherwise, <c>false</c>.
/// </returns>
public override bool Dispatch(string line)
{
string target;
ORStatus status;
string[] parts = StringHelper.GetAll(line, ' ');
if (parts.Length < 2)
return false;
target = parts[0];
status = ReflectionHelper.GetEnumerator<ORStatus, DescriptionAttribute>(attr => parts[1].Equals(attr.Description, StringComparison.CurrentCultureIgnoreCase));
ORConnection connection = new ORConnection();
connection.Status = status;
connection.Target = target;
for (int i = 2; i < parts.Length; i++)
{
string data = parts[i].Trim();
if (!data.Contains("="))
continue;
string[] values = data.Split(new[] { '=' }, 2);
if (values.Length < 2)
continue;
string name = values[0].Trim();
string value = values[1].Trim();
if ("REASON".Equals(name, StringComparison.CurrentCultureIgnoreCase))
{
connection.Reason = ReflectionHelper.GetEnumerator<ORReason, DescriptionAttribute>(attr => value.Equals(attr.Description, StringComparison.CurrentCultureIgnoreCase));
continue;
}
if ("NCIRCS".Equals(name, StringComparison.CurrentCultureIgnoreCase))
{
int circuits;
if (int.TryParse(value, out circuits))
connection.CircuitCount = circuits;
continue;
}
if ("ID".Equals(name, StringComparison.CurrentCultureIgnoreCase))
{
int id;
if (int.TryParse(value, out id))
connection.ID = id;
continue;
}
}
Client.Events.OnORConnectionChanged(new ORConnectionEventArgs(connection));
return true;
}
#endregion
}
}

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tor.Helpers;
using System.ComponentModel;
namespace Tor.Events
{
/// <summary>
/// A class containing the logic for processing a stream-status event.
/// </summary>
[EventAssoc(Event.Streams)]
internal sealed class StreamDispatcher : Dispatcher
{
#region Tor.Events.Dispatcher
/// <summary>
/// Dispatches the event, parsing the content of the line and raising the relevant event.
/// </summary>
/// <param name="line">The line which was received from the control connection.</param>
/// <returns>
/// <c>true</c> if the event is parsed and dispatched successfully; otherwise, <c>false</c>.
/// </returns>
public override bool Dispatch(string line)
{
int streamID;
int circuitID;
int port;
StreamStatus status;
Host target;
string[] parts = StringHelper.GetAll(line, ' ');
if (parts.Length < 4)
return false;
if ("Tor_internal".Equals(parts[3], StringComparison.CurrentCultureIgnoreCase))
return false;
if (!int.TryParse(parts[0], out streamID))
return false;
if (!int.TryParse(parts[2], out circuitID))
return false;
string[] targetParts = parts[3].Split(new[] { ':' }, 2);
if (targetParts.Length < 2)
return false;
if (!int.TryParse(targetParts[1], out port))
return false;
status = ReflectionHelper.GetEnumerator<StreamStatus, DescriptionAttribute>(attr => parts[1].Equals(attr.Description, StringComparison.CurrentCultureIgnoreCase));
target = new Host(targetParts[0], port);
Stream stream = new Stream(Client, streamID, target);
stream.CircuitID = circuitID;
stream.Status = status;
for (int i = 4; i < parts.Length; i++)
{
string data = parts[i].Trim();
if (!data.Contains("="))
continue;
string[] values = data.Split(new[] { '=' }, 2);
if (values.Length < 2)
continue;
string name = values[0].Trim();
string value = values[1].Trim();
if ("REASON".Equals(name, StringComparison.CurrentCultureIgnoreCase))
{
stream.Reason = ReflectionHelper.GetEnumerator<StreamReason, DescriptionAttribute>(attr => value.Equals(attr.Description, StringComparison.CurrentCultureIgnoreCase));
continue;
}
if ("PURPOSE".Equals(name, StringComparison.CurrentCultureIgnoreCase))
{
stream.Purpose = ReflectionHelper.GetEnumerator<StreamPurpose, DescriptionAttribute>(attr => value.Equals(attr.Description, StringComparison.CurrentCultureIgnoreCase));
continue;
}
}
Events.OnStreamChanged(new StreamEventArgs(stream));
return true;
}
#endregion
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace Tor.Events
{
/// <summary>
/// An enumerator containing the list of events which can be monitored in the tor service.
/// </summary>
[Flags]
public enum Event : int
{
/// <summary>
/// The event was unrecognised.
/// </summary>
[Description(null)]
Unknown = 0x000,
/// <summary>
/// An event raised when the circuit status is changed.
/// </summary>
[Description("CIRC")]
Circuits = 0x001,
/// <summary>
/// An event raised when a stream status is changed.
/// </summary>
[Description("STREAM")]
Streams = 0x002,
/// <summary>
/// An event raised when an OR connection status is changed.
/// </summary>
[Description("ORCONN")]
ORConnections = 0x004,
/// <summary>
/// An event raised when the bandwidth used within the last second has changed.
/// </summary>
[Description("BW")]
Bandwidth = 0x008,
/// <summary>
/// An event raised when the value of a configuration has changed.
/// </summary>
[Description("CONF_CHANGED")]
ConfigChanged = 0x010,
/// <summary>
/// An event raised when a debug message is produced.
/// </summary>
[Description("DEBUG")]
LogDebug = 0x020,
/// <summary>
/// An event raised when an information message is produced.
/// </summary>
[Description("INFO")]
LogInfo = 0x040,
/// <summary>
/// An event raised when a notice message is produced.
/// </summary>
[Description("NOTICE")]
LogNotice = 0x080,
/// <summary>
/// An event raised when a warning message is produced.
/// </summary>
[Description("WARN")]
LogWarn = 0x100,
/// <summary>
/// An event raised when an error message is produced.
/// </summary>
[Description("ERR")]
LogError = 0x200,
}
}

View File

@@ -0,0 +1,240 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using Tor.Helpers;
namespace Tor.Events
{
/// <summary>
/// A class which provides monitoring for events occuring within the tor service.
/// </summary>
public sealed class Events : IDisposable
{
private readonly static string EOL = "\r\n";
private readonly Client client;
private readonly List<Event> events;
private readonly object synchronize;
private StringBuilder backlog;
private byte[] buffer;
private volatile bool disposed;
private volatile bool enabled;
private Socket socket;
/// <summary>
/// Initializes a new instance of the <see cref="Events"/> class.
/// </summary>
/// <param name="client">The client for which this object instance belongs.</param>
internal Events(Client client)
{
this.backlog = new StringBuilder();
this.buffer = new byte[256];
this.client = client;
this.disposed = false;
this.enabled = false;
this.events = new List<Event>();
this.socket = null;
this.synchronize = new object();
}
#region Properties
/// <summary>
/// Gets or sets a value indicating whether the client should monitor for interested events within the tor service.
/// </summary>
public bool Enabled
{
get
{
return enabled;
}
}
#endregion
#region System.IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
private void Dispose(bool disposing)
{
}
#endregion
#region System.Net.Sockets.Socket
/// <summary>
/// Called when the socket connects to the control port.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketConnect(IAsyncResult ar)
{
try
{
socket.EndConnect(ar);
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, OnSocketReceive, null);
}
catch { }
}
/// <summary>
/// Called when the socket receives data from the control port.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketReceive(IAsyncResult ar)
{
try
{
int received = socket.EndReceive(ar);
if (received <= 0)
return;
SetEventResponse(Encoding.ASCII.GetString(buffer, 0, received));
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, OnSocketReceive, null);
}
catch { }
}
/// <summary>
/// Called when the socket completes sending a list of interested events.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketSendEvents(IAsyncResult ar)
{
try
{
socket.EndSend(ar);
}
catch { }
}
#endregion
/// <summary>
/// Signals to the control connection to begin monitoring for interested events.
/// </summary>
private void SetEvents()
{
if (disposed)
throw new ObjectDisposedException("this");
lock (synchronize)
{
if (socket == null || !enabled)
return;
StringBuilder builder = new StringBuilder("SETEVENTS");
foreach (Event interested in events)
{
string name = ReflectionHelper.GetDescription<Event>(interested);
builder.Append(' ');
builder.Append(name);
}
string command = builder.Append("\r\n").ToString();
byte[] buffer = Encoding.ASCII.GetBytes(command);
socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, OnSocketSendEvents, null);
}
}
/// <summary>
/// Sets the current response data received from the control port, which is event information.
/// </summary>
/// <param name="response">The response received from the control port.</param>
private void SetEventResponse(string response)
{
if (response == null)
throw new ArgumentNullException("response");
if (response.Length == 0)
return;
if (backlog.Length > 0)
response = new StringBuilder(backlog.Length + response.Length).Append(backlog).Append(response).ToString();
int index = 0;
int length = response.Length;
if (response.Contains(EOL))
{
for (int next = response.IndexOf(EOL); 0 <= next; next = response.IndexOf(EOL, next))
{
if (next + EOL.Length == length)
{
index = length;
break;
}
string line = response.Substring(index, next);
index = next + EOL.Length;
}
}
if (backlog.Length > 0 && index == length)
backlog = new StringBuilder();
if (index < length)
backlog = new StringBuilder(response.Substring(index));
}
/// <summary>
/// Starts the process of monitoring for events by launching a control connection.
/// </summary>
private void Start()
{
if (disposed)
throw new ObjectDisposedException("this");
lock (synchronize)
{
if (socket != null || !enabled)
return;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);
socket.BeginConnect("127.0.0.1", client.Configuration.ControlPort, OnSocketConnect, null);
}
}
/// <summary>
/// Shuts down the listener socket and releases all resources associated with it.
/// </summary>
private void Shutdown()
{
lock (synchronize)
{
if (socket != null)
{
try
{
socket.Shutdown(SocketShutdown.Both);
}
catch { }
socket.Dispose();
socket = null;
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace Tor.Events
{
/// <summary>
/// An enumerator containing the list of events which can be monitored in the tor service.
/// </summary>
public enum Event
{
/// <summary>
/// An event raised when the circuit status is changed.
/// </summary>
[Description("CIRC")]
Circuits,
/// <summary>
/// An event raised when a stream status is changed.
/// </summary>
[Description("STREAM")]
Streams,
/// <summary>
/// An event raised when an OR connection status is changed.
/// </summary>
[Description("ORCONN")]
ORConnections,
/// <summary>
/// An event raised when the bandwidth used within the last second has changed.
/// </summary>
[Description("BW")]
Bandwidth,
/// <summary>
/// An event raised when new descriptors are available.
/// </summary>
[Description("NEWDESC")]
NewDescriptors,
/// <summary>
/// An event raised when a new address mapping is registered in the tor address map cache.
/// </summary>
[Description("ADDRMAP")]
AddressMapping,
}
}

View File

@@ -0,0 +1,652 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using Tor.Helpers;
using System.ComponentModel;
using System.Reflection;
using System.Diagnostics;
namespace Tor.Events
{
/// <summary>
/// A class which provides monitoring for events occuring within the tor service.
/// </summary>
public sealed class Events : IDisposable
{
private readonly static Dictionary<Event, Type> dispatchers;
private readonly static string EOL = "\r\n";
private readonly Client client;
private readonly Dictionary<Event, int> events;
private readonly object synchronize;
private StringBuilder backlog;
private Action connectCallback;
private StringBuilder multiLine;
private byte[] buffer;
private volatile bool disposed;
private volatile bool enabled;
private Socket socket;
private event BandwidthEventHandler bandwidthChangedHandlers;
private event CircuitEventHandler circuitChangedHandlers;
private event ConfigurationChangedEventHandler configurationChangedHandlers;
private event LogEventHandler debugReceivedHandlers;
private event LogEventHandler errorReceivedHandlers;
private event LogEventHandler infoReceivedHandlers;
private event LogEventHandler noticeReceivedHandlers;
private event ORConnectionEventHandler orConnectionChangedHandlers;
private event StreamEventHandler streamChangedHandlers;
private event LogEventHandler warnReceivedHandlers;
/// <summary>
/// Initializes the <see cref="Events"/> class.
/// </summary>
static Events()
{
dispatchers = new Dictionary<Event, Type>();
Event[] values = Enum.GetValues(typeof(Event)) as Event[];
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes().Where(t => t.Namespace == "Tor.Events" && typeof(Dispatcher).IsAssignableFrom(t)))
{
EventAssocAttribute attribute = Attribute.GetCustomAttribute(type, typeof(EventAssocAttribute)) as EventAssocAttribute;
if (attribute == null)
continue;
foreach (Event value in values)
{
if ((attribute.Event & value) == value)
dispatchers[value] = type;
}
}
}
/// <summary>
/// Initializes a new instance of the <see cref="Events"/> class.
/// </summary>
/// <param name="client">The client for which this object instance belongs.</param>
internal Events(Client client)
{
this.backlog = new StringBuilder();
this.buffer = new byte[256];
this.connectCallback = null;
this.client = client;
this.disposed = false;
this.enabled = true;
this.events = new Dictionary<Event, int>();
this.multiLine = null;
this.socket = null;
this.synchronize = new object();
}
#region Events
/// <summary>
/// Occurs when the bandwidth download and upload values change within the tor service.
/// </summary>
public event BandwidthEventHandler BandwidthChanged
{
add { bandwidthChangedHandlers += value; AddEvent(Event.Bandwidth); }
remove { bandwidthChangedHandlers += value; RemoveEvent(Event.Bandwidth); }
}
/// <summary>
/// Occurs when a circuit has changed within the tor service.
/// </summary>
public event CircuitEventHandler CircuitChanged
{
add { circuitChangedHandlers += value; AddEvent(Event.Circuits); }
remove { circuitChangedHandlers -= value; RemoveEvent(Event.Circuits); }
}
/// <summary>
/// Occurs when configurations have changed within the tor service.
/// </summary>
public event ConfigurationChangedEventHandler ConfigurationChanged
{
add { configurationChangedHandlers += value; AddEvent(Event.ConfigChanged); }
remove { configurationChangedHandlers -= value; RemoveEvent(Event.ConfigChanged); }
}
/// <summary>
/// Occurs when a debug log message is received.
/// </summary>
public event LogEventHandler DebugReceived
{
add { debugReceivedHandlers += value; AddEvent(Event.LogDebug); }
remove { debugReceivedHandlers -= value; RemoveEvent(Event.LogDebug); }
}
/// <summary>
/// Occurs when an error log message is received.
/// </summary>
public event LogEventHandler ErrorReceived
{
add { errorReceivedHandlers += value; AddEvent(Event.LogError); }
remove { errorReceivedHandlers -= value; RemoveEvent(Event.LogError); }
}
/// <summary>
/// Occurs when an information log message is received.
/// </summary>
public event LogEventHandler InfoReceived
{
add { infoReceivedHandlers += value; AddEvent(Event.LogInfo); }
remove { infoReceivedHandlers -= value; RemoveEvent(Event.LogInfo); }
}
/// <summary>
/// Occurs when a notice log message is received.
/// </summary>
public event LogEventHandler NoticeReceived
{
add { noticeReceivedHandlers += value; AddEvent(Event.LogNotice); }
remove { noticeReceivedHandlers -= value; RemoveEvent(Event.LogNotice); }
}
/// <summary>
/// Occurs when an OR connection has changed within the tor service.
/// </summary>
public event ORConnectionEventHandler ORConnectionChanged
{
add { orConnectionChangedHandlers += value; AddEvent(Event.ORConnections); }
remove { orConnectionChangedHandlers -= value; RemoveEvent(Event.ORConnections); }
}
/// <summary>
/// Occurs when a stream has changed within the tor service.
/// </summary>
public event StreamEventHandler StreamChanged
{
add { streamChangedHandlers += value; AddEvent(Event.Streams); }
remove { streamChangedHandlers -= value; RemoveEvent(Event.Streams); }
}
/// <summary>
/// Occurs when a warning log message is received.
/// </summary>
public event LogEventHandler WarnReceived
{
add { warnReceivedHandlers += value; AddEvent(Event.LogWarn); }
remove { warnReceivedHandlers -= value; RemoveEvent(Event.LogWarn); }
}
#endregion
#region Events Invoke
/// <summary>
/// Raises the <see cref="E:BandwidthChanged" /> event.
/// </summary>
/// <param name="e">The <see cref="BandwidthEventArgs"/> instance containing the event data.</param>
internal void OnBandwidthChanged(BandwidthEventArgs e)
{
if (bandwidthChangedHandlers != null)
bandwidthChangedHandlers(client, e);
}
/// <summary>
/// Raises the <see cref="E:CircuitChanged" /> event.
/// </summary>
/// <param name="e">The <see cref="CircuitEventArgs"/> instance containing the event data.</param>
internal void OnCircuitChanged(CircuitEventArgs e)
{
if (circuitChangedHandlers != null)
circuitChangedHandlers(client, e);
}
/// <summary>
/// Raises the <see cref="E:ConfigurationChanged" /> event.
/// </summary>
/// <param name="e">The <see cref="ConfigurationChangedEventArgs"/> instance containing the event data.</param>
internal void OnConfigurationChanged(ConfigurationChangedEventArgs e)
{
if (configurationChangedHandlers != null)
configurationChangedHandlers(client, e);
}
/// <summary>
/// Raises the <see cref="E:LogReceived" /> event.
/// </summary>
/// <param name="e">The <see cref="LogEventArgs"/> instance containing the event data.</param>
/// <param name="evt">The event which was processed.</param>
internal void OnLogReceived(LogEventArgs e, Event evt)
{
switch (evt)
{
case Event.LogDebug:
if (debugReceivedHandlers != null)
debugReceivedHandlers(client, e);
break;
case Event.LogError:
if (errorReceivedHandlers != null)
errorReceivedHandlers(client, e);
break;
case Event.LogInfo:
if (infoReceivedHandlers != null)
infoReceivedHandlers(client, e);
break;
case Event.LogNotice:
if (noticeReceivedHandlers != null)
noticeReceivedHandlers(client, e);
break;
case Event.LogWarn:
if (warnReceivedHandlers != null)
warnReceivedHandlers(client, e);
break;
}
}
/// <summary>
/// Raises the <see cref="E:ORConnectionChanged" /> event.
/// </summary>
/// <param name="e">The <see cref="ORConnectionEventArgs"/> instance containing the event data.</param>
internal void OnORConnectionChanged(ORConnectionEventArgs e)
{
if (orConnectionChangedHandlers != null)
orConnectionChangedHandlers(client, e);
}
/// <summary>
/// Raises the <see cref="E:StreamChanged" /> event.
/// </summary>
/// <param name="e">The <see cref="StreamEventArgs"/> instance containing the event data.</param>
internal void OnStreamChanged(StreamEventArgs e)
{
if (streamChangedHandlers != null)
streamChangedHandlers(client, e);
}
#endregion
#region System.IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
private void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
Shutdown();
disposed = true;
}
}
#endregion
#region System.Net.Sockets.Socket
/// <summary>
/// Called when the socket connects to the control port.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketConnect(IAsyncResult ar)
{
try
{
socket.EndConnect(ar);
string authenticate = string.Format("authenticate \"{0}\"{1}", client.GetControlPassword(), EOL);
byte[] authenticateBuffer = Encoding.ASCII.GetBytes(authenticate);
socket.BeginSend(authenticateBuffer, 0, authenticateBuffer.Length, SocketFlags.None, OnSocketSendAuthenticate, null);
}
catch { }
}
/// <summary>
/// Called when the socket receives a response to the authentication command.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketReceiveAuthenticate(IAsyncResult ar)
{
try
{
int received = socket.EndReceive(ar);
if (received <= 0)
return;
string response = Encoding.ASCII.GetString(buffer, 0, received);
if (response.Length < 3 || !response.Substring(0, 3).Equals("250"))
throw new TorException("The events manager failed to authenticate with the control connection");
if (connectCallback != null)
connectCallback();
SetEvents();
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, OnSocketReceive, null);
}
catch { }
}
/// <summary>
/// Called when the socket receives data from the control port.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketReceive(IAsyncResult ar)
{
try
{
int received = socket.EndReceive(ar);
if (received <= 0)
return;
string response = Encoding.ASCII.GetString(buffer, 0, received);
SetEventResponse(response);
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, OnSocketReceive, null);
}
catch { }
}
/// <summary>
/// Called when the socket has completed sending the authentication command.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketSendAuthenticate(IAsyncResult ar)
{
try
{
socket.EndSend(ar);
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, OnSocketReceiveAuthenticate, null);
}
catch { }
}
/// <summary>
/// Called when the socket completes sending a list of interested events.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketSendEvents(IAsyncResult ar)
{
try
{
socket.EndSend(ar);
}
catch { }
}
#endregion
/// <summary>
/// Adds an event to the list of monitored events.
/// </summary>
/// <param name="evt">The event which should be monitored for.</param>
private void AddEvent(Event evt)
{
if (disposed)
throw new ObjectDisposedException("this");
lock (synchronize)
{
int counter = 0;
events.TryGetValue(evt, out counter);
events[evt] = ++counter;
if (enabled && counter == 1)
SetEvents();
}
}
/// <summary>
/// Dispatches an event by parsing the content of the line and raising the relevant event.
/// </summary>
/// <param name="line">The line received from the control connection.</param>
private void Dispatch(string line)
{
if (line == null)
throw new ArgumentNullException("line");
string trimmed = line.Trim();
if (trimmed.Length < 3)
return;
string[] parts = null;
if (trimmed.Length > 3 && trimmed[3] == '-')
{
string[] intermediate = trimmed.Split(new[] { '-' }, 2); // [650, EVENT....]
if (intermediate.Length < 2)
return;
string status = intermediate[0].Trim();
string content = intermediate[1].Trim();
string[] data = content.Split(new string[] { EOL }, 2, StringSplitOptions.None); // [EVENT, ...]
if (data.Length < 2)
return;
parts = new string[] { status, data[0], data[1] };
}
else
parts = trimmed.Split(new[] { ' ' }, 3);
if (parts.Length < 2)
return;
if (parts[0].Equals("650"))
{
Event evt = ReflectionHelper.GetEnumerator<Event, DescriptionAttribute>(attr => parts[1].Equals(attr.Description, StringComparison.CurrentCultureIgnoreCase));
if (evt == Event.Unknown)
return;
Type type;
if (!dispatchers.TryGetValue(evt, out type))
return;
Dispatcher dispatcher = Activator.CreateInstance(type) as Dispatcher;
dispatcher.Client = client;
dispatcher.CurrentEvent = evt;
dispatcher.Events = this;
if (dispatcher == null)
return;
dispatcher.Dispatch(parts[2]);
}
}
/// <summary>
/// Removes an event from the list of monitored events.
/// </summary>
/// <param name="evt">The event which should be removed.</param>
private void RemoveEvent(Event evt)
{
if (disposed)
throw new ObjectDisposedException("this");
lock (synchronize)
{
int counter = 0;
if (!events.ContainsKey(evt))
return;
events.TryGetValue(evt, out counter);
counter--;
if (counter == 0)
{
events.Remove(evt);
SetEvents();
}
else
events[evt] = counter;
}
}
/// <summary>
/// Signals to the control connection to begin monitoring for interested events.
/// </summary>
private void SetEvents()
{
if (disposed)
throw new ObjectDisposedException("this");
lock (synchronize)
{
if (socket == null || !socket.Connected || !enabled)
return;
StringBuilder builder = new StringBuilder("SETEVENTS");
foreach (KeyValuePair<Event, int> interested in events)
{
string name = ReflectionHelper.GetDescription<Event>(interested.Key);
builder.Append(' ');
builder.Append(name);
}
string command = builder.Append("\r\n").ToString();
byte[] buffer = Encoding.ASCII.GetBytes(command);
socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, OnSocketSendEvents, null);
}
}
/// <summary>
/// Sets the current response data received from the control port, which is event information.
/// </summary>
/// <param name="response">The response received from the control port.</param>
private void SetEventResponse(string response)
{
if (response == null)
throw new ArgumentNullException("response");
if (response.Length == 0)
return;
if (backlog.Length > 0)
response = new StringBuilder(backlog.Length + response.Length).Append(backlog).Append(response).ToString();
int index = 0;
int length = response.Length;
List<string> dispatch = new List<string>();
if (response.Contains(EOL))
{
for (int next = response.IndexOf(EOL); 0 <= next; next = response.IndexOf(EOL, next))
{
string line = response.Substring(index, next - index);
next += EOL.Length;
index = next;
if (line.Length == 0)
{
if (index == length)
break;
continue;
}
if (line.StartsWith("250"))
continue;
if (3 < line.Length && line[3] == '-')
{
if (multiLine == null)
multiLine = new StringBuilder();
multiLine.AppendLine(line);
continue;
}
if (multiLine != null)
{
dispatch.Add(multiLine.ToString());
multiLine = null;
}
else
dispatch.Add(line);
if (index == length)
break;
}
}
if (backlog.Length > 0 && index == length)
backlog = new StringBuilder();
if (index < length)
backlog = new StringBuilder(response.Substring(index));
if (dispatch.Count > 0)
foreach (string line in dispatch)
Dispatch(line);
}
/// <summary>
/// Starts the process of monitoring for events by launching a control connection.
/// </summary>
/// <param name="callback">A method raised when the connection has completed.</param>
internal void Start(Action callback)
{
if (disposed)
throw new ObjectDisposedException("this");
lock (synchronize)
{
if (socket != null || !enabled)
return;
connectCallback = callback;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);
socket.BeginConnect(client.GetClientAddress(), client.GetControlPort(), OnSocketConnect, null);
}
}
/// <summary>
/// Shuts down the listener socket and releases all resources associated with it.
/// </summary>
internal void Shutdown()
{
lock (synchronize)
{
if (socket != null)
{
try
{
socket.Shutdown(SocketShutdown.Both);
}
catch { }
socket.Dispose();
socket = null;
}
}
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tor
{
/// <summary>
/// A class containing information regarding bandwidth values changing.
/// </summary>
[Serializable]
public sealed class BandwidthEventArgs : EventArgs
{
private readonly Bytes downloaded;
private readonly Bytes uploaded;
/// <summary>
/// Initializes a new instance of the <see cref="BandwidthEventArgs"/> class.
/// </summary>
/// <param name="downloaded">The bytes downloaded.</param>
/// <param name="uploaded">The bytes uploaded.</param>
public BandwidthEventArgs(Bytes downloaded, Bytes uploaded)
{
this.downloaded = downloaded;
this.uploaded = uploaded;
}
#region Properties
/// <summary>
/// Gets the bytes downloaded.
/// </summary>
public Bytes Downloaded
{
get { return downloaded; }
}
/// <summary>
/// Gets the bytes uploaded.
/// </summary>
public Bytes Uploaded
{
get { return uploaded; }
}
#endregion
}
/// <summary>
/// Represents the method that will handle a bandwidth changed event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="BandwidthEventArgs"/> instance containing the event data.</param>
public delegate void BandwidthEventHandler(object sender, BandwidthEventArgs e);
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tor
{
/// <summary>
/// A class containing information regarding a circuit status changing.
/// </summary>
public sealed class CircuitEventArgs : EventArgs
{
private readonly Circuit circuit;
/// <summary>
/// Initializes a new instance of the <see cref="CircuitEventArgs"/> class.
/// </summary>
/// <param name="circuit">The circuit which was changed.</param>
public CircuitEventArgs(Circuit circuit)
{
this.circuit = circuit;
}
#region Properties
/// <summary>
/// Gets the circuit which was changed.
/// </summary>
public Circuit Circuit
{
get { return circuit; }
}
#endregion
}
/// <summary>
/// Represents the method that will handle a circuit changed event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="CircuitEventArgs"/> instance containing the event data.</param>
public delegate void CircuitEventHandler(object sender, CircuitEventArgs e);
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tor.Events
{
/// <summary>
/// A class containing information regarding configuration values changing.
/// </summary>
public sealed class ConfigurationChangedEventArgs : EventArgs
{
private readonly Dictionary<string, string> configurations;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationChangedEventArgs"/> class.
/// </summary>
/// <param name="configurations">The configurations which were changed.</param>
internal ConfigurationChangedEventArgs(Dictionary<string, string> configurations)
{
this.configurations = configurations;
}
#region Properties
/// <summary>
/// Gets the configurations which were changed.
/// </summary>
public Dictionary<string, string> Configurations
{
get { return configurations; }
}
#endregion
}
/// <summary>
/// Represents the method that will handle a configuration changed event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="ConfigurationChangedEventArgs"/> instance containing the event data.</param>
public delegate void ConfigurationChangedEventHandler(object sender, ConfigurationChangedEventArgs e);
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tor
{
/// <summary>
/// A class containing information regarding a log message received from a client.
/// </summary>
public sealed class LogEventArgs : EventArgs
{
private readonly string message;
/// <summary>
/// Initializes a new instance of the <see cref="LogEventArgs"/> class.
/// </summary>
/// <param name="message">The message which was received.</param>
public LogEventArgs(string message)
{
this.message = message;
}
#region Properties
/// <summary>
/// Gets the message which was received.
/// </summary>
public string Message
{
get { return message; }
}
#endregion
}
/// <summary>
/// Represents the method that will handle a log message event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="LogEventArgs"/> instance containing the event data.</param>
public delegate void LogEventHandler(object sender, LogEventArgs e);
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tor
{
/// <summary>
/// A class containing information regarding an OR connection event.
/// </summary>
[Serializable]
public sealed class ORConnectionEventArgs : EventArgs
{
private readonly ORConnection connection;
/// <summary>
/// Initializes a new instance of the <see cref="ORConnectionEventArgs"/> class.
/// </summary>
/// <param name="connection">The connection which was changed.</param>
public ORConnectionEventArgs(ORConnection connection)
{
this.connection = connection;
}
#region Properties
/// <summary>
/// Gets the connection which was changed.
/// </summary>
public ORConnection Connection
{
get { return connection; }
}
#endregion
}
/// <summary>
/// Represents the method that will handle an OR connection changed event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="ORConnectionEventArgs"/> instance containing the event data.</param>
public delegate void ORConnectionEventHandler(object sender, ORConnectionEventArgs e);
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tor
{
/// <summary>
/// A class containing information regarding a stream status changing.
/// </summary>
public sealed class StreamEventArgs : EventArgs
{
private readonly Stream stream;
/// <summary>
/// Initializes a new instance of the <see cref="StreamEventArgs"/> class.
/// </summary>
/// <param name="stream">The stream which was changed.</param>
public StreamEventArgs(Stream stream)
{
this.stream = stream;
}
#region Properties
/// <summary>
/// Gets the stream which was changed.
/// </summary>
public Stream Stream
{
get { return stream; }
}
#endregion
}
/// <summary>
/// Represents the method that will handle a stream changed event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="StreamEventArgs"/> instance containing the event data.</param>
public delegate void StreamEventHandler(object sender, StreamEventArgs e);
}