// ********************************************************************************************************** // CommDeviceTcpAsync.cs // 3/6/2024 // NGI - Next Generation Interceptor // // Contract No. HQ0856-21-C-0003/1022000209 // // THIS DOCUMENT DOES NOT CONTAIN TECHNOLOGY OR TECHNICAL DATA CONTROLLED UNDER EITHER THE U.S. // INTERNATIONAL TRAFFIC IN ARMS REGULATIONS OR THE U.S. EXPORT ADMINISTRATION REGULATIONS. // // RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION PROPRIETARY TO RAYTHEON // COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. // DISCLOSURE TO UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO RAYTHEON // COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS CONTENTS SHALL BE FURNISHED OR DISCLOSED // TO OR COPIED OR USED BY PERSONS OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF // RAYTHEON COMPANY. // // UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY. // // DESTRUCTION NOTICE: FOR CLASSIFIED DOCUMENTS FOLLOW THE PROCEDURES IN DOD 5220.22-M, // NATIONAL INDUSTRIAL SECURITY PROGRAM OPERATING MANUAL, FEBRUARY 2006, // INCORPORATING CHANGE 1, MARCH 28, 2013, CHAPTER 5, SECTION 7, OR DODM 5200.01-VOLUME 3, // DOD INFORMATION SECURITY PROGRAM: PROTECTION OF CLASSIFIED INFORMATION, ENCLOSURE 3, // SECTION 17. FOR CONTROLLED UNCLASSIFIED INFORMATION FOLLOW THE PROCEDURES IN DODM 5200.01-VOLUME 4, // INFORMATION SECURITY PROGRAM: CONTROLLED UNCLASSIFIED INFORMATION. // // CONTROLLED BY: MISSILE DEFENSE AGENCY // CONTROLLED BY: GROUND-BASED MIDCOURSE DEFENSE PROGRAM OFFICE // CUI CATEGORY: CTI // DISTRIBUTION/DISSEMINATION CONTROL: F // POC: Alex Kravchenko (1118268) // ********************************************************************************************************** using System; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using NLog; using Raytheon.Common; namespace Raytheon.Instruments { /// /// A sim communication device /// public class CommDeviceTcpAsync : ICommAsync { #region PrivateClassMembers private uint _defaultReadTimeout; private uint _defaultSendTimeout; private uint _defaultReadBufferSize; private static readonly object _syncObj = new object(); private TcpClient _tcpClient; private TcpListener _tcpListener; private NetworkStream _tcpIpStream; private int _port; private string _remoteAddress; private int _remotePort; private readonly string _name; private State _state; private readonly ILogger _logger; private readonly IConfigurationManager _configurationManager; private readonly IConfiguration _configuration; #endregion public bool ClearErrors() => false; public bool FrontPanelEnabled { get => false; set => throw new NotImplementedException(); } public bool DisplayEnabled { get => false; set => throw new NotImplementedException(); } public string DetailedStatus => $"This is a TCP/IP Device called {_name}"; public InstrumentMetadata Info => throw new NotImplementedException(); public State Status => _state; public string Name => _name; public SelfTestResult PerformSelfTest() => SelfTestResult; public SelfTestResult SelfTestResult => SelfTestResult.Unknown; public void Open() => Initialize(); public void Close() => Shutdown(); public void Reset() { Close(); Open(); } #region Private Functions /// /// Dispose of the resources contained by this object /// public void Dispose() { try { lock (_syncObj) { Dispose(true); GC.SuppressFinalize(this); } } catch (Exception err) { _logger.Error(err.Message + "\r\n" + err.StackTrace); } } /// /// Dispose of the resources contained by this object /// /// protected virtual void Dispose(bool disposing) { if (disposing) { // close the socket try { Shutdown(); } catch (Exception err) { _logger.Error(err.Message + "\r\n" + err.StackTrace); } } } #endregion #region Public Functions /// /// CommDevice factory constructor /// /// /// public CommDeviceTcpAsync(string deviceName, IConfigurationManager configurationManager) { _name = deviceName; // _tcpClient is created in Initialize() _tcpClient = null; _tcpIpStream = null; _state = State.Uninitialized; _logger = LogManager.GetLogger($"{this.GetType().Name} - {deviceName}"); _configurationManager = configurationManager; _configuration = _configurationManager.GetConfiguration(Name); } /// /// initialize instrument /// public void Initialize() { if (_state != State.Uninitialized) { _logger.Warn("Reinitialization of existing TCP Async Connection. Attempting to call Shutdown."); Shutdown(); } _defaultReadTimeout = _configuration.GetConfigurationValue("TcpClient", "ReadTimeout", 25); _defaultSendTimeout = _configuration.GetConfigurationValue("TcpClient", "SendTimeout", 5000); _defaultReadBufferSize = _configuration.GetConfigurationValue("TcpClient", "BufferSize", 1024); _remoteAddress = _configuration.GetConfigurationValue("TcpClient", "RemoteAddress", "127.0.0.1"); _port = _configuration.GetConfigurationValue("TcpClient", "Port", 0); if (string.IsNullOrEmpty(_remoteAddress)) { _tcpListener = new TcpListener(IPAddress.Any, _port); _tcpListener.Start(); _logger.Debug($"{MethodBase.GetCurrentMethod().Name} Started Listening on Port: {_port}"); Task.Run(async () => await _tcpListener.AcceptTcpClientAsync()).ContinueWith(t => { _tcpClient = t.Result; _remoteAddress = ((IPEndPoint)_tcpClient.Client.RemoteEndPoint).Address.ToString(); _remotePort = ((IPEndPoint)_tcpClient.Client.RemoteEndPoint).Port; _logger.Debug($"{MethodBase.GetCurrentMethod().Name} Connection Established from Remote Address: {_remoteAddress}:{_remotePort}"); // set timeouts _tcpClient.Client.SendTimeout = (int)_defaultSendTimeout; _tcpClient.Client.ReceiveTimeout = (int)_defaultReadTimeout; // get the stream _tcpIpStream = _tcpClient.GetStream(); _state = State.Ready; }); } else { _remotePort = _port; _tcpClient = new TcpClient(_remoteAddress, _remotePort); _logger.Debug($"{MethodBase.GetCurrentMethod().Name} Connected to Remote Address {_remoteAddress}:{_remotePort}"); // set timeouts _tcpClient.Client.SendTimeout = (int)_defaultSendTimeout; _tcpClient.Client.ReceiveTimeout = (int)_defaultReadTimeout; // get the stream _tcpIpStream = _tcpClient.GetStream(); _state = State.Ready; } } /// /// shuts down the device /// public void Shutdown() { _logger.Debug("Shutting down"); _tcpClient?.Dispose(); _tcpClient = null; _tcpListener?.Stop(); _tcpListener = null; _tcpIpStream?.Dispose(); _tcpIpStream = null; _state = State.Uninitialized; } /// /// Read data from the device asynchronously. /// /// The buffer to put the data in /// The number of bytes read public async Task ReadAsync(byte[] dataRead, CancellationToken token = default) { if (_tcpIpStream == null) return 0; if (_tcpIpStream.DataAvailable == true) { _state = State.Busy; var bytesRead = await _tcpIpStream.ReadAsync(dataRead, 0, dataRead.Length, token); _logger.Trace($"Reading Data, bytes received: {bytesRead}"); _state = State.Ready; return (uint)bytesRead; } else { return 0; } } /// /// Read string from the device asynchronously. /// /// The buffer to put the data in /// The number of bytes read public async Task ReadAsync(CancellationToken token = default) { if (_tcpIpStream == null) return null; if (_tcpIpStream.DataAvailable == true) { _state = State.Busy; var buffer = new byte[_defaultReadBufferSize]; var bytesRead = await _tcpIpStream.ReadAsync(buffer, 0, buffer.Length, token); _state = State.Ready; var message = Encoding.UTF8.GetString(buffer, 0, bytesRead); _logger.Trace($"Reading Data, message received: {message}"); return message; } else { return null; } } /// /// Sets the read timeout /// /// public void SetReadTimeout(uint timeoutMs) { _logger.Trace($"Setting Reader Timeout: {timeoutMs} Ms"); _tcpClient.Client.ReceiveTimeout = (int)timeoutMs; } /// /// Write data to the device asynchronously /// /// /// /// public async Task WriteAsync(byte[] dataToSend, uint numBytesToWrite, CancellationToken token = default) { if (_tcpIpStream == null) return 0; _logger.Trace($"Writing message to ({_remoteAddress}:{_remotePort}), bytes: {dataToSend?.Length}"); _state = State.Busy; await _tcpIpStream.WriteAsync(dataToSend, 0, (int)numBytesToWrite, token); _state = State.Ready; return numBytesToWrite; } /// /// Write string data to the device asynchronously /// /// /// public async Task WriteAsync(string message, CancellationToken token = default) { if (_tcpIpStream == null) return; _logger.Trace($"Writing message to ({_remoteAddress}:{_remotePort}), message: {message}"); _state = State.Busy; var buffer = Encoding.UTF8.GetBytes(message); await _tcpIpStream.WriteAsync(buffer, 0, buffer.Length, token); _state = State.Ready; } /// /// Send Command and Get Response asynchronously /// /// /// /// public async Task SendCommandGetResponseAsync(string message, CancellationToken cancellationToken = default, int timeoutInMs = 5000) { if (_tcpIpStream == null) return null; _logger.Trace($"Sending command waiting for response from ({_remoteAddress}:{_remotePort}), message: {message}"); using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeoutInMs))) { if (cancellationToken == default) { cancellationToken = cts.Token; } await WriteAsync(message, cancellationToken); string readResponse = await ReadAsync(cancellationToken); _logger.Trace($"Received response: {readResponse}"); return readResponse; } } /// /// Send Command and Get Response asynchronously /// /// /// /// public async Task SendCommandGetResponseAsync(byte[] data, CancellationToken cancellationToken = default, int timeoutInMs = 5000) { if (_tcpIpStream == null) return null; _logger.Trace($"Sending command waiting for response from ({_remoteAddress}:{_remotePort}), message: {data}"); using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeoutInMs))) { if (cancellationToken == default) { cancellationToken = cts.Token; } await WriteAsync(data, (uint)data.Length, cancellationToken); byte[] buffer = new byte[_defaultReadBufferSize]; uint bytesRead = await ReadAsync(buffer, cancellationToken); _logger.Trace($"Received response of size: {bytesRead}"); return buffer; } } /// /// keeps reading until canceled via token, /// received messages sent to dataReceived function /// /// /// /// public async Task KeepReadingAsync(CancellationToken cancellationToken, Action dataReceived) { _logger.Debug($"Starting continuous reading from {_remoteAddress}:{_remotePort} ..."); byte[] buffer = new byte[_defaultReadBufferSize]; while (!cancellationToken.IsCancellationRequested) { if (_tcpIpStream == null) { continue; } uint bytesRead = await ReadAsync(buffer, cancellationToken); if (bytesRead > 0) { string data = Encoding.UTF8.GetString(buffer, 0, (int)bytesRead); _logger.Trace($"Message received from {_remoteAddress}:{_remotePort}: {data}"); dataReceived(data); Array.Clear(buffer, 0, (int)bytesRead); } } _logger.Debug($"Finished continuous reading from {_remoteAddress}:{_remotePort} ..."); } /// /// keeps reading until canceled via token, /// received messages sent to dataReceived function /// /// /// /// public async Task KeepReadingAsync(CancellationToken cancellationToken, Action dataReceived) { _logger.Debug($"Starting continuous reading from {_remoteAddress}:{_remotePort} ..."); byte[] buffer = new byte[_defaultReadBufferSize]; while (!cancellationToken.IsCancellationRequested) { if (_tcpIpStream == null) { continue; } uint bytesRead = await ReadAsync(buffer, cancellationToken); if (bytesRead > 0) { _logger.Trace($"Message received from {_remoteAddress}:{_remotePort}: size {bytesRead}"); byte[] bufferCopy = new byte[bytesRead]; Array.Copy(buffer, bufferCopy, bytesRead); dataReceived(bufferCopy); Array.Clear(buffer, 0, (int)bytesRead); } } _logger.Debug($"Finished continuous reading from {_remoteAddress}:{_remotePort} ..."); } #endregion } }