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
}
}