From cc364a75d6800bbed09c60db9f95805c7a280c8b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 12 Nov 2017 20:42:04 -0500 Subject: [PATCH] Renames Interface to ServerInterface --- TGCommandLine/Program.cs | 17 +- TGControlPanel/InstanceSelector.cs | 2 +- TGControlPanel/Login.cs | 4 +- TGControlPanel/Program.cs | 2 +- TGDreamDaemonBridge/DreamDaemonBridge.cs | 2 +- TGInstallerWrapper/Main.cs | 2 +- TGServerService/Service.cs | 12 +- .../{Interface.cs => ServerInterface.cs} | 1000 ++++++++--------- TGServiceInterface/TGServiceInterface.csproj | 160 +-- .../TestHelpers.cs | 0 .../TestInterface.cs | 12 +- TGServiceTests/TGServiceTests.csproj | 230 ++-- 12 files changed, 721 insertions(+), 722 deletions(-) rename TGServiceInterface/{Interface.cs => ServerInterface.cs} (92%) rename TGServiceTests/{Interface => ServiceInterface}/TestHelpers.cs (100%) rename TGServiceTests/{Interface => ServiceInterface}/TestInterface.cs (89%) diff --git a/TGCommandLine/Program.cs b/TGCommandLine/Program.cs index ef15f54ed5..c1e7c18465 100644 --- a/TGCommandLine/Program.cs +++ b/TGCommandLine/Program.cs @@ -6,7 +6,6 @@ using TGServiceInterface.Components; namespace TGCommandLine { - class Program { static bool interactive = false, saidSrvVersion = false; @@ -53,7 +52,7 @@ namespace TGCommandLine } argsAsList.RemoveAt(I); argsAsList.RemoveAt(I); - ReplaceInterface(new Interface(address, port, username, password)); + ReplaceInterface(new ServerInterface(address, port, username, password)); break; } } @@ -176,7 +175,7 @@ namespace TGCommandLine static int Main(string[] args) { - ReplaceInterface(new Interface()); + ReplaceInterface(new ServerInterface()); Command.OutputProcVar.Value = Console.WriteLine; if (args.Length != 0) { @@ -194,13 +193,13 @@ namespace TGCommandLine { argsAsList.RemoveAt(I); --I; - Interface.SetBadCertificateHandler(_ => false); + ServerInterface.SetBadCertificateHandler(_ => false); } } return (int)RunCommandLine(argsAsList); } //interactive mode - Interface.SetBadCertificateHandler(BadCertificateInteractive); + ServerInterface.SetBadCertificateHandler(BadCertificateInteractive); Console.WriteLine("Type 'instance' to connect to a server instance"); Console.WriteLine("Type 'remote' to connect to a remote service"); while (true) @@ -231,17 +230,17 @@ namespace TGCommandLine var username = Console.ReadLine(); Console.Write("Enter password: "); var password = ReadLineSecure(); - ReplaceInterface(new Interface(address, port, username, password)); + ReplaceInterface(new ServerInterface(address, port, username, password)); var res = currentInterface.ConnectionStatus(out string error); if (!res.HasFlag(ConnectivityLevel.Connected)) { Console.WriteLine("Unable to connect: " + error); - ReplaceInterface(new Interface()); + ReplaceInterface(new ServerInterface()); } else if (!res.HasFlag(ConnectivityLevel.Authenticated)) { Console.WriteLine("Authentication error: Username/password/windows identity is not authorized! Returning to local mode..."); - ReplaceInterface(new Interface()); + ReplaceInterface(new ServerInterface()); } else { @@ -256,7 +255,7 @@ namespace TGCommandLine break; case "disconnect": SentVMMWarning = false; - ReplaceInterface(new Interface()); + ReplaceInterface(new ServerInterface()); Console.WriteLine("Switch to local mode"); break; case "quit": diff --git a/TGControlPanel/InstanceSelector.cs b/TGControlPanel/InstanceSelector.cs index 21e8a32624..ba46fc11f6 100644 --- a/TGControlPanel/InstanceSelector.cs +++ b/TGControlPanel/InstanceSelector.cs @@ -100,7 +100,7 @@ namespace TGControlPanel activeCP.BringToFront(); return; } - var InstanceAccessor = new Interface(masterInterface as Interface); + var InstanceAccessor = new ServerInterface(masterInterface as ServerInterface); try { ConnectivityLevel res = ConnectivityLevel.None; diff --git a/TGControlPanel/Login.cs b/TGControlPanel/Login.cs index 9a45e19e5a..882592d1cc 100644 --- a/TGControlPanel/Login.cs +++ b/TGControlPanel/Login.cs @@ -31,7 +31,7 @@ namespace TGControlPanel { IPTextBox.Text = IPTextBox.Text.Trim(); UsernameTextBox.Text = UsernameTextBox.Text.Trim(); - using (var I = new Interface(IPTextBox.Text, (ushort)PortSelector.Value, UsernameTextBox.Text, PasswordTextBox.Text)) + using (var I = new ServerInterface(IPTextBox.Text, (ushort)PortSelector.Value, UsernameTextBox.Text, PasswordTextBox.Text)) { var Config = Properties.Settings.Default; Config.RemoteIP = IPTextBox.Text; @@ -55,7 +55,7 @@ namespace TGControlPanel private void LocalLoginButton_Click(object sender, EventArgs e) { Properties.Settings.Default.RemoteDefault = false; - VerifyAndConnect(new Interface()); + VerifyAndConnect(new ServerInterface()); } void VerifyAndConnect(IServerInterface I) diff --git a/TGControlPanel/Program.cs b/TGControlPanel/Program.cs index b484dbc88f..4865d338df 100644 --- a/TGControlPanel/Program.cs +++ b/TGControlPanel/Program.cs @@ -20,7 +20,7 @@ namespace TGControlPanel } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Interface.SetBadCertificateHandler(BadCertificateHandler); + ServerInterface.SetBadCertificateHandler(BadCertificateHandler); var login = new Login(); login.Show(); Application.Run(); diff --git a/TGDreamDaemonBridge/DreamDaemonBridge.cs b/TGDreamDaemonBridge/DreamDaemonBridge.cs index 003e9b6b9b..07835d4232 100644 --- a/TGDreamDaemonBridge/DreamDaemonBridge.cs +++ b/TGDreamDaemonBridge/DreamDaemonBridge.cs @@ -27,7 +27,7 @@ namespace TGDreamDaemonBridge parsedArgs.AddRange(args); var instance = parsedArgs[0]; parsedArgs.RemoveAt(0); - using (var I = new Interface()) + using (var I = new ServerInterface()) if(I.ConnectToInstance(instance, true).HasFlag(ConnectivityLevel.Connected)) I.GetComponent().InteropMessage(String.Join(" ", parsedArgs)); } diff --git a/TGInstallerWrapper/Main.cs b/TGInstallerWrapper/Main.cs index 81eed484af..df1b275a83 100644 --- a/TGInstallerWrapper/Main.cs +++ b/TGInstallerWrapper/Main.cs @@ -69,7 +69,7 @@ namespace TGInstallerWrapper } void CheckForExistingVersion() { - Interface = new Interface(); + Interface = new ServerInterface(); var verifiedConnection = Interface.ConnectionStatus().HasFlag(ConnectivityLevel.Administrator); try { diff --git a/TGServerService/Service.cs b/TGServerService/Service.cs index 0486e7c5de..75f0f23cac 100644 --- a/TGServerService/Service.cs +++ b/TGServerService/Service.cs @@ -234,8 +234,8 @@ namespace TGServerService /// void SetupService() { - serviceHost = CreateHost(this, Interface.MasterInterfaceName); - foreach (var I in Interface.ValidServiceInterfaces) + serviceHost = CreateHost(this, ServerInterface.MasterInterfaceName); + foreach (var I in ServerInterface.ValidServiceInterfaces) AddEndpoint(serviceHost, I); serviceHost.Authorization.ServiceAuthorizationManager = new RootAuthorizationManager(); //only admins can diddle us } @@ -348,10 +348,10 @@ namespace TGServerService return null; } - var host = CreateHost(instance, String.Format("{0}/{1}", Interface.InstanceInterfaceName, instanceName)); + var host = CreateHost(instance, String.Format("{0}/{1}", ServerInterface.InstanceInterfaceName, instanceName)); hosts.Add(instanceName, host); - foreach (var J in Interface.ValidInstanceInterfaces) + foreach (var J in ServerInterface.ValidInstanceInterfaces) AddEndpoint(host, J); host.Authorization.ServiceAuthorizationManager = instance; @@ -366,11 +366,11 @@ namespace TGServerService void AddEndpoint(ServiceHost host, Type typetype) { var bindingName = typetype.Name; - host.AddServiceEndpoint(typetype, new NetNamedPipeBinding() { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = Interface.TransferLimitLocal }, bindingName); + host.AddServiceEndpoint(typetype, new NetNamedPipeBinding() { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = ServerInterface.TransferLimitLocal }, bindingName); var httpsBinding = new WSHttpBinding() { SendTimeout = new TimeSpan(0, 0, 40), - MaxReceivedMessageSize = Interface.TransferLimitRemote + MaxReceivedMessageSize = ServerInterface.TransferLimitRemote }; var requireAuth = typetype.Name != typeof(ITGConnectivity).Name; httpsBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None; diff --git a/TGServiceInterface/Interface.cs b/TGServiceInterface/ServerInterface.cs similarity index 92% rename from TGServiceInterface/Interface.cs rename to TGServiceInterface/ServerInterface.cs index 15795c4746..62608e7cf9 100644 --- a/TGServiceInterface/Interface.cs +++ b/TGServiceInterface/ServerInterface.cs @@ -1,500 +1,500 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Net.Security; -using System.Reflection; -using System.Security.Principal; -using System.ServiceModel; -using TGServiceInterface.Components; - -namespace TGServiceInterface -{ - /// - /// Main for communicating the - /// - public interface IServerInterface : IDisposable - { - /// - /// The name of the current instance in use. Defaults to - /// - string InstanceName { get; } - - /// - /// If this is set, we will try and connect to an HTTPS server running at this address - /// - string HTTPSURL { get; } - - /// - /// The port used to connect to the - /// - ushort HTTPSPort { get; } - - /// - /// Checks if the is setup for a remote connection - /// - bool IsRemoteConnection { get; } - - /// - /// Targets as the instance to use with . Closes all connections to any previous instance - /// - /// The name of the instance to connect to - /// If set to , skips the connectivity and authentication checks, sets , and returns - /// The apporopriate - ConnectivityLevel ConnectToInstance(string instanceName = null, bool skipChecks = false); - - /// - /// Returns if the interface being used to connect to a service does not have the same release version as the service - /// - /// An error message to display to the user should this function return - /// if the interface being used to connect to a service does not have the same release version as the service - bool VersionMismatch(out string errorMessage); - - /// - /// Returns the requested component for the instance . This does not guarantee a successful connection. - /// - /// The component to retrieve - /// The correct component - T GetComponent(); - - /// - /// Returns a root service component - /// - /// The component for the service - T GetServiceComponent(); - - /// - /// Used to test if the is avaiable on the target machine. Note that state can change at any time and any call into the may throw an exception because of communcation errors - /// - /// on successful connection, error message on failure - ConnectivityLevel ConnectionStatus(); - - /// - /// Used to test if the is avaiable on the target machine. Note that state can change at any time and any call into the may throw an exception because of communcation errors - /// - /// String of the error that prevented an elevated connectivity level - /// The apporopriate - ConnectivityLevel ConnectionStatus(out string error); - } - - /// - sealed public class Interface : IServerInterface - { - /// - /// List of s that can be used with - /// - public static readonly IList ValidServiceInterfaces = new List { typeof(ITGSService), typeof(ITGInstanceManager), typeof(ITGConnectivity), typeof(ITGLanding) }; - - /// - /// List of s that can be used with - /// - public static readonly IList ValidInstanceInterfaces = CollectComponents(); - - /// - /// The maximum message size to and from a local server - /// - public const long TransferLimitLocal = Int32.MaxValue; //2GB can't go higher - - /// - /// The maximum message size to and from a remote server - /// - public const long TransferLimitRemote = 10485760; //10 MB - - /// - /// Base name of communication URLs - /// - public const string MasterInterfaceName = "TGStationServerService"; - /// - /// Base name of instance URLs - /// - public const string InstanceInterfaceName = MasterInterfaceName + "/Instance"; - - /// - public string InstanceName { get; private set; } - - /// - /// If this is set, we will try and connect to an HTTPS server running at this address - /// - readonly string _HTTPSURL; - - /// - public string HTTPSURL { get { return _HTTPSURL; } } - - /// - /// The port used to connect to the - /// - readonly ushort _HTTPSPort; - - /// - public ushort HTTPSPort { get { return _HTTPSPort; } } - - /// - /// Username for remote operations - /// - readonly string HTTPSUsername; - - /// - /// Password for remote operations - /// - readonly string HTTPSPassword; - - /// - /// 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 = new Dictionary(); - - /// - /// Returns a of s that can be used with the service - /// - /// A of s that can be used with the service - static IList CollectComponents() - { - var ConnectivityComponent = typeof(ITGConnectivity); - //find all interfaces in this assembly in this namespace that have the service contract attribute - var query = from t in Assembly.GetExecutingAssembly().GetTypes() - where t.IsInterface - && t.Namespace == ConnectivityComponent.Namespace - && t.GetCustomAttribute(typeof(ServiceContractAttribute)) != null - && (t == ConnectivityComponent || !ValidServiceInterfaces.Contains(t)) - select t; - return query.ToList(); - } - - /// - /// 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 Interface() { } - - /// - /// Construct an for a remote connection - /// - /// The address of the remote server - /// The port the remote server runs on - /// Windows account username for the remote server - /// Windows account password for the remote server - public Interface(string address, ushort port, string username, string password) - { - _HTTPSURL = address; - _HTTPSPort = port; - HTTPSUsername = username; - HTTPSPassword = password; - } - - /// - /// Constructs an that connects to the same as some - /// - /// Another to copy settings from - public Interface(Interface other) : this(other.HTTPSURL, other.HTTPSPort, other.HTTPSUsername, other.HTTPSPassword) { } - - /// - public ConnectivityLevel ConnectToInstance(string instanceName = null, bool skipChecks = false) - { - if (instanceName == null) - instanceName = InstanceName; - if (!skipChecks && !ConnectionStatus().HasFlag(ConnectivityLevel.Connected)) - return ConnectivityLevel.None; - var prevInstance = InstanceName; - if (prevInstance != instanceName) - CloseAllChannels(false); - InstanceName = instanceName; - if (skipChecks) - return ConnectivityLevel.Connected; - try - { - GetComponent().VerifyConnection(); - } - catch - { - InstanceName = prevInstance; - return ConnectivityLevel.None; - } - try - { - GetComponent().ServerDirectory(); - } - catch - { - return ConnectivityLevel.Connected; - } - try - { - GetComponent().GetCurrentAuthorizedGroup(); - return ConnectivityLevel.Administrator; - } - catch - { - return ConnectivityLevel.Authenticated; - } - } - - /// - public bool IsRemoteConnection { get { return HTTPSURL != null; } } - - /// - /// Closes all s stored in and clears it - /// - /// If set to , doesn't clear the channels that are used by - void CloseAllChannels(bool includingRoot) - { - string[] RootThings = { typeof(ITGSService).Name, 'S' + typeof(ITGConnectivity).Name }; - lock (ChannelFactoryCache) - { - var toRemove = new List(); - foreach (var I in ChannelFactoryCache) - { - if (RootThings.Contains(I.Key)) - continue; - var cf = I.Value; - try - { - cf.Closed += ChannelFactory_Closed; - cf.Close(); - } - catch - { - cf.Abort(); - } - toRemove.Add(I.Key); - } - foreach (var I in toRemove) - ChannelFactoryCache.Remove(I); - } - } - - /// - public bool VersionMismatch(out string errorMessage) - { - var splits = GetServiceComponent().Version().Split(' '); - var theirs = new Version(splits[splits.Length - 1].Substring(1)); - var ours = new Version(FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).FileVersion); - if(theirs.Major != ours.Major || theirs.Minor != ours.Minor || theirs.Revision != ours.Revision) //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.", ours, theirs); - return true; - } - errorMessage = null; - return false; - } - - /// - /// Disposes a closed - /// - /// The channel factory that was closed - /// The event arguments - static void ChannelFactory_Closed(object sender, EventArgs e) - { - (sender as IDisposable).Dispose(); - } - - /// - public T GetComponent() - { - var ToT = typeof(T); - if (!ValidInstanceInterfaces.Contains(ToT)) - throw new Exception("Invalid type!"); - return GetComponentImpl(true); - } - - /// - /// 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 - /// If should be used to connect - /// The correct component - T GetComponentImpl(bool useInstanceName) - { - if (useInstanceName & InstanceName == null) - throw new Exception("Instance not selected!"); - var actualToT = typeof(T); - var tot = actualToT.Name; - if (actualToT == typeof(ITGConnectivity) && !useInstanceName) - tot = 'S' + tot; - ChannelFactory cf; - - lock (ChannelFactoryCache) - { - 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(useInstanceName ? InstanceName : null); - ChannelFactoryCache[tot] = cf; - } - return cf.CreateChannel(); - } - - /// - public T GetServiceComponent() - { - var ToT = typeof(T); - if (!ValidServiceInterfaces.Contains(ToT)) - throw new Exception("Invalid type!"); - return GetComponentImpl(false); - } - - /// - /// Directly creates a for without caching. This should be eventually closed by the caller - /// - /// The component of the channel to be created - /// The correct - /// Thrown if isn't a valid component - ChannelFactory CreateChannel(string instanceName) - { - var accessPath = instanceName == null ? MasterInterfaceName : String.Format("{0}/{1}", InstanceInterfaceName, instanceName); - - var InterfaceName = typeof(T).Name; - if (!IsRemoteConnection) - { - var res2 = new ChannelFactory( - new NetNamedPipeBinding { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = TransferLimitLocal }, new EndpointAddress(String.Format("net.pipe://localhost/{0}/{1}", accessPath, InterfaceName))); //10 megs - res2.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; - return res2; - } - - //okay we're going over - var binding = new WSHttpBinding() - { - SendTimeout = new TimeSpan(0, 0, 40), - MaxReceivedMessageSize = TransferLimitRemote - }; - var requireAuth = InterfaceName != typeof(ITGConnectivity).Name; - binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None; - binding.Security.Mode = requireAuth ? SecurityMode.TransportWithMessageCredential : SecurityMode.Transport; //do not require auth for a connectivity check - binding.Security.Message.ClientCredentialType = requireAuth ? MessageCredentialType.UserName : MessageCredentialType.None; - var address = new EndpointAddress(String.Format("https://{0}:{1}/{2}/{3}", HTTPSURL, HTTPSPort, accessPath, InterfaceName)); - var res = new ChannelFactory(binding, address); - if (requireAuth) - { - res.Credentials.UserName.UserName = HTTPSUsername; - res.Credentials.UserName.Password = HTTPSPassword; - res.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; - } - return res; - } - - /// - public ConnectivityLevel ConnectionStatus() - { - return ConnectionStatus(out string unused); - } - - /// - public ConnectivityLevel ConnectionStatus(out string error) - { - try - { - GetComponentImpl(false).VerifyConnection(); - } - catch (CommunicationException e) - { - error = e.ToString(); - return ConnectivityLevel.None; - } - try - { - GetServiceComponent().Version(); - } - catch(Exception e) - { - error = e.ToString(); - return ConnectivityLevel.Connected; - } - try - { - GetServiceComponent().Version(); - error = null; - return ConnectivityLevel.Administrator; - } - catch(Exception e) - { - error = e.ToString(); - return ConnectivityLevel.Authenticated; - } - } - - #region IDisposable Support - /// - /// To detect redundant calls - /// - private bool disposedValue = false; - - /// - /// Implements the pattern. Calls - /// - /// if was called manually, if it was from the finalizer - void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - CloseAllChannels(true); - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~Interface() { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - - /// - /// Implements the pattern - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Reflection; +using System.Security.Principal; +using System.ServiceModel; +using TGServiceInterface.Components; + +namespace TGServiceInterface +{ + /// + /// Main for communicating the + /// + public interface IServerInterface : IDisposable + { + /// + /// The name of the current instance in use. Defaults to + /// + string InstanceName { get; } + + /// + /// If this is set, we will try and connect to an HTTPS server running at this address + /// + string HTTPSURL { get; } + + /// + /// The port used to connect to the + /// + ushort HTTPSPort { get; } + + /// + /// Checks if the is setup for a remote connection + /// + bool IsRemoteConnection { get; } + + /// + /// Targets as the instance to use with . Closes all connections to any previous instance + /// + /// The name of the instance to connect to + /// If set to , skips the connectivity and authentication checks, sets , and returns + /// The apporopriate + ConnectivityLevel ConnectToInstance(string instanceName = null, bool skipChecks = false); + + /// + /// Returns if the interface being used to connect to a service does not have the same release version as the service + /// + /// An error message to display to the user should this function return + /// if the interface being used to connect to a service does not have the same release version as the service + bool VersionMismatch(out string errorMessage); + + /// + /// Returns the requested component for the instance . This does not guarantee a successful connection. + /// + /// The component to retrieve + /// The correct component + T GetComponent(); + + /// + /// Returns a root service component + /// + /// The component for the service + T GetServiceComponent(); + + /// + /// Used to test if the is avaiable on the target machine. Note that state can change at any time and any call into the may throw an exception because of communcation errors + /// + /// on successful connection, error message on failure + ConnectivityLevel ConnectionStatus(); + + /// + /// Used to test if the is avaiable on the target machine. Note that state can change at any time and any call into the may throw an exception because of communcation errors + /// + /// String of the error that prevented an elevated connectivity level + /// The apporopriate + ConnectivityLevel ConnectionStatus(out string error); + } + + /// + sealed public class ServerInterface : IServerInterface + { + /// + /// List of s that can be used with + /// + public static readonly IList ValidServiceInterfaces = new List { typeof(ITGSService), typeof(ITGInstanceManager), typeof(ITGConnectivity), typeof(ITGLanding) }; + + /// + /// List of s that can be used with + /// + public static readonly IList ValidInstanceInterfaces = CollectComponents(); + + /// + /// The maximum message size to and from a local server + /// + public const long TransferLimitLocal = Int32.MaxValue; //2GB can't go higher + + /// + /// The maximum message size to and from a remote server + /// + public const long TransferLimitRemote = 10485760; //10 MB + + /// + /// Base name of communication URLs + /// + public const string MasterInterfaceName = "TGStationServerService"; + /// + /// Base name of instance URLs + /// + public const string InstanceInterfaceName = MasterInterfaceName + "/Instance"; + + /// + public string InstanceName { get; private set; } + + /// + /// If this is set, we will try and connect to an HTTPS server running at this address + /// + readonly string _HTTPSURL; + + /// + public string HTTPSURL { get { return _HTTPSURL; } } + + /// + /// The port used to connect to the + /// + readonly ushort _HTTPSPort; + + /// + public ushort HTTPSPort { get { return _HTTPSPort; } } + + /// + /// Username for remote operations + /// + readonly string HTTPSUsername; + + /// + /// Password for remote operations + /// + readonly string HTTPSPassword; + + /// + /// 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 = new Dictionary(); + + /// + /// Returns a of s that can be used with the service + /// + /// A of s that can be used with the service + static IList CollectComponents() + { + var ConnectivityComponent = typeof(ITGConnectivity); + //find all interfaces in this assembly in this namespace that have the service contract attribute + var query = from t in Assembly.GetExecutingAssembly().GetTypes() + where t.IsInterface + && t.Namespace == ConnectivityComponent.Namespace + && t.GetCustomAttribute(typeof(ServiceContractAttribute)) != null + && (t == ConnectivityComponent || !ValidServiceInterfaces.Contains(t)) + select t; + return query.ToList(); + } + + /// + /// 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 ServerInterface() { } + + /// + /// Construct an for a remote connection + /// + /// The address of the remote server + /// The port the remote server runs on + /// Windows account username for the remote server + /// Windows account password for the remote server + public ServerInterface(string address, ushort port, string username, string password) + { + _HTTPSURL = address; + _HTTPSPort = port; + HTTPSUsername = username; + HTTPSPassword = password; + } + + /// + /// Constructs an that connects to the same as some + /// + /// Another to copy settings from + public ServerInterface(ServerInterface other) : this(other.HTTPSURL, other.HTTPSPort, other.HTTPSUsername, other.HTTPSPassword) { } + + /// + public ConnectivityLevel ConnectToInstance(string instanceName = null, bool skipChecks = false) + { + if (instanceName == null) + instanceName = InstanceName; + if (!skipChecks && !ConnectionStatus().HasFlag(ConnectivityLevel.Connected)) + return ConnectivityLevel.None; + var prevInstance = InstanceName; + if (prevInstance != instanceName) + CloseAllChannels(false); + InstanceName = instanceName; + if (skipChecks) + return ConnectivityLevel.Connected; + try + { + GetComponent().VerifyConnection(); + } + catch + { + InstanceName = prevInstance; + return ConnectivityLevel.None; + } + try + { + GetComponent().ServerDirectory(); + } + catch + { + return ConnectivityLevel.Connected; + } + try + { + GetComponent().GetCurrentAuthorizedGroup(); + return ConnectivityLevel.Administrator; + } + catch + { + return ConnectivityLevel.Authenticated; + } + } + + /// + public bool IsRemoteConnection { get { return HTTPSURL != null; } } + + /// + /// Closes all s stored in and clears it + /// + /// If set to , doesn't clear the channels that are used by + void CloseAllChannels(bool includingRoot) + { + string[] RootThings = { typeof(ITGSService).Name, 'S' + typeof(ITGConnectivity).Name }; + lock (ChannelFactoryCache) + { + var toRemove = new List(); + foreach (var I in ChannelFactoryCache) + { + if (RootThings.Contains(I.Key)) + continue; + var cf = I.Value; + try + { + cf.Closed += ChannelFactory_Closed; + cf.Close(); + } + catch + { + cf.Abort(); + } + toRemove.Add(I.Key); + } + foreach (var I in toRemove) + ChannelFactoryCache.Remove(I); + } + } + + /// + public bool VersionMismatch(out string errorMessage) + { + var splits = GetServiceComponent().Version().Split(' '); + var theirs = new Version(splits[splits.Length - 1].Substring(1)); + var ours = new Version(FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).FileVersion); + if(theirs.Major != ours.Major || theirs.Minor != ours.Minor || theirs.Revision != ours.Revision) //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.", ours, theirs); + return true; + } + errorMessage = null; + return false; + } + + /// + /// Disposes a closed + /// + /// The channel factory that was closed + /// The event arguments + static void ChannelFactory_Closed(object sender, EventArgs e) + { + (sender as IDisposable).Dispose(); + } + + /// + public T GetComponent() + { + var ToT = typeof(T); + if (!ValidInstanceInterfaces.Contains(ToT)) + throw new Exception("Invalid type!"); + return GetComponentImpl(true); + } + + /// + /// 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 + /// If should be used to connect + /// The correct component + T GetComponentImpl(bool useInstanceName) + { + if (useInstanceName & InstanceName == null) + throw new Exception("Instance not selected!"); + var actualToT = typeof(T); + var tot = actualToT.Name; + if (actualToT == typeof(ITGConnectivity) && !useInstanceName) + tot = 'S' + tot; + ChannelFactory cf; + + lock (ChannelFactoryCache) + { + 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(useInstanceName ? InstanceName : null); + ChannelFactoryCache[tot] = cf; + } + return cf.CreateChannel(); + } + + /// + public T GetServiceComponent() + { + var ToT = typeof(T); + if (!ValidServiceInterfaces.Contains(ToT)) + throw new Exception("Invalid type!"); + return GetComponentImpl(false); + } + + /// + /// Directly creates a for without caching. This should be eventually closed by the caller + /// + /// The component of the channel to be created + /// The correct + /// Thrown if isn't a valid component + ChannelFactory CreateChannel(string instanceName) + { + var accessPath = instanceName == null ? MasterInterfaceName : String.Format("{0}/{1}", InstanceInterfaceName, instanceName); + + var InterfaceName = typeof(T).Name; + if (!IsRemoteConnection) + { + var res2 = new ChannelFactory( + new NetNamedPipeBinding { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = TransferLimitLocal }, new EndpointAddress(String.Format("net.pipe://localhost/{0}/{1}", accessPath, InterfaceName))); //10 megs + res2.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; + return res2; + } + + //okay we're going over + var binding = new WSHttpBinding() + { + SendTimeout = new TimeSpan(0, 0, 40), + MaxReceivedMessageSize = TransferLimitRemote + }; + var requireAuth = InterfaceName != typeof(ITGConnectivity).Name; + binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None; + binding.Security.Mode = requireAuth ? SecurityMode.TransportWithMessageCredential : SecurityMode.Transport; //do not require auth for a connectivity check + binding.Security.Message.ClientCredentialType = requireAuth ? MessageCredentialType.UserName : MessageCredentialType.None; + var address = new EndpointAddress(String.Format("https://{0}:{1}/{2}/{3}", HTTPSURL, HTTPSPort, accessPath, InterfaceName)); + var res = new ChannelFactory(binding, address); + if (requireAuth) + { + res.Credentials.UserName.UserName = HTTPSUsername; + res.Credentials.UserName.Password = HTTPSPassword; + res.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; + } + return res; + } + + /// + public ConnectivityLevel ConnectionStatus() + { + return ConnectionStatus(out string unused); + } + + /// + public ConnectivityLevel ConnectionStatus(out string error) + { + try + { + GetComponentImpl(false).VerifyConnection(); + } + catch (CommunicationException e) + { + error = e.ToString(); + return ConnectivityLevel.None; + } + try + { + GetServiceComponent().Version(); + } + catch(Exception e) + { + error = e.ToString(); + return ConnectivityLevel.Connected; + } + try + { + GetServiceComponent().Version(); + error = null; + return ConnectivityLevel.Administrator; + } + catch(Exception e) + { + error = e.ToString(); + return ConnectivityLevel.Authenticated; + } + } + + #region IDisposable Support + /// + /// To detect redundant calls + /// + private bool disposedValue = false; + + /// + /// Implements the pattern. Calls + /// + /// if was called manually, if it was from the finalizer + void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + CloseAllChannels(true); + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~Interface() { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + /// + /// Implements the pattern + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/TGServiceInterface/TGServiceInterface.csproj b/TGServiceInterface/TGServiceInterface.csproj index 1c5769d1ba..c5a0a379a6 100644 --- a/TGServiceInterface/TGServiceInterface.csproj +++ b/TGServiceInterface/TGServiceInterface.csproj @@ -1,81 +1,81 @@ - - - - - Debug - AnyCPU - {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB} - Library - Properties - TGServiceInterface - TGServiceInterface - v4.5.2 - 512 - - - tgs.ico - - - true - bin\Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Release\ - TRACE - bin\x86\Release\TGServiceInterface.xml - true - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - false - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + AnyCPU + {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB} + Library + Properties + TGServiceInterface + TGServiceInterface + v4.5.2 + 512 + + + tgs.ico + + + true + bin\Debug\ + DEBUG;TRACE + full + AnyCPU + prompt + MinimumRecommendedRules.ruleset + + + bin\Release\ + TRACE + bin\x86\Release\TGServiceInterface.xml + true + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + false + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TGServiceTests/Interface/TestHelpers.cs b/TGServiceTests/ServiceInterface/TestHelpers.cs similarity index 100% rename from TGServiceTests/Interface/TestHelpers.cs rename to TGServiceTests/ServiceInterface/TestHelpers.cs diff --git a/TGServiceTests/Interface/TestInterface.cs b/TGServiceTests/ServiceInterface/TestInterface.cs similarity index 89% rename from TGServiceTests/Interface/TestInterface.cs rename to TGServiceTests/ServiceInterface/TestInterface.cs index ebb8ec3751..f8fa81420a 100644 --- a/TGServiceTests/Interface/TestInterface.cs +++ b/TGServiceTests/ServiceInterface/TestInterface.cs @@ -27,7 +27,7 @@ namespace TGServiceInterface.Tests Assert.IsFalse(String.IsNullOrWhiteSpace(message)); return true; }; - Interface.SetBadCertificateHandler(func); + ServerInterface.SetBadCertificateHandler(func); } /// @@ -37,7 +37,7 @@ namespace TGServiceInterface.Tests public void TestBadCertificateHandler() { var ran = false; - Interface.SetBadCertificateHandler(_ => + ServerInterface.SetBadCertificateHandler(_ => { ran = true; return true; @@ -50,9 +50,9 @@ namespace TGServiceInterface.Tests /// Creates a remote configured pointing at an invalid address /// /// The created - Interface CreateFakeRemoteInterface() + ServerInterface CreateFakeRemoteInterface() { - return new Interface("some.fake.url.420", 34752, "user", "password"); + return new ServerInterface("some.fake.url.420", 34752, "user", "password"); } /// @@ -61,7 +61,7 @@ namespace TGServiceInterface.Tests [TestMethod] public void TestLocalInstantiation() { - Assert.IsFalse(new Interface().IsRemoteConnection); + Assert.IsFalse(new ServerInterface().IsRemoteConnection); } /// @@ -77,7 +77,7 @@ namespace TGServiceInterface.Tests public void TestCopyRemoteInterface() { var first = CreateFakeRemoteInterface(); - var second = new Interface(first); + var second = new ServerInterface(first); Assert.AreEqual(first.HTTPSURL, second.HTTPSURL); Assert.AreEqual(first.HTTPSPort, second.HTTPSPort); Assert.IsTrue(second.IsRemoteConnection); diff --git a/TGServiceTests/TGServiceTests.csproj b/TGServiceTests/TGServiceTests.csproj index 870d624bb6..f61981f9ee 100644 --- a/TGServiceTests/TGServiceTests.csproj +++ b/TGServiceTests/TGServiceTests.csproj @@ -1,116 +1,116 @@ - - - - - Debug - AnyCPU - {FB693FFB-17E3-4E84-8CBF-6FFA9C8FD971} - Library - Properties - TGServiceTests - TGServiceTests - v4.6.1 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - false - - - - ..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll - - - ..\packages\MSTest.TestFramework.1.1.18\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\packages\MSTest.TestFramework.1.1.18\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - ..\packages\Moq.4.7.145\lib\net45\Moq.dll - - - - - - - - - - - - - - - - - - Component - - - - - - - - - - - - - - {89191f69-b18e-4b59-b72e-e12f9b6811a0} - TGCommandLine - - - {394e7643-6b8c-416f-ab18-95ac12648cdc} - TGControlPanel - - - {8956d4c3-bfb9-448e-bf5f-ee7e6f9996f9} - TGInstallerWrapper - - - {f32eda25-0855-411c-af5e-f0d042917e2d} - TGServerService - - - {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} - TGServiceInterface - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + + Debug + AnyCPU + {FB693FFB-17E3-4E84-8CBF-6FFA9C8FD971} + Library + Properties + TGServiceTests + TGServiceTests + v4.6.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + false + + + + ..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll + + + ..\packages\MSTest.TestFramework.1.1.18\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.1.1.18\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + ..\packages\Moq.4.7.145\lib\net45\Moq.dll + + + + + + + + + + + + + + + + + + Component + + + + + + + + + + + + + + {89191f69-b18e-4b59-b72e-e12f9b6811a0} + TGCommandLine + + + {394e7643-6b8c-416f-ab18-95ac12648cdc} + TGControlPanel + + + {8956d4c3-bfb9-448e-bf5f-ee7e6f9996f9} + TGInstallerWrapper + + + {f32eda25-0855-411c-af5e-f0d042917e2d} + TGServerService + + + {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} + TGServiceInterface + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file