// ********************************************************************************************************** // CommDeviceUdpAsync.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.Text; using System.Threading; using System.Threading.Tasks; using NLog; using Raytheon.Common; namespace Raytheon.Instruments { /// /// A sim communication device /// public class CommDeviceUdpAsync : ICommAsync { #region PrivateClassMembers private uint _defaultReadTimeout; private uint _defaultSendTimeout; private uint _defaultReadBufferSize; private static readonly object _syncObj = new object(); private UdpClient _udpClient; private IPEndPoint _remoteEndPoint; private int _localPort; private int _remotePort; private string _remoteAddress; 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 CommDeviceUdpAsync(string deviceName, IConfigurationManager configurationManager) { _name = deviceName; _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 UDP Async Connection. Attempting to call Shutdown."); Shutdown(); } _defaultReadTimeout = _configuration.GetConfigurationValue("UdpClient", "ReadTimeout", 25); _defaultSendTimeout = _configuration.GetConfigurationValue("UdpClient", "SendTimeout", 5000); _defaultReadBufferSize = _configuration.GetConfigurationValue("UdpClient", "BufferSize", 1024); _localPort = _configuration.GetConfigurationValue("UdpClient", "LocalPort", 0); _remoteAddress = _configuration.GetConfigurationValue("UdpClient", "RemoteAddress", "127.0.0.1"); _remotePort = _configuration.GetConfigurationValue("UdpClient", "RemotePort", 0); _udpClient = new UdpClient(); if (string.IsNullOrEmpty(_remoteAddress)) { _logger.Debug($"Initializing as UDP Server. Listening on port: {_localPort}"); _udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, _localPort)); } else { _logger.Debug($"Initializing as UDP Client. Ready to Talk to: {_remoteAddress}:{_remotePort}"); // get the remote endpoint _remoteEndPoint = new IPEndPoint(IPAddress.Parse(_remoteAddress), _remotePort); } // set timeouts _udpClient.Client.SendTimeout = (int)_defaultSendTimeout; _udpClient.Client.ReceiveTimeout = (int)_defaultReadTimeout; _state = State.Ready; } /// /// shuts down the device /// public void Shutdown() { _logger.Debug("Shutting Down..."); _state = State.Uninitialized; _udpClient?.Dispose(); _udpClient = null; } /// /// 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 (_udpClient == null) return 0; var received = await _udpClient.ReceiveAsync(); Array.Copy(received.Buffer, dataRead, Math.Min(dataRead.Length, received.Buffer.Length)); UpdateRemoteAddressAndPort(received.RemoteEndPoint); _logger.Trace($"Reading Data, bytes received: {received.Buffer?.Length}"); return (uint)received.Buffer.Length; } /// /// 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 (_udpClient == null) return null; var received = await _udpClient.ReceiveAsync(); UpdateRemoteAddressAndPort(received.RemoteEndPoint); var data = Encoding.UTF8.GetString(received.Buffer); _logger.Trace($"Reading Data, message received: {data}"); return data; } /// /// Sets the read timeout /// /// public void SetReadTimeout(uint timeoutMs) { if (_udpClient == null) return; _logger.Trace($"Setting Reader Timeout: {timeoutMs} Ms"); _udpClient.Client.ReceiveTimeout = (int)timeoutMs; } /// /// Write data to the device asynchronously /// /// /// /// public async Task WriteAsync(byte[] dataToSend, uint numBytesToWrite, CancellationToken token = default) { if (_udpClient == null || _remoteEndPoint == null) return 0; _logger.Trace($"Writing message to ({_remoteAddress}:{_remotePort}), bytes: {dataToSend?.Length}"); _state = State.Busy; await _udpClient.SendAsync(dataToSend, (int)numBytesToWrite, _remoteEndPoint); _state = State.Ready; return numBytesToWrite; } /// /// Write string data to the device asynchronously /// /// /// /// public async Task WriteAsync(string message, CancellationToken token = default) { if (_udpClient == null || _remoteEndPoint == null) return; _logger.Trace($"Writing message to ({_remoteAddress}:{_remotePort}), message: {message}"); _state = State.Busy; var dataToSend = Encoding.UTF8.GetBytes(message); await _udpClient.SendAsync(dataToSend, dataToSend.Length, _remoteEndPoint); _state = State.Ready; } /// /// Send Command and Get Response asynchronously /// /// /// /// /// public async Task SendCommandGetResponseAsync(string message, CancellationToken cancellationToken = default, int timeoutInMs = 5000) { if (_udpClient == null) return null; _logger.Trace($"Sending command waiting for response from ({_remoteAddress}:{_remotePort}), message: {message}"); using (CancellationTokenSource 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 (_udpClient == null) return null; _logger.Trace($"Sending command waiting for response from ({_remoteAddress}:{_remotePort}), message length: {data.Length}"); using (CancellationTokenSource 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) { if (_udpClient == null) return; _logger.Debug($"Starting continuous reading from port: {_localPort} ..."); while (!cancellationToken.IsCancellationRequested) { if (_udpClient == null) break; var received = await ReceiveAsync(_udpClient, cancellationToken); if (received != null && received.Buffer != null) { UpdateRemoteAddressAndPort(received.RemoteEndPoint); _logger.Trace($"Incoming Data from {_remoteAddress}:{_remotePort}: size {received.Buffer.Length}"); string data = Encoding.UTF8.GetString(received.Buffer, 0, received.Buffer.Length); dataReceived(data); } } _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) { if (_udpClient == null) return; _logger.Debug($"Starting continuous reading from port: {_localPort} ..."); while (!cancellationToken.IsCancellationRequested) { if (_udpClient == null) break; var received = await ReceiveAsync(_udpClient, cancellationToken); if (received != null && received.Buffer != null) { UpdateRemoteAddressAndPort(received.RemoteEndPoint); _logger.Trace($"Incoming Data from {_remoteAddress}:{_remotePort}: size {received.Buffer.Length}"); dataReceived(received.Buffer); } } _logger.Debug($"Finished continuous reading from {_remoteAddress}:{_remotePort} ..."); } #endregion #region Private Functions /// /// Update client information for logging /// /// private void UpdateRemoteAddressAndPort(IPEndPoint remoteEndPoint) { if (remoteEndPoint == null) return; if (_remotePort == 0 || string.IsNullOrEmpty(_remoteAddress)) { _remotePort = remoteEndPoint.Port; _remoteAddress = remoteEndPoint.Address.ToString(); } if (_remoteEndPoint == null || _remoteEndPoint.Port != remoteEndPoint.Port) { // get the remote endpoint _remoteEndPoint = remoteEndPoint; _logger.Debug($"Starting to receive data from {_remoteAddress}:{_remotePort}"); } } /// /// ReceiveAsyc with cancellation token implementation /// /// /// /// private Task ReceiveAsync(UdpClient client, CancellationToken breakToken) => breakToken.IsCancellationRequested ? Task.Run(() => new UdpReceiveResult()) : Task.Factory.FromAsync ((callback, state) => client.BeginReceive(callback, state), (ar) => { if (breakToken.IsCancellationRequested) return new UdpReceiveResult(); IPEndPoint remoteEP = null; var buffer = client.EndReceive(ar, ref remoteEP); return new UdpReceiveResult(buffer, remoteEP); }, null); #endregion } }