using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Google.Protobuf; using Tango.BL.Entities; using Tango.Integration.Operation; using Tango.PMR; using Tango.PMR.Common; using Tango.Transport; using Tango.Transport.Transporters; using Tango.PMR.Integration; using Tango.Transport.Discovery; using Tango.Transport.Servers; using Tango.Transport.Adapters; using Tango.PMR.Connection; using Tango.PMR.Diagnostics; using Tango.PMR.Debugging; using System.Security.Authentication; using Tango.Settings; using Tango.Core.ExtensionMethods; using Tango.PMR.MachineStatus; namespace Tango.Integration.ExternalBridge { public class ExternalBridgeService : BasicTransporter, IExternalBridgeService { private UdpDiscoveryService _discoveryService; private TcpServer _tcpServer; private bool _send_app_logs; private String _app_logs_token; private Dictionary> _messageHandlers; private int _discovery_port = 8888; //Will be overridden by settings in constructor.. private int _external_bridge_port = 1984; //Will be overridden by settings in constructor.. private String _eventsNotificationsToken; private String _machineStatusUpdateToken; private String _diagnosticsNotificationToken; private String _debugLogsNotificationToken; private bool _resend_diagnostics_and_debug; #region Events /// /// Occurs when a new client is waiting for authentication. /// public event EventHandler ConnectionRequest; /// /// Occurs when the last client has been disconnected. /// public event EventHandler ClientDisconnected; /// /// Occurs when the service has received a new color profile request. /// public event EventHandler ColorProfileRequest; #endregion #region Properties private IMachineOperator _machineOperator; /// /// Gets or sets the machine operator. /// public IMachineOperator MachineOperator { get { return _machineOperator; } set { _machineOperator = value; OnMachineOperatorChanged(); } } private Machine _machine; /// /// Gets or sets the machine. /// public Machine Machine { get { return _machine; } set { _machine = value; OnMachineChanged(); } } /// /// Gets a value indicating whether this instance is started. /// public bool IsStarted { get; private set; } private bool _enabled; /// /// Gets or sets a value indicating whether this is enabled. /// public bool Enabled { get { return _enabled; } set { _enabled = value; if (_enabled) { Start(); } else { Stop(); } RaisePropertyChangedAuto(); } } private bool _isInSession; /// /// Gets a value indicating whether a remote client is authenticated and in session. /// public bool IsInSession { get { return _isInSession; } private set { _isInSession = value; RaisePropertyChangedAuto(); } } /// /// Gets the current session login intent. /// public ExternalBridgeLoginIntent SessionIntent { get; private set; } #endregion #region Constructors /// /// Initializes a new instance of the class. /// public ExternalBridgeService() { var settings = SettingsManager.Default.GetOrCreate(); _discovery_port = settings.ExternalBridgeServiceDiscoveryPort; _external_bridge_port = settings.ExternalBridgeServicePort; _messageHandlers = new Dictionary>(); RegisterMessageHandlers(); _tcpServer = new TcpServer(_external_bridge_port); _tcpServer.ClientConnected += _tcpServer_ClientConnected; LogManager.NewLog += LogManager_NewLog; KeepAliveTimeout = TimeSpan.FromSeconds(5); KeepAliveRetries = 2; } /// /// Initializes a new instance of the class. /// /// The machine operator. /// The machine. public ExternalBridgeService(IMachineOperator machineOperator, Machine machine) : this() { Machine = machine; MachineOperator = machineOperator; } #endregion #region Properties Changes /// /// Called when the machine has been changed /// protected virtual void OnMachineChanged() { if (_discoveryService != null && _discoveryService.IsStarted) { _discoveryService.Stop(); } _discoveryService = new UdpDiscoveryService(_discovery_port, new ExternalBridgeUdpDiscoveryPacket() { SerialNumber = Machine.SerialNumber, }); _discoveryService.BeforeBroadcasting -= _discoverySevice_BeforeBroadcasting; _discoveryService.BeforeBroadcasting += _discoverySevice_BeforeBroadcasting; } private void _discoverySevice_BeforeBroadcasting(object sender, ExternalBridgeUdpDiscoveryPacket e) { e.Time = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"); } /// /// Called when the machine operator has been changed /// protected virtual void OnMachineOperatorChanged() { if (MachineOperator != null) { MachineOperator.StateChanged -= MachineOperator_StateChanged; MachineOperator.StateChanged += MachineOperator_StateChanged; MachineOperator.StatusChanged -= MachineOperator_StatusChanged; MachineOperator.StatusChanged += MachineOperator_StatusChanged; MachineOperator.PendingResponseReceived -= MachineOperator_PendingResponseReceived; MachineOperator.PendingResponseReceived += MachineOperator_PendingResponseReceived; } } #endregion #region Event Handlers /// /// Handles the ClientConnected event of the _tcpServer control. /// /// The source of the event. /// The instance containing the event data. private async void _tcpServer_ClientConnected(object sender, ClientConnectedEventArgs e) { if (!IsInSession) { UseKeepAlive = false; LogManager.Log("External bridge client connected from: " + e.Socket.GetIPAddress()); Adapter = new TcpTransportAdapter(e.Socket); await Connect(); } else { e.Socket.Dispose(); } } /// /// Machines the operator state changed. /// /// The sender. /// The e. private void MachineOperator_StateChanged(object sender, TransportComponentState e) { //Do nothing right now. if (e != TransportComponentState.Connected) { _resend_diagnostics_and_debug = true; } } private async void MachineOperator_StatusChanged(object sender, MachineStatuses e) { if (e == MachineStatuses.ReadyToDye && _machineOperator.State == TransportComponentState.Connected) { if (IsInSession && _resend_diagnostics_and_debug) { _resend_diagnostics_and_debug = false; if (_diagnosticsNotificationToken != null) { var msg = MessageFactory.CreateTangoMessage(_diagnosticsNotificationToken); _machineOperator.SendContinuousRequest(msg); } if (_debugLogsNotificationToken != null) { var msg = MessageFactory.CreateTangoMessage(_debugLogsNotificationToken); _machineOperator.SendContinuousRequest(msg); } } } if (IsInSession) { try { UpdateStatus s = (UpdateStatus)e; await SendRequest(new UpdateStatusRequest() { Status = s, }); } catch (Exception ex) { LogManager.Log(ex, "Error updating status of external bridge service client."); } } } private void LogManager_NewLog(object sender, Tango.Logging.LogItemBase e) { if (State == TransportComponentState.Connected && _send_app_logs) { try { SendResponse(new StartApplicationLogsResponse() { LogItem = ByteString.CopyFrom(e.Serialize()) }, _app_logs_token); } catch { } } } private void MachineOperator_PendingResponseReceived(object sender, MessageContainer container) { OnOperatorResponseReceived(container); } #endregion #region Public Methods /// /// Starts this instance. /// public void Start() { if (!IsStarted) { _tcpServer.Start(); _discoveryService.Start(); IsStarted = true; _enabled = true; RaisePropertyChanged(nameof(Enabled)); } } /// /// Stops this instance. /// public async void Stop() { if (IsStarted) { _tcpServer.Stop(); _discoveryService.Stop(); IsStarted = false; IsInSession = false; _enabled = false; RaisePropertyChanged(nameof(Enabled)); try { await Disconnect(); } catch { } } } /// /// Disconnects the current remote client session. /// public async void DisconnectSession() { await Disconnect(); } #endregion #region Override Methods protected override void OnRequestReceived(MessageContainer container) { base.OnRequestReceived(container); try { if (Enabled) { if (container.Type == MessageType.ConnectRequest || container.Type == MessageType.DisconnectRequest) { //Do nothing ! } else { if (IsInSession) { if (container.Type == MessageType.ExternalBridgeLoginRequest) { SendErrorResponse(new AuthenticationException("Machine is already in session."), container.Token); return; } if (_messageHandlers.ContainsKey(container.Type)) { try { try { _messageHandlers[container.Type](container); } catch (Exception ex) { SendErrorResponse(ex, container.Token); } } catch (Exception ex) { if (ex is ResponseErrorException) { SendResponse((ex as ResponseErrorException).Container); } else { SendErrorResponse(ex, container.Token); } } } else { OnAnyRequest(container); } } else { if (container.Type == MessageType.ExternalBridgeLoginRequest) { OnExternalBridgeLoginRequest(container); } else if (container.Type == MessageType.ColorProfileRequest) { OnColorProfileRequest(container); } } } } } catch (Exception ex) { LogManager.Log(ex, String.Format("An error occurred while processing a request message '{0}' from the remote host.", container.Type)); } } public async override Task Disconnect() { _send_app_logs = false; try { if (IsInSession) { await SendRequest(new ExternalBridgeLogoutRequest(), TimeSpan.FromSeconds(0.5)); } } catch (Exception ex) { LogManager.Log(ex, "Error sending an external bridge log out request."); } finally { ClearQueues(); } OnClientDisconnected(); } protected override void OnFailed(Exception ex) { if (ex is KeepAliveException) { LogManager.Log("External bridge client has failed to provide a keep alive response. Disconnecting session..."); } base.OnFailed(ex); } #endregion #region Virtual Methods protected async virtual void OnClientDisconnected() { _eventsNotificationsToken = null; _machineStatusUpdateToken = null; _diagnosticsNotificationToken = null; _debugLogsNotificationToken = null; IsInSession = false; _send_app_logs = false; if (MachineOperator.State == TransportComponentState.Connected) { try { await MachineOperator.SendRequest(new StopDiagnosticsRequest()); } catch (Exception ex) { LogManager.Log(ex); } try { await MachineOperator.SendRequest(new StopDebugLogRequest()); } catch (Exception ex) { LogManager.Log(ex); } } ClientDisconnected?.Invoke(this, new EventArgs()); try { await base.Disconnect(); } catch (Exception ex) { LogManager.Log(ex); } LogManager.Log("External bridge client disconnected."); } protected virtual void OnOperatorResponseReceived(MessageContainer container) { try { if (IsInSession) { if (container.Type == MessageType.StartEventsNotificationResponse) { if (_eventsNotificationsToken != null) { container.Token = _eventsNotificationsToken; SendResponse(container); } } if (container.Type == MessageType.StartMachineStatusUpdateResponse) { if (_machineStatusUpdateToken != null) { container.Token = _machineStatusUpdateToken; SendResponse(container); } } else if (container.Type == MessageType.StartDiagnosticsResponse) { if (_diagnosticsNotificationToken != null) { container.Token = _diagnosticsNotificationToken; SendResponse(container); } } else if (container.Type == MessageType.StartDebugLogResponse) { if (_debugLogsNotificationToken != null) { container.Token = _debugLogsNotificationToken; SendResponse(container); } } } } catch { } } #endregion #region Message Handlers private void RegisterMessageHandlers() { _messageHandlers.Add(MessageType.ExternalBridgeLogoutRequest, OnExternalBridgeLogoutRequest); _messageHandlers.Add(MessageType.StartApplicationLogsRequest, OnStartApplicationLogsRequest); _messageHandlers.Add(MessageType.StopApplicationLogsRequest, OnStopApplicationLogsRequest); _messageHandlers.Add(MessageType.JobRequest, OnJobRequest); //Events _messageHandlers.Add(MessageType.StartEventsNotificationRequest, OnStartEventsNotificationRequest); _messageHandlers.Add(MessageType.StopEventsNotificationRequest, OnStopEventsNotificationRequest); //Machine Status _messageHandlers.Add(MessageType.StartMachineStatusUpdateRequest, OnStartMachineStatusUpdateRequest); _messageHandlers.Add(MessageType.StopMachineStatusUpdateRequest, OnStopMachineStatusUpdateRequest); //Diagnostics _messageHandlers.Add(MessageType.StartDiagnosticsRequest, OnStartDiagnosticsRequest); //Debug Logs _messageHandlers.Add(MessageType.StartDebugLogRequest, OnStartDebugLogRequest); } protected virtual async void OnAnyRequest(MessageContainer container) { if (SessionIntent == ExternalBridgeLoginIntent.ColorProfile) { await SendErrorResponse(new AuthenticationException("The specified intent does not grant the specified action."), container.Token); return; } if (!container.Continuous) { try { var response = await MachineOperator.SendRequest(container); await SendResponse(response); } //catch (TimeoutException) //{ //} catch (Exception ex) { await SendErrorResponse(ex, container.Token); } } else { try { MachineOperator.SendContinuousRequest(container).Subscribe((response) => { if (Enabled && IsInSession) { SendResponse(response); } }, (ex) => { if (Enabled) { if (ex is ResponseErrorException) { SendResponse((ex as ResponseErrorException).Container); } } }); } catch (Exception ex) { await SendErrorResponse(ex, container.Token); } } } protected async virtual void OnExternalBridgeLoginRequest(MessageContainer container) { var request = MessageFactory.ParseTangoMessageFromContainer(container); LogManager.Log($"External bridge login attempt:\nIntent: {request.Message.Intent}\nMessage:\n{request.Message.ToJsonString()}"); if (MachineOperator.Status != MachineStatuses.Printing || request.Message.Intent != ExternalBridgeLoginIntent.FullControl) { ExternalBridgeClientConnectedEventArgs args = new ExternalBridgeClientConnectedEventArgs(); args.Request = request; args.IpAddress = Adapter.Address; ConnectionRequest?.Invoke(this, args); var response = new ExternalBridgeLoginResponse(); response.Authenticated = args.Confirmed; response.SerialNumber = Machine.SerialNumber; response.DeviceInformation = MachineOperator.DeviceInformation; IsInSession = args.Confirmed; if (IsInSession) { LogManager.Log("External bridge client has logged-in successfully."); UseKeepAlive = true; MachineOperator.EnableDiagnostics = false; MachineOperator.EnableEmbeddedDebugging = false; SessionIntent = request.Message.Intent; } else { LogManager.Log("External bridge client login failed, invalid password."); } await SendResponse(response, container.Token, null, null, IsInSession ? null : "Invalid password or intent."); } else { LogManager.Log($"External bridge client login failed because the machine is currently printing and '{ExternalBridgeLoginIntent.FullControl}' intent was requested."); await SendResponse(new ExternalBridgeLoginResponse(), container.Token, false, ErrorCode.GeneralError, $"Machine connection with '{ExternalBridgeLoginIntent.FullControl}' intent is not permitted while printing."); } } protected async virtual void OnExternalBridgeLogoutRequest(MessageContainer container) { try { await SendResponse(new ExternalBridgeLogoutResponse(), container.Token); } catch (Exception ex) { LogManager.Log(ex); } finally { ClearQueues(); } OnClientDisconnected(); } protected virtual void OnStartApplicationLogsRequest(MessageContainer container) { if (SessionIntent == ExternalBridgeLoginIntent.Diagnostics || SessionIntent == ExternalBridgeLoginIntent.FullControl) { _app_logs_token = container.Token; _send_app_logs = true; SendResponse(new StartApplicationLogsResponse(), container.Token); } else { throw new AuthenticationException("The specified intent does not grant the specified action."); } } protected virtual void OnStopApplicationLogsRequest(MessageContainer container) { if (SessionIntent == ExternalBridgeLoginIntent.Diagnostics || SessionIntent == ExternalBridgeLoginIntent.FullControl) { _send_app_logs = false; SendResponse(new StopApplicationLogsResponse() { }, container.Token); SendResponse(new StartApplicationLogsResponse() { }, _app_logs_token, true); } else { throw new AuthenticationException("The specified intent does not grant the specified action."); } } protected virtual void OnJobRequest(MessageContainer container) { if (SessionIntent != ExternalBridgeLoginIntent.FullControl) { throw new InvalidOperationException($"Job execution is disabled while session intent is '{SessionIntent}'."); } if (MachineOperator.Status != MachineStatuses.ReadyToDye) { throw new InvalidOperationException($"Could not execute job while machine operator status is '{MachineOperator.Status}'."); } else { OnAnyRequest(container); } } protected virtual void OnStartEventsNotificationRequest(MessageContainer container) { _eventsNotificationsToken = container.Token; SendResponse(new StartEventsNotificationResponse(), container.Token); } protected virtual void OnStopEventsNotificationRequest(MessageContainer container) { if (_eventsNotificationsToken != null) { SendResponse(new StartEventsNotificationResponse(), _eventsNotificationsToken, true); _eventsNotificationsToken = null; SendResponse(new StopEventsNotificationResponse(), container.Token); } } protected virtual void OnStartMachineStatusUpdateRequest(MessageContainer container) { _machineStatusUpdateToken = container.Token; SendResponse(new StartMachineStatusUpdateResponse(), container.Token); } protected virtual void OnStopMachineStatusUpdateRequest(MessageContainer container) { if (_machineStatusUpdateToken != null) { SendResponse(new StartMachineStatusUpdateResponse(), _machineStatusUpdateToken, true); _machineStatusUpdateToken = null; SendResponse(new StopMachineStatusUpdateResponse(), container.Token); } } protected virtual void OnStartDiagnosticsRequest(MessageContainer container) { if (_diagnosticsNotificationToken == null) { _diagnosticsNotificationToken = container.Token; _machineOperator.SendContinuousRequest(container); } } protected virtual void OnStartDebugLogRequest(MessageContainer container) { if (_debugLogsNotificationToken == null) { _debugLogsNotificationToken = container.Token; _machineOperator.SendContinuousRequest(container); } } private void OnColorProfileRequest(MessageContainer container) { var request = MessageFactory.ParseTangoMessageFromContainer(container); ColorProfileRequestEventArgs e = new ColorProfileRequestEventArgs(request, async () => { //Approved. await SendResponse(new ColorProfileResponse() { Approved = true }, container.Token); await Task.Delay(500); await base.Disconnect(); }, async () => { //Declined. await SendResponse(new ColorProfileResponse(), container.Token); await Task.Delay(500); await base.Disconnect(); }); ColorProfileRequest?.Invoke(this, e); } #endregion } }