using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tor.Controller;
using System.Text.RegularExpressions;
using System.Diagnostics;
namespace Tor.Status
{
///
/// A class containing methods and properties for reading the status of the tor network service.
///
[DebuggerStepThrough]
public sealed class Status : MarshalByRefObject
{
private const int MaximumRecords = 30;
private readonly List circuits;
private readonly Client client;
private readonly List orConnections;
private readonly List streams;
private readonly object synchronizeCircuits;
private readonly object synchronizeORConnections;
private readonly object synchronizeStreams;
///
/// Initializes a new instance of the class.
///
/// The client for which this object instance belongs.
internal Status(Client client)
{
this.circuits = new List();
this.client = client;
this.orConnections = new List();
this.streams = new List();
this.synchronizeCircuits = new object();
this.synchronizeORConnections = new object();
this.synchronizeStreams = new object();
}
#region Properties
///
/// Gets the current circuits configured against the tor client.
///
public CircuitCollection Circuits
{
get { lock (synchronizeCircuits) return new CircuitCollection(circuits); }
}
///
/// Gets a value indicating whether the tor software service is dormant. A value of true indicates that
/// the tor network has not been active for a while, or is dormant for some other reason.
///
public bool IsDormant
{
get { return PropertyGetIsDormant(); }
}
///
/// Gets the current OR connections configured against the tor client.
///
public ORConnectionCollection ORConnections
{
get { lock (synchronizeORConnections) return new ORConnectionCollection(orConnections); }
}
///
/// Gets the current streams configured against the tor client.
///
public StreamCollection Streams
{
get { lock (synchronizeStreams) return new StreamCollection(streams); }
}
///
/// Gets an approximation of the total bytes downloaded by the tor software.
///
public Bytes TotalBytesDownloaded
{
get { return PropertyGetTotalBytesDownloaded(); }
}
///
/// Gets an approximation of the total bytes uploaded by the tor software.
///
public Bytes TotalBytesUploaded
{
get { return PropertyGetTotalBytesUploaded(); }
}
///
/// Gets the version number of the running tor application associated with this client.
///
public Version Version
{
get { return PropertyGetVersion(); }
}
#endregion
#region Events
///
/// Occurs when the bandwidth download and upload values change within the tor service. These values are a report of the
/// download and upload rates within the last second.
///
public event BandwidthEventHandler BandwidthChanged
{
add { client.Events.BandwidthChanged += value; }
remove { client.Events.BandwidthChanged -= value; }
}
///
/// Occurs when the circuits have been updated.
///
public event EventHandler CircuitsChanged;
///
/// Occurs when the OR connections have been updated.
///
public event EventHandler ORConnectionsChanged;
///
/// Occurs when the streams have been updated.
///
public event EventHandler StreamsChanged;
#endregion
#region Tor.Events.Events
///
/// Called when a circuit changes within the tor service.
///
/// The sender.
/// The instance containing the event data.
private void OnCircuitChanged(object sender, CircuitEventArgs e)
{
if (e.Circuit == null)
return;
lock (synchronizeCircuits)
{
Circuit existing = circuits.Where(c => c.ID == e.Circuit.ID).FirstOrDefault();
if (existing == null)
{
if (Status.MaximumRecords <= circuits.Count)
{
Circuit removal = circuits.Where(c => c.Status == CircuitStatus.Closed || c.Status == CircuitStatus.Failed).FirstOrDefault();
if (removal == null)
return;
circuits.Remove(removal);
}
existing = e.Circuit;
existing.GetRouters();
circuits.Add(existing);
}
else
{
bool update = existing.Paths.Count != e.Circuit.Paths.Count || e.Circuit.Status == CircuitStatus.Extended;
existing.BuildFlags = e.Circuit.BuildFlags;
existing.HSState = e.Circuit.HSState;
existing.Paths = e.Circuit.Paths;
existing.Purpose = e.Circuit.Purpose;
existing.Reason = e.Circuit.Reason;
existing.Status = e.Circuit.Status;
existing.TimeCreated = e.Circuit.TimeCreated;
if (update)
existing.GetRouters();
}
}
if (CircuitsChanged != null)
CircuitsChanged(client, EventArgs.Empty);
}
///
/// Called when an OR connection changes within the tor service.
///
/// The sender.
/// The instance containing the event data.
private void OnORConnectionChanged(object sender, ORConnectionEventArgs e)
{
if (e.Connection == null)
return;
lock (synchronizeORConnections)
{
ORConnection existing;
if (e.Connection.ID != 0)
existing = orConnections.Where(o => o.ID == e.Connection.ID).FirstOrDefault();
else
existing = orConnections.Where(o => o.Target.Equals(e.Connection.Target, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
if (existing == null)
{
if (Status.MaximumRecords <= orConnections.Count)
{
ORConnection removal = orConnections.Where(o => o.Status == ORStatus.Closed || o.Status == ORStatus.Failed).FirstOrDefault();
if (removal == null)
return;
orConnections.Remove(removal);
}
existing = e.Connection;
orConnections.Add(existing);
}
else
{
existing.CircuitCount = e.Connection.CircuitCount;
existing.ID = e.Connection.ID;
existing.Reason = e.Connection.Reason;
existing.Status = e.Connection.Status;
existing.Target = e.Connection.Target;
}
}
if (ORConnectionsChanged != null)
ORConnectionsChanged(client, EventArgs.Empty);
}
///
/// Called when a stream changes within the tor service.
///
/// The sender.
/// The instance containing the event data.
private void OnStreamChanged(object sender, StreamEventArgs e)
{
if (e.Stream == null)
return;
lock (synchronizeStreams)
{
Stream existing = streams.Where(s => s.ID == e.Stream.ID).FirstOrDefault();
if (existing == null)
{
if (Status.MaximumRecords <= streams.Count)
{
Stream removal = streams.Where(s => s.Status == StreamStatus.Closed || s.Status == StreamStatus.Failed).FirstOrDefault();
if (removal == null)
return;
streams.Remove(removal);
}
existing = e.Stream;
streams.Add(existing);
}
else
{
existing.CircuitID = e.Stream.CircuitID;
existing.Purpose = e.Stream.Purpose;
existing.Reason = e.Stream.Reason;
existing.Status = e.Stream.Status;
}
}
if (StreamsChanged != null)
StreamsChanged(client, EventArgs.Empty);
}
#endregion
///
/// Gets a read-only collection of all ORs which the tor application has an opinion about. This method can be time, memory
/// and CPU intensive, so should be used infrequently.
///
/// A object instance containing the router information; otherwise, null if the request fails.
public RouterCollection GetAllRouters()
{
GetAllRouterStatusCommand command = new GetAllRouterStatusCommand();
GetAllRouterStatusResponse response = command.Dispatch(client);
if (response.Success)
return response.Routers;
return null;
}
///
/// Gets a country code for an IP address. This method will not work unless a geoip and/or geoip6 file has been supplied.
///
/// The IP address which should be resolved.
/// A containing the country code; otherwise, null if the country code could not be resolved.
public string GetCountryCode(string ipAddress)
{
if (ipAddress == null)
throw new ArgumentNullException("router");
GetInfoCommand command = new GetInfoCommand(string.Format("ip-to-country/{0}", ipAddress));
GetInfoResponse response = command.Dispatch(client);
if (response.Success)
{
string[] values = response.Values[0].Split(new[] { '=' }, 2);
if (values.Length == 2)
return values[1].Trim();
}
return null;
}
///
/// Gets a country code for a router within the tor network. This method will not work unless a geoip and/or geoip6 file has been supplied.
///
/// The router to retrieve the country code for.
/// A containing the country code; otherwise, null if the country code could not be resolved.
public string GetCountryCode(Router router)
{
if (router == null)
throw new ArgumentNullException("router");
string address = router.IPAddress.ToString();
GetInfoCommand command = new GetInfoCommand(string.Format("ip-to-country/{0}", address));
GetInfoResponse response = command.Dispatch(client);
if (response.Success)
{
string[] values = response.Values[0].Split(new[] { '=' }, 2);
if (values.Length == 2)
return values[1].Trim();
return values[0].Trim().ToUpper();
}
return null;
}
///
/// Gets a value indicating whether the tor software service is dormant.
///
/// true if the tor software service is dormant; otherwise, false.
private bool PropertyGetIsDormant()
{
GetInfoCommand command = new GetInfoCommand("dormant");
GetInfoResponse response = command.Dispatch(client);
if (!response.Success)
return false;
int value;
if (!int.TryParse(response.Values[0], out value))
return false;
return value != 0;
}
///
/// Gets an approximation of the total bytes downloaded by the tor software.
///
/// A object instance containing the estimated number of bytes.
private Bytes PropertyGetTotalBytesDownloaded()
{
GetInfoCommand command = new GetInfoCommand("traffic/read");
GetInfoResponse response = command.Dispatch(client);
if (!response.Success)
return Bytes.Empty;
double value;
if (!double.TryParse(response.Values[0], out value))
return Bytes.Empty;
return new Bytes(value).Normalize();
}
///
/// Gets an approximation of the total bytes uploaded by the tor software.
///
/// A object instance containing the estimated number of bytes.
private Bytes PropertyGetTotalBytesUploaded()
{
GetInfoCommand command = new GetInfoCommand("traffic/written");
GetInfoResponse response = command.Dispatch(client);
if (!response.Success)
return Bytes.Empty;
double value;
if (!double.TryParse(response.Values[0], out value))
return Bytes.Empty;
return new Bytes(value).Normalize();
}
///
/// Gets the version of the running tor application.
///
/// A object instance containing the version.
private Version PropertyGetVersion()
{
GetInfoCommand command = new GetInfoCommand("version");
GetInfoResponse response = command.Dispatch(client);
if (!response.Success)
return new Version();
Regex pattern = new Regex(@"(?\d{1,})\.(?\d{1,})\.(?\d{1,})\.(?\d{1,})(?:$|\s)");
Match match = pattern.Match(response.Values[0]);
if (match.Success)
return new Version(
Convert.ToInt32(match.Groups["major"].Value),
Convert.ToInt32(match.Groups["minor"].Value),
Convert.ToInt32(match.Groups["build"].Value),
Convert.ToInt32(match.Groups["revision"].Value)
);
return new Version();
}
///
/// Starts the status controller listening for changes within the tor client.
///
internal void Start()
{
this.client.Events.CircuitChanged += new CircuitEventHandler(OnCircuitChanged);
this.client.Events.ORConnectionChanged += new ORConnectionEventHandler(OnORConnectionChanged);
this.client.Events.StreamChanged += new StreamEventHandler(OnStreamChanged);
}
}
}