using Google.Protobuf; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net.Sockets; using System.Security.Authentication; using System.Text; using System.Threading; using System.Threading.Tasks; using Tango.Core; using Tango.Core.ExtensionMethods; using Tango.Integration.Operation; using Tango.PMR; using Tango.PMR.Common; using Tango.PMR.Debugging; using Tango.PMR.Diagnostics; using Tango.PMR.IFS; using Tango.PMR.Integration; using Tango.PMR.MachineStatus; using Tango.PMR.Power; using Tango.Transport; using Tango.Transport.Adapters; using Tango.Transport.Transporters; namespace Tango.Integration.ExternalBridge { public class ExternalBridgeReceiver : BasicTransporter { /// /// A dictionary containing the last time a user name has made a request or response. /// This is used to bypass the safety operations confirmation. /// FullName, Contact Time /// public static Dictionary LastSafetyLevelContactsTimes { get; private set; } #region Message Handler private class MessageHandler { public Action Method { get; set; } public bool RequiresLogin { get; set; } public ExternalBridgeLoginIntent LoginIntent { get; set; } public MessageHandler(Action method) { Method = method; } public MessageHandler(Action method, ExternalBridgeLoginIntent intent) : this(method) { RequiresLogin = true; LoginIntent = intent; } } #endregion private String _eventsToken; private String _machineStatusToken; private String _diagnosticsToken; private String _debugLogsToken; private String _applicationLogsToken; private String _inkFillingToken; private IMachineOperator _machineOperator; private Dictionary _messageHandlers; #region Events public event EventHandler LoginRequest; public event EventHandler ColorProfileRequest; public event EventHandler Disconnected; public event EventHandler ReceiverRequestReceived; #endregion #region Properties public bool AllowSafetyLevelOperations { get; set; } public bool RequiresDiagnostics { get; private set; } public bool RequiresDebugLogs { get; private set; } public bool RequiresEventsNotification { get; private set; } public bool RequiresMachineStatusUpdate { get; private set; } public bool RequiresApplicationLogs { get; private set; } public bool RequiresInkFillingStatus { get; private set; } public bool IsLoggedIn { get; private set; } public ExternalBridgeLoginIntent LoginIntent { get; private set; } public String UserName { get; private set; } private ExternalBridgeLoginRequest _loginInfo; public ExternalBridgeLoginRequest LoginInfo { get { return _loginInfo; } set { _loginInfo = value; RaisePropertyChangedAuto(); } } public bool IsLoggedInAndRequiresDiagnostics { get { return IsLoggedIn && (LoginIntent == ExternalBridgeLoginIntent.Diagnostics || LoginIntent == ExternalBridgeLoginIntent.FullControl); } } #endregion #region Constructors static ExternalBridgeReceiver() { LastSafetyLevelContactsTimes = new Dictionary(); } public ExternalBridgeReceiver(IMachineOperator machineOperator) { ComponentName = $"External Bridge Receiver {_component_counter++}"; FailsWithAdapter = true; _machineOperator = machineOperator; _messageHandlers = new Dictionary(); UseKeepAlive = false; KeepAliveTimeout = TimeSpan.FromSeconds(10); KeepAliveRetries = 4; _messageHandlers.Add(MessageType.ExternalBridgeLoginRequest, new MessageHandler(OnExternalBridgeLoginRequest)); _messageHandlers.Add(MessageType.ExternalBridgeLogoutRequest, new MessageHandler(OnExternalBridgeLogoutRequest)); _messageHandlers.Add(MessageType.ColorProfileRequest, new MessageHandler(OnColorProfileRequest)); _messageHandlers.Add(MessageType.StartDiagnosticsRequest, new MessageHandler(OnStartDiagnosticsRequest, ExternalBridgeLoginIntent.Diagnostics)); _messageHandlers.Add(MessageType.StopDiagnosticsRequest, new MessageHandler(OnStopDiagnosticsRequest, ExternalBridgeLoginIntent.Diagnostics)); _messageHandlers.Add(MessageType.StartDebugLogRequest, new MessageHandler(OnStartDebugLogRequest, ExternalBridgeLoginIntent.Diagnostics)); _messageHandlers.Add(MessageType.StopDebugLogRequest, new MessageHandler(OnStopDebugLogRequest, ExternalBridgeLoginIntent.Diagnostics)); _messageHandlers.Add(MessageType.StartEventsNotificationRequest, new MessageHandler(OnStartEventsNotificationRequest, ExternalBridgeLoginIntent.Diagnostics)); _messageHandlers.Add(MessageType.StopEventsNotificationRequest, new MessageHandler(OnStopEventsNotificationRequest, ExternalBridgeLoginIntent.Diagnostics)); _messageHandlers.Add(MessageType.StartMachineStatusUpdateRequest, new MessageHandler(OnStartMachineStatusUpdateRequest, ExternalBridgeLoginIntent.Diagnostics)); _messageHandlers.Add(MessageType.StopMachineStatusUpdateRequest, new MessageHandler(OnStopMachineStatusUpdateRequest, ExternalBridgeLoginIntent.Diagnostics)); _messageHandlers.Add(MessageType.StartApplicationLogsRequest, new MessageHandler(OnStartApplicationLogsRequest, ExternalBridgeLoginIntent.Diagnostics)); _messageHandlers.Add(MessageType.StopApplicationLogsRequest, new MessageHandler(OnStopApplicationLogsRequest, ExternalBridgeLoginIntent.Diagnostics)); _messageHandlers.Add(MessageType.JobRequest, new MessageHandler(OnJobRequest, ExternalBridgeLoginIntent.Diagnostics)); _messageHandlers.Add(MessageType.StartPowerDownRequest, new MessageHandler(OnStartPowerDownRequest, ExternalBridgeLoginIntent.Diagnostics)); _messageHandlers.Add(MessageType.StartInkFillingStatusRequest, new MessageHandler(OnStartInkFillingStatusRequest, ExternalBridgeLoginIntent.Diagnostics)); } public ExternalBridgeReceiver(TcpClient tcpClient, IMachineOperator machineOperator) : this(machineOperator) { Adapter = new TcpTransportAdapter(tcpClient); } public ExternalBridgeReceiver(SignalRTransportAdapter signalRAdapter, IMachineOperator machineOperator) : this(machineOperator) { Adapter = signalRAdapter; } #endregion #region Override Methods protected override void OnRequestReceived(RequestReceivedEventArgs e) { base.OnRequestReceived(e); if (e.Handled) return; var container = e.Container; try { if (e.Container.Type == MessageType.ConfigureProtocolRequest) { var message = MessageFactory.ParseTangoMessageFromContainer(container); SendResponse(new ConfigureProtocolResponse() { Confirmed = true }, container.Token, new TransportResponseConfig() { Immediate = true, Priority = QueuePriority.High }); Task.Factory.StartNew(() => { Thread.Sleep(200); Adapter.EnableCompression = message.Message.EnableCompression; GenericProtocol = message.Message.GenericProtocol; }); return; } if (!AllowSafetyLevelOperations) { if (ExternalBridgeService.SafetyLevelOperations.Contains(container.Type)) { SendErrorResponse(new AuthenticationException("The specified action requires safety level permission that is not granted for the current session."), container.Token); return; } } else { if (UserName != null) { LastSafetyLevelContactsTimes[UserName] = DateTime.Now; } } if (_messageHandlers.ContainsKey(container.Type)) { var handler = _messageHandlers[container.Type]; if (handler.RequiresLogin && (!IsLoggedIn || (int)handler.LoginIntent > (int)LoginIntent)) { SendErrorResponse(new AuthenticationException("The specified intent does not grant the specified action."), container.Token); return; } try { try { handler.Method.Invoke(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 { if (IsLoggedIn) { ExternalBridgeReceiverRequestReceivedEventArgs args = new ExternalBridgeReceiverRequestReceivedEventArgs(); args.Container = container; ReceiverRequestReceived?.Invoke(this, args); if (!args.Handled) { OnAnyRequest(container); } } } } catch (Exception ex) { LogManager.Log(ex, "Error occurred in processing request message on external bridge receiver."); } } 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); OnDisconnected(); } public async override Task Disconnect() { try { if (IsLoggedIn) { await SendRequest(new ExternalBridgeLogoutRequest(), new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(3), ShouldLog = true }); } } catch (Exception ex) { LogManager.Log(ex, "Error sending an external bridge log out request."); } finally { ClearQueues(); } try { IsLoggedIn = false; await base.Disconnect(); } catch { } OnDisconnected(); } #endregion #region Virtual Methods protected virtual void OnDisconnected() { Disconnected?.Invoke(this, new EventArgs()); } #endregion #region Message Handlers protected 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()}"); ExternalBridgeReceiverLoginRequestEventArgs args = new ExternalBridgeReceiverLoginRequestEventArgs((machine, deviceInfo, applicationInfo) => { //Confirm LogManager.Log("External bridge client has logged-in successfully."); IsLoggedIn = true; LoginIntent = request.Message.Intent; UserName = request.Message.UserName; LoginInfo = request.Message; var response = new ExternalBridgeLoginResponse(); response.Authenticated = true; response.SerialNumber = machine.SerialNumber; response.DeviceInformation = deviceInfo; response.ApplicationInformation = applicationInfo; AllowSafetyLevelOperations = request.Message.RequireSafetyLevelOperations; SendResponse(response, container.Token); UpdateMachineOperatorStatus((UpdateStatus)_machineOperator.Status); UseKeepAlive = true; if (AllowSafetyLevelOperations && UserName != null) { LastSafetyLevelContactsTimes[UserName] = DateTime.Now; } }, (reason) => { //Decline SendResponse(new ExternalBridgeLoginResponse(), container.Token, new TransportResponseConfig() { ErrorCode = ErrorCode.GeneralError, ErrorMessage = reason }); Disconnect().GetAwaiter().GetResult(); }); args.Address = Adapter.Address; args.Request = request; LoginRequest?.Invoke(this, args); } protected virtual void OnExternalBridgeLogoutRequest(MessageContainer container) { try { SendResponse(new ExternalBridgeLogoutResponse(), container.Token); } catch (Exception ex) { LogManager.Log(ex); } finally { OnDisconnected(); ClearQueues(); } } protected async virtual void OnAnyRequest(MessageContainer container) { if (!container.Continuous) { try { var response = await _machineOperator.SendRequest(container); await SendResponse(response); } catch (Exception ex) { await SendErrorResponse(ex, container.Token); } } else { try { _machineOperator.SendContinuousRequest(container).Subscribe((response) => { if (State == TransportComponentState.Connected) { SendResponse(response); } }, (ex) => { if (State == TransportComponentState.Connected) { if (ex is ResponseErrorException) { SendResponse((ex as ResponseErrorException).Container); } else if (ex is ContinuousResponseAbortedException) { SendResponse((ex as ContinuousResponseAbortedException).Container); } } }); } catch (Exception ex) { await SendErrorResponse(ex, container.Token); } } } protected virtual 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); } protected virtual void OnStartDiagnosticsRequest(MessageContainer container) { _diagnosticsToken = container.Token; SendResponse(new StartDiagnosticsResponse(), _diagnosticsToken); RequiresDiagnostics = true; } protected virtual void OnStopDiagnosticsRequest(MessageContainer container) { if (_diagnosticsToken != null) { RequiresDiagnostics = false; SendResponse(new StartDiagnosticsResponse(), _diagnosticsToken, new TransportResponseConfig() { Completed = true }); _diagnosticsToken = null; SendResponse(new StopDiagnosticsResponse(), container.Token); } } protected virtual void OnStartDebugLogRequest(MessageContainer container) { _debugLogsToken = container.Token; SendResponse(new StartDebugLogResponse(), _debugLogsToken); RequiresDebugLogs = true; } protected virtual void OnStopDebugLogRequest(MessageContainer container) { if (_debugLogsToken != null) { RequiresDebugLogs = false; SendResponse(new StartDebugLogResponse(), _debugLogsToken, new TransportResponseConfig() { Completed = true }); _debugLogsToken = null; SendResponse(new StopDebugLogResponse(), container.Token); } } protected virtual void OnStartEventsNotificationRequest(MessageContainer container) { _eventsToken = container.Token; SendResponse(new StartEventsNotificationResponse(), _eventsToken); RequiresEventsNotification = true; } protected virtual void OnStopEventsNotificationRequest(MessageContainer container) { if (_eventsToken != null) { RequiresEventsNotification = false; SendResponse(new StartEventsNotificationResponse(), _eventsToken, new TransportResponseConfig() { Completed = true }); _eventsToken = null; SendResponse(new StopEventsNotificationResponse(), container.Token); } } protected virtual void OnStartMachineStatusUpdateRequest(MessageContainer container) { _machineStatusToken = container.Token; SendResponse(new StartMachineStatusUpdateResponse(), _machineStatusToken); RequiresMachineStatusUpdate = true; } protected virtual void OnStopMachineStatusUpdateRequest(MessageContainer container) { if (_machineStatusToken != null) { RequiresMachineStatusUpdate = false; SendResponse(new StartMachineStatusUpdateResponse(), _machineStatusToken, new TransportResponseConfig() { Completed = true }); _machineStatusToken = null; SendResponse(new StopMachineStatusUpdateResponse(), container.Token); } } protected virtual void OnStartApplicationLogsRequest(MessageContainer container) { _applicationLogsToken = container.Token; SendResponse(new StartApplicationLogsResponse(), _applicationLogsToken); RequiresApplicationLogs = true; } protected virtual void OnStopApplicationLogsRequest(MessageContainer container) { if (_applicationLogsToken != null) { RequiresApplicationLogs = false; SendResponse(new StartApplicationLogsResponse(), _applicationLogsToken, new TransportResponseConfig() { Completed = true }); _applicationLogsToken = null; SendResponse(new StopApplicationLogsResponse(), container.Token); } } protected virtual void OnJobRequest(MessageContainer container) { if (LoginIntent != ExternalBridgeLoginIntent.FullControl) { throw new InvalidOperationException($"Job execution is disabled while session intent is '{LoginIntent}'."); } if (_machineOperator.IsPrinting) { throw new InvalidOperationException($"Could not execute job while machine operator status is '{_machineOperator.Status}'."); } else { OnAnyRequest(container); } } protected virtual void OnStartPowerDownRequest(MessageContainer container) { SendResponse(new StartPowerDownResponse() { }, container.Token, new TransportResponseConfig() { ErrorCode = ErrorCode.ContinuousResponseAborted, ErrorMessage = "Power down request is not supported via external bridge." }); } protected virtual void OnStartInkFillingStatusRequest(MessageContainer container) { _inkFillingToken = container.Token; UpdateInkFillingStatus(null); RequiresInkFillingStatus = true; } #endregion #region Continuous Updates public void UpdateDiagnostics(MessageContainer container) { try { if (AllowSafetyLevelOperations && UserName != null) //Usually all clients require diagnostics so we will update the last contact time here.. { LastSafetyLevelContactsTimes[UserName] = DateTime.Now; } if (_diagnosticsToken != null) { var cloned = container.Clone(); cloned.Token = _diagnosticsToken; SendResponse(cloned); } } catch (Exception ex) { Debug.WriteLine(ex); } } public void UpdateDebugLogs(MessageContainer container) { try { if (_debugLogsToken != null) { var cloned = container.Clone(); cloned.Token = _debugLogsToken; SendResponse(cloned, new TransportResponseConfig() { Priority = QueuePriority.Low, }); } } catch (Exception ex) { Debug.WriteLine(ex); } } public void UpdateEvents(MessageContainer container) { try { if (_eventsToken != null) { var cloned = container.Clone(); cloned.Token = _eventsToken; SendResponse(cloned); } } catch (Exception ex) { Debug.WriteLine(ex); } } public void UpdateMachineStatus(MessageContainer container) { try { if (_machineStatusToken != null) { var cloned = container.Clone(); cloned.Token = _machineStatusToken; SendResponse(cloned); } } catch (Exception ex) { Debug.WriteLine(ex); } } public void UpdateApplicationLogs(MessageContainer container) { try { if (_applicationLogsToken != null) { var cloned = container.Clone(); cloned.Token = _applicationLogsToken; SendResponse(cloned, new TransportResponseConfig() { Priority = QueuePriority.Low }); } } catch (Exception ex) { Debug.WriteLine(ex); } } public void UpdateMachineOperatorStatus(UpdateStatus status) { try { SendRequest(new UpdateStatusRequest() { Status = status, }); } catch (Exception ex) { Debug.WriteLine(ex); } } public void UpdateInkFillingStatus(MessageContainer container) { try { if (_inkFillingToken != null) { if (container == null) { container = new MessageContainer() { Continuous = true, Type = MessageType.StartInkFillingStatusResponse, Data = ByteString.CopyFrom((new StartInkFillingStatusResponse() { Status = _machineOperator.InkFillingStatus }).ToBytes()) }; } var cloned = container.Clone(); cloned.Token = _inkFillingToken; SendResponse(cloned, new TransportResponseConfig()); } } catch (Exception ex) { Debug.WriteLine(ex); } } #endregion } }