using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Net;
using System.Net.Security;
using System.Reflection;
using System.Security.Principal;
using System.ServiceModel;
using System.ServiceModel.Description;
using TGS.Interface.Components;
namespace TGS.Interface
{
///
sealed public class Client : IClient
{
///
/// Version of the
///
public static readonly Version Version = Assembly.GetExecutingAssembly().GetName().Version;
///
public IServer Server => server;
///
public Version ServerVersion { get
{
lock (this)
if (_serverVersion == null)
{
string rawVersion;
//check ITGSService first for compatiblity reasons
try
{
rawVersion = GetComponent(null).Version();
}
catch
{
rawVersion = GetComponent(null).Version();
}
var splits = rawVersion.Split(' ');
_serverVersion = new Version(splits[splits.Length - 1].Substring(1));
}
return _serverVersion;
} }
///
public string InstanceName { get; private set; }
///
public RemoteLoginInfo LoginInfo { get { return _loginInfo; } }
///
/// Backing field for
///
readonly IServer server;
///
/// Backing field for
///
readonly RemoteLoginInfo _loginInfo;
///
/// The
///
Version _serverVersion;
///
/// Associated list of open s keyed by type name. A in this list may close or fault at any time. must be locked before being accessed
///
IDictionary ChannelFactoryCache;
///
/// Sets the function called when a remote login fails due to the server having an invalid SSL cert
///
/// The to be called when a remote login is attempted while the server posesses a bad certificate. Passed a of error information about the and should return if it the connection should be made anyway
public static void SetBadCertificateHandler(Func handler)
{
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, error) =>
{
string ErrorMessage;
switch (error)
{
case SslPolicyErrors.None:
return true;
case SslPolicyErrors.RemoteCertificateChainErrors:
ErrorMessage = "There are certificate chain errors.";
break;
case SslPolicyErrors.RemoteCertificateNameMismatch:
ErrorMessage = "The certificate name does not match.";
break;
case SslPolicyErrors.RemoteCertificateNotAvailable:
ErrorMessage = "The certificate doesn't exist in the trust store.";
break;
default:
ErrorMessage = "An unknown error occurred.";
break;
}
ErrorMessage = String.Format("The server's certificate failed to verify! Error: {0} Cert: {1}", ErrorMessage, cert.ToString());
return handler(ErrorMessage);
};
}
///
/// Construct an for a local connection
///
public Client()
{
ChannelFactoryCache = new Dictionary();
server = new Server(this);
}
///
/// Construct an for a remote connection
///
/// The for a remote connection
public Client(RemoteLoginInfo loginInfo) : this()
{
if (!loginInfo.HasPassword)
throw new InvalidOperationException("password must be set on loginInfo!");
_loginInfo = loginInfo;
}
///
public bool IsRemoteConnection { get { return LoginInfo != null; } }
///
/// Closes all s stored in and it
///
public void Dispose()
{
lock (this)
{
if (ChannelFactoryCache == null)
return;
foreach (var I in ChannelFactoryCache)
{
var cf = I.Value;
try
{
cf.Close();
}
catch
{
cf.Abort();
}
}
ChannelFactoryCache = null;
}
}
///
public bool VersionMismatch(out string errorMessage)
{
if (ServerVersion.Major != Version.Major || ServerVersion.Minor != Version.Minor || ServerVersion.Build != Version.Build) //don't care about the patch level
{
errorMessage = String.Format("Version mismatch between interface version ({0}) and service version ({1}). Some functionality may crash this program.", Version, ServerVersion);
return true;
}
errorMessage = null;
return false;
}
///
/// Returns the requested component for the instance . This does not guarantee a successful connection. s created this way are recycled for minimum latency and bandwidth usage
///
/// The component to retrieve
/// The name of the to use
/// The correct component
internal T GetComponent(string instanceName)
{
var actualToT = typeof(T);
var tot = actualToT.Name;
if (instanceName != null)
tot = instanceName + tot;
ChannelFactory cf;
lock (this)
{
if (ChannelFactoryCache == null)
throw new ObjectDisposedException(GetType().Name);
if (ChannelFactoryCache.ContainsKey(tot))
try
{
cf = ((ChannelFactory)ChannelFactoryCache[tot]);
if (cf.State != CommunicationState.Opened)
throw new Exception();
return cf.CreateChannel();
}
catch
{
ChannelFactoryCache[tot].Abort();
ChannelFactoryCache.Remove(tot);
}
cf = CreateChannel(instanceName);
ChannelFactoryCache[tot] = cf;
}
return cf.CreateChannel();
}
///
/// Directly creates a for without caching. This should be eventually closed by the caller
///
/// The component of the channel to be created
/// The instance to connect to, if any
/// The correct
ChannelFactory CreateChannel(string instanceName)
{
var accessPath = instanceName == null ? Definitions.MasterInterfaceName : String.Format("{0}/{1}", Definitions.InstanceInterfaceName, instanceName);
if (!IsRemoteConnection)
return CreateLocalChannel(instanceName, accessPath);
return CreateRemoteChannel(instanceName, accessPath);
}
//NOTE: This needs to be kept seperate from CreateRemoteChannel because not all our client implementations have NetNamedPipeBinding and attempting to call this function on those platforms will throw an exception
///
/// Directly creates a for on a local connection without caching. This should be eventually closed by the caller
///
/// The component of the channel to be created
/// The instance to connect to, if any
/// The URL of the interface to connect to
/// The correct
ChannelFactory CreateLocalChannel(string instanceName, string accessPath)
{
var interfaceName = typeof(T).Name;
var res2 = new ChannelFactory(
new NetNamedPipeBinding { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = Definitions.TransferLimitLocal }, new EndpointAddress(String.Format("net.pipe://localhost/{0}/{1}", accessPath, interfaceName))); //10 megs
res2.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
return res2;
}
///
/// Directly creates a for on a remote connection without caching. This should be eventually closed by the caller
///
/// The component of the channel to be created
/// The instance to connect to, if any
/// The URL of the interface to connect to
/// The correct
ChannelFactory CreateRemoteChannel(string instanceName, string accessPath)
{
//tls memes
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
//okay we're going over
var binding = new BasicHttpsBinding()
{
SendTimeout = new TimeSpan(0, 0, 40),
MaxReceivedMessageSize = Definitions.TransferLimitRemote
};
var interfaceName = typeof(T).Name;
var requireAuth = interfaceName != typeof(ITGConnectivity).Name;
var url = String.Format("https://{0}:{1}/{2}/{3}", LoginInfo.IP, LoginInfo.Port, accessPath, interfaceName);
var address = new EndpointAddress(url);
var res = new ChannelFactory(binding, address);
if (requireAuth)
{
var applicator = new AuthenticationHeaderApplicator(LoginInfo);
var t = Type.GetType("Mono.Runtime");
KeyedCollection behaviours;
if (t != null)
{
var prop = res.Endpoint.GetType()
.GetTypeInfo()
.GetDeclaredProperty("Behaviors");
behaviours = (KeyedCollection)prop
.GetValue(res.Endpoint);
}
else
behaviours = res.Endpoint.EndpointBehaviors;
behaviours.Add(applicator);
}
return res;
}
///
public ConnectivityLevel ConnectionStatus()
{
return ConnectionStatus(out string unused);
}
///
public ConnectivityLevel ConnectionStatus(out string error)
{
try
{
GetComponent(null).VerifyConnection();
}
catch (Exception e)
{
error = e.ToString();
return ConnectivityLevel.None;
}
try
{
GetComponent(null).Version();
}
catch(Exception e)
{
error = e.ToString();
return ConnectivityLevel.Connected;
}
try
{
GetComponent(null).Version();
error = null;
return ConnectivityLevel.Administrator;
}
catch(Exception e)
{
error = e.ToString();
return ConnectivityLevel.Authenticated;
}
}
}
}