using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Security.Authentication; using System.Text; using System.Threading.Tasks; using Tango.BL; using Tango.BL.Entities; using Tango.Integration.Operation; using Tango.Logging; using Tango.PMR; using Tango.PMR.Common; using Tango.PMR.Connection; using Tango.PMR.Integration; using Tango.PMR.MachineStatus; using Tango.Settings; using Tango.Transport; using Tango.Transport.Adapters; using Tango.Transport.Transporters; namespace Tango.Integration.ExternalBridge { /// /// Represents a secure external bridge TCP client. /// /// /// public class ExternalBridgeTcpClient : MachineOperator, IExternalBridgeSecureClient { private bool _logs_sent; public event EventHandler ApplicationLogAvailable; #region Properties private String _serialNumber; /// /// Gets the machine serial number. /// public String SerialNumber { get { return _serialNumber; } set { _serialNumber = value; RaisePropertyChangedAuto(); } } private String _ipAddress; /// /// Gets or sets the machine IP address. /// public String IPAddress { get { return _ipAddress; } set { _ipAddress = value; RaisePropertyChangedAuto(); } } private bool _enableApplicationLogs; /// /// Gets or sets a value indicating whether to enable receiving application logs. /// public bool EnableApplicationLogs { get { return _enableApplicationLogs; } set { _enableApplicationLogs = value; RaisePropertyChangedAuto(); OnEnableApplicationLogsChanged(value); } } public bool InjectApplicationLogsToDefaultLogManager { get; set; } = true; /// /// Gets a value indicating whether this client requires authentication. /// public bool RequiresAuthentication => true; /// /// Gets or sets the login request message when using . /// public ExternalBridgeLoginRequest LoginRequest { get; set; } /// /// Gets or sets the configure protocol request message when using . /// public ConfigureProtocolRequest ConfigureProtocolRequest { get; set; } /// /// Gets or sets a value indicating to apply the even if there is an error with the request. /// public bool ForceProtocolConfiguration { get; set; } private ApplicationInformation _applicationInformation; /// /// Gets or sets the remote application information (PPC). /// public ApplicationInformation ApplicationInformation { get { return _applicationInformation; } protected set { _applicationInformation = value; RaisePropertyChangedAuto(); } } #endregion /// /// Connects to a remote external bridge service using the specified . /// /// The login. /// /// The machine password is invalid. public override Task Connect() { if (LoginRequest == null) { throw new InvalidOperationException("No LoginRequest was not specified."); } return Connect(LoginRequest, ConfigureProtocolRequest); } /// /// Connects to a remote external bridge service using the specified login. /// /// The login request. /// Optional protocol configuration. /// /// public virtual async Task Connect(ExternalBridgeLoginRequest login, ConfigureProtocolRequest protocol = null) { if (State != TransportComponentState.Connected) { try { Adapter.EnableCompression = false; GenericProtocol = GenericMessageProtocol.Json; await Adapter.Connect(); State = TransportComponentState.Connected; StartThreads(); LogManager.Log($"{ComponentName}: External Bridge TCP Client Connected..."); TimeSpan? timeout = null; if (login.RequireSafetyLevelOperations) { timeout = TimeSpan.FromSeconds(35); } var response = await SendRequest(login, new TransportRequestConfig() { ShouldLog = true, Timeout = timeout }); if (protocol != null) { try { var configureResponse = await SendRequest(protocol, new TransportRequestConfig() { ShouldLog = true }); if (configureResponse.Message.Confirmed) { await Task.Delay(500); Adapter.EnableCompression = protocol.EnableCompression; GenericProtocol = protocol.GenericProtocol; } } catch (Exception ex) { if (ForceProtocolConfiguration) { LogManager.Log(ex, $"{ComponentName}: Could not configure remote machine protocol. Could be an old PPC version. (forcing protocol configuration)"); Adapter.EnableCompression = protocol.EnableCompression; GenericProtocol = protocol.GenericProtocol; } else { LogManager.Log(ex, $"{ComponentName}: Could not configure remote machine protocol. Could be an old PPC version."); } } } ApplicationInformation = response.Message.ApplicationInformation; SessionLogger.CreateSession(); DeviceInformation = response.Message.DeviceInformation; if (!response.Message.Authenticated) { await Adapter.Disconnect(); throw new AuthenticationException(response.Container.ErrorMessage); } } catch (Exception ex) { try { await Adapter.Disconnect(); } catch { } throw ex; } ApplyContinuousChannelsConfiguration(); } } protected virtual void ApplyContinuousChannelsConfiguration() { OnEnableDiagnosticsChanged(EnableDiagnostics); OnEnableEmbeddedDebuggingChanged(EnableEmbeddedDebugging); OnEnableEventsNotification(EnableEventsNotification); OnEnableApplicationLogsChanged(EnableApplicationLogs); OnEnableMachineStatusUpdatesChanged(EnableMachineStatusUpdates); OnEnableInkFillingStatus(EnableInkFillingStatus); //TODO: Uncomment this only when Machine Studio enables automatic thread loading (ExternalBridgeTCPClient). //OnEnableAutomaticThreadLoadingChanged(EnableAutomaticThreadLoading); } protected async void OnEnableApplicationLogsChanged(bool value) { if (value && State == TransportComponentState.Connected && !_logs_sent) { var request = new StartApplicationLogsRequest(); bool responseLogged = false; _logs_sent = true; SendContinuousRequest(request, new TransportContinuousRequestConfig() { ShouldLog = true }).ObserveOn(new NewThreadScheduler()) .Subscribe ( (response) => { if (!responseLogged) { responseLogged = true; } OnApplicationLogAvailable(response); }, (ex) => { _logs_sent = false; }, () => { _logs_sent = false; }); } else if (_logs_sent) { _logs_sent = false; if (State == TransportComponentState.Connected) { var req = new StopApplicationLogsRequest(); try { var res = await SendRequest(req, new TransportRequestConfig() { ShouldLog = true }); } catch { } } } } private void OnApplicationLogAvailable(TangoMessage response) { try { if (response.Message.LogItem.Count() > 0) { LogItemBase log = LogItemBase.Deserialize(response.Message.LogItem.ToArray()); log.LogObject = "External Bridge"; if (InjectApplicationLogsToDefaultLogManager) { LogManager.Log(log); } ApplicationLogAvailable?.Invoke(this, log); } } catch (Exception ex) { LogManager.Log(ex, "Error deserializing incoming application log item!"); } } public async override Task Disconnect() { if (State == TransportComponentState.Connected) { ExternalBridgeLogoutRequest request = new ExternalBridgeLogoutRequest(); try { var response = await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } catch { } Status = MachineStatuses.Standby; } State = TransportComponentState.Disconnected; NotifyContinuousRequestMessagesDisconnection(); SessionLogger.EndSession(); if (Adapter != null) { await Adapter.Disconnect(); } LogManager.Log($"{ComponentName} disconnected."); } internal ExternalBridgeTcpClient() { } /// /// Initializes a new instance of the class. /// /// The machine serial number. /// The machine IP address. public ExternalBridgeTcpClient(String serialNumber, String ipAddress) { ComponentName = $"External Bridge TCP Client {_component_counter++}"; SerialNumber = serialNumber; if (ObservablesStaticCollections.Instance.IsInitialized) { Machine = ObservablesStaticCollections.Instance.Machines.SingleOrDefault(x => x.SerialNumber == serialNumber); } IPAddress = ipAddress; KeepAliveTimeout = TimeSpan.FromSeconds(5); KeepAliveRetries = 2; UseKeepAlive = false; EnableDiagnostics = true; Adapter = new TcpTransportAdapter(IPAddress, SettingsManager.Default.GetOrCreate().ExternalBridgeServicePort); } public ExternalBridgeTcpClient(Machine machine, String ipAddress) { ComponentName = $"External Bridge TCP Client {_component_counter++}"; Machine = machine; SerialNumber = Machine.SerialNumber; IPAddress = ipAddress; KeepAliveTimeout = TimeSpan.FromSeconds(5); KeepAliveRetries = 2; UseKeepAlive = false; EnableDiagnostics = true; Adapter = new TcpTransportAdapter(IPAddress, SettingsManager.Default.GetOrCreate().ExternalBridgeServicePort); } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() { return SerialNumber; } /// /// Called when a new request has been received. /// /// The request. protected async override void OnRequestReceived(RequestReceivedEventArgs e) { base.OnRequestReceived(e); var container = e.Container; if (container.Type == MessageType.ExternalBridgeLogoutRequest) { try { await SendResponse(new ExternalBridgeLogoutResponse(), container.Token); } catch { } await Task.Delay(2000); try { State = TransportComponentState.Disconnected; if (Adapter != null) { await Adapter.Disconnect(); } LogManager.Log("External Bridge TCP client disconnected by the remote host."); } catch { } SessionLogger.EndSession(); SessionClosed?.Invoke(this, new EventArgs()); NotifyContinuousRequestMessagesDisconnection(); } } protected override void OnMachineStateChanged(MachineState state) { //Do Nothing... } /// /// Occurs when the remote host has closed the session. /// public event EventHandler SessionClosed; /// /// Gets the database machine associated with this client. /// public Machine Machine { get; protected set; } /// /// Sets the database machine. /// /// The machine. public void SetMachine(Machine machine) { Machine = machine; } } }