// ********************************************************************************************************** // BitGenSoftMeasurementManager.cs // 7/28/2022 // 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 NLog; using Raytheon.Common; using Raytheon.Instruments; using Raytheon.Instruments.Exceptions; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using BitGenSoftMeasurementManagerLib; namespace MeasurementManagerLib { /// /// Bit Gen Soft Measurement Manager class /// public class BitGenSoftMeasurementManager : IDisposable { private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Dictionary _bitNodes = new Dictionary(); private readonly IInstrumentManager _instrumentManager; private IConfigurationManager _configurationManager; private int _checkForMessageIntervalMs = 10; /// /// constructor that will create a list of BIT instruments /// /// /// public BitGenSoftMeasurementManager(IInstrumentManager instrumentManager, List instrumentNames) : this(instrumentManager) { try { foreach (string instrumentName in instrumentNames) { var bitNode = (IBit)_instrumentManager.GetGenericInstrument(instrumentName); if (bitNode == null) { _logger.Error("Error creating TCP BIT device, check your settings"); } else { _bitNodes.Add(instrumentName, bitNode); } } } catch (Exception ex) { _logger.Error($"Unable to create BIT instrument\n{ex.Message}"); } } /// /// in case instrument manager was passed from upper level /// /// private BitGenSoftMeasurementManager(IInstrumentManager instrumentManager) { _instrumentManager = instrumentManager; InitializeConfigurationManager(); } /// /// initializes configuration manager, /// will use configuration path from General Instrument Manager if available /// otherwise will try to use the instruments path /// private void InitializeConfigurationManager() { string defaultPath; if (_instrumentManager is GeneralInstrumentManager generalinstrumentManager) { defaultPath = generalinstrumentManager._configLocation; } else { defaultPath = _instrumentManager._partsLocation; } _configurationManager = new RaytheonConfigurationManager(defaultPath); var config = _configurationManager.GetConfiguration("BitGenSoftMeasurementManager"); _checkForMessageIntervalMs = config.GetConfigurationValue("Settings", "CheckForMessageIntervalMs", 10); } /// /// initialize COE nodes based on test type /// /// /// public void InitNodes() { foreach (var node in _bitNodes) { try { IBit bitNode = node.Value; bitNode.Initialize(); bitNode.Open(); } catch (Exception ex) { _logger.Error(ex, ex.Message); } } } /// /// close all connections /// /// public void Dispose() { foreach (var bitNode in _bitNodes) { bitNode.Value?.Shutdown(); } } /// /// straight pass-through the message and parameters /// /// /// /// /// public bool RunBIT(string messageId, uint timeoutInMs, IEnumerable> messageParams = null) { if (_bitNodes.Any()) { IBit bitNode = _bitNodes.First().Value; return bitNode.RunBIT(messageId, timeoutInMs, messageParams); } else { _logger.Error($"Unable to locate BIT node. No nodes defined"); return false; } } /// /// straight pass-through the message and parameters /// /// /// /// /// /// public bool RunBIT(string instrumentName, string messageId, uint timeoutInMs, IEnumerable> messageParams = null) { if (_bitNodes.ContainsKey(instrumentName)) { return _bitNodes[instrumentName].RunBIT(messageId, timeoutInMs, messageParams); } else { _logger.Error($"Unable to locate BIT node {instrumentName}"); return false; } } /// /// always runs the request on the first node, /// /// /// /// /// /// public BitTestResults RunBITWaitForResults(string messageIdOut, string messageIdIn, uint timeoutInMs, IEnumerable> messageParams = null) { if (_bitNodes.Any()) { IBit bitNode = _bitNodes.First().Value; return bitNode.RunBITWaitForResults(messageIdOut, messageIdIn, timeoutInMs, messageParams); } else { _logger.Error($"Unable to locate BIT node. No nodes defined"); return null; } } /// /// run BIT message and wait for result from the instrument based on the name /// /// /// /// /// /// /// public BitTestResults RunBITWaitForResults(string instrumentName, string messageIdOut, string messageIdIn, uint timeoutInMs, IEnumerable> messageParams = null) { if (_bitNodes.ContainsKey(instrumentName)) { return _bitNodes[instrumentName].RunBITWaitForResults(messageIdOut, messageIdIn, timeoutInMs, messageParams); } else { _logger.Error($"Unable to locate BIT node {instrumentName}"); return null; } } /// /// RunBITWaitForResults for the whole list of BIT Commands /// runs on the first node /// /// /// public List RunBITWaitForResultsList(List commands) { if (_bitNodes.Any()) { return RunBITWaitForResultsList(commands, _bitNodes.First().Value); } else { _logger.Error("Unable to locate BIT node. No nodes defined"); return null; } } /// /// RunBITWaitForResults for the whole list of BIT Commands /// /// /// /// public List RunBITWaitForResultsList(string instrumentName, List commands) { if (_bitNodes.ContainsKey(instrumentName)) { return RunBITWaitForResultsList(commands, _bitNodes[instrumentName]); } else { _logger.Error($"Unable to locate BIT node {instrumentName}"); return null; } } /// /// Runs BIT command and returns the results as a list /// /// /// public async Task> RunBITWaitForResultsListAsync(List commands) { if (_bitNodes.Any()) { return await RunBITWaitForResultsListAsync(commands, _bitNodes.First().Value); } else { _logger.Error("Unable to locate BIT node. No nodes defined"); return null; } } /// /// Runs BIT command for specific instrument and returns the results as a list /// /// /// /// public async Task> RunBITWaitForResultsListAsync(string instrumentName, List commands) { if (_bitNodes.ContainsKey(instrumentName)) { return await RunBITWaitForResultsListAsync(commands, _bitNodes[instrumentName]); } else { _logger.Error($"Unable to locate BIT node {instrumentName}"); return null; } } /// /// asynchronously runs BIT command and waits for multiple results including parametric messages /// /// BIT instrument to run command /// main command /// timeout value in ms for the main response /// optional parameters for the main command /// list of additional response messages with their respective timeouts to wait for /// parametric timeout value in ms /// nack response id when provided will be expected as a possible response for either main response or parametric response /// /// public async Task> RunBITWaitForResultsAsync( string instrumentName, string bitCommandId, int retries, uint timeout, IEnumerable> commandParams = null, List parametricResponseIds = null, uint timeout2 = 0, string errorMessageId = null) { IBit bitGenSoftManager = _bitNodes[instrumentName] ?? throw new Exception("BitGenSoftManager is null. Unable to perform operation."); BITCommand command = BuildBITCommand(bitCommandId, retries, timeout, commandParams, parametricResponseIds, timeout2, errorMessageId); return await RunBITWaitForResultsListAsync(new List { command }, bitGenSoftManager); } /// /// keep reading any of the messages from the list /// /// /// /// /// /// public async Task KeepReadingBitResultsListAsync(string instrumentName, List parametricResponseIds, CancellationToken token) { IBit bitNode = _bitNodes[instrumentName] ?? throw new Exception("BitGenSoftManager is null. Unable to perform operation."); await KeepReadingBitResultsListAsync(parametricResponseIds, token, bitNode); } /// /// keep reading any of the messages from the list from the first node /// /// /// /// /// public async Task KeepReadingBitResultsListAsync(List parametricResponseIds, CancellationToken token) { IBit bitNode = _bitNodes.First().Value; await KeepReadingBitResultsListAsync(parametricResponseIds, token, bitNode); } /// /// looking for a any of the messages from the list /// /// /// /// /// /// public async Task> GetBitResultsListAsync(string instrumentName, List parametricResponseIds, int timeOut) { IBit bitNode = _bitNodes[instrumentName] ?? throw new Exception("BitGenSoftManager is null. Unable to perform operation."); return await GetBitResultsListAsync(parametricResponseIds, timeOut, bitNode); } /// /// looking for a any of the messages from the list on the first node /// /// /// /// /// public async Task> GetBitResultsListAsync(List parametricResponseIds, int timeOut) { IBit bitNode = _bitNodes.First().Value; return await GetBitResultsListAsync(parametricResponseIds, timeOut, bitNode); } /// /// look for results in the node until either result was found or canceled /// /// /// /// public async Task GetBITResultsAsync(CancellationToken token, string responseId) { IBit bitNode = _bitNodes.First().Value; return await GetBITResultsAsync(token, responseId, bitNode); } /// /// look for results in the node until either result was found or canceled /// /// /// /// /// public async Task GetBITResultsAsync(string instrumentName, CancellationToken token, string responseId) { IBit bitNode = _bitNodes[instrumentName] ?? throw new Exception("BitGenSoftManager is null. Unable to perform operation."); return await GetBITResultsAsync(token, responseId, bitNode); } /// /// look for results in either node /// /// /// public BitTestResults GetBITResults(string messageId) { _logger.Trace($"Getting BIT result for: {messageId}."); if (_bitNodes.Any()) { IBit bitNode = _bitNodes.First().Value; return bitNode.GetBITResults(messageId); } else return null; } /// /// look for results in either node /// /// /// /// public BitTestResults GetBITResults(string instrumentName, string messageId) { _logger.Trace($"{instrumentName} Getting BIT result for: {messageId}."); return _bitNodes.ContainsKey(instrumentName) ? _bitNodes[instrumentName].GetBITResults(messageId) : null; } /// /// opens a folder parse XML files for BITCommand definitions and returns a list /// /// /// public Dictionary> GetBITCommands(string directoryPath) { Dictionary> bitCommands = new Dictionary>(); try { string[] xmlFiles; if (directoryPath.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase)) { xmlFiles = new string[] {directoryPath}; } else { // Get all XML files recursively in the specified directory xmlFiles = Directory.GetFiles(directoryPath, "*.xml", SearchOption.TopDirectoryOnly); } foreach (string xmlFile in xmlFiles) { ConfigurationFile configFile = new ConfigurationFile(xmlFile); List sections = configFile.ReadAllSections(); foreach (string section in sections) { List commands = configFile.ReadList(section, "BIT_CMD"); if(bitCommands.ContainsKey(section)) { _logger.Warn($"Found a duplicate BIT configuration section {section}, overwriting from {xmlFile} file."); } else { _logger.Trace($"Adding {section} commands from {xmlFile} file"); } bitCommands[section] = commands; } } } catch (Exception ex) { _logger.Error(ex, $"Error reading BIT Command definitions in {directoryPath}"); throw; } return bitCommands; } #region Private functions /// /// will keep pumping messages from the receiving queue until canceled /// /// /// /// /// private async Task KeepReadingBitResultsListAsync(List parametricResponseIds, CancellationToken token, IBit bitNode) { _logger.Trace($"{bitNode.Name} Start continuously reading these messages: {string.Join(", ", parametricResponseIds)}."); Dictionary score = new Dictionary(); do { foreach (var responseId in parametricResponseIds) { BitTestResults bitTestresults = await GetBITResultsAsync(token, responseId, bitNode); if (bitTestresults != null && !string.IsNullOrEmpty(bitTestresults.Label)) { if(uint.TryParse(bitTestresults.Label, out uint label)) { if(!score.ContainsKey(label)) { score.Add(label, 1); } else { score[label]++; } } } } //await Task.Delay(_checkForMessageIntervalMs); } while (!token.IsCancellationRequested); foreach (var item in score) { _logger.Trace($"{bitNode.Name} Dequeued the total of {item.Value} BIT messages for 0x{item.Key:X} label"); } } /// /// /// /// /// /// /// private async Task> GetBitResultsListAsync(List parametricResponseIds, int timeOut, IBit node) { _logger.Trace($"{node.Name} Start continuously getting BIT results for these messages: {string.Join(", ", parametricResponseIds)}."); List results = new List(); CancellationTokenSource tokenSource = new CancellationTokenSource(); List> responseTasks = new List>(); foreach (var responseId in parametricResponseIds) { responseTasks.Add(GetBITResultsAsync(tokenSource.Token, responseId, node)); } Task timeoutTask = Task.Delay(timeOut); var completedTask = await Task.WhenAny(responseTasks.Concat(new[] { timeoutTask })); tokenSource.Cancel(); tokenSource.Dispose(); if (completedTask == timeoutTask) { _logger.Warn($"Timed out after {timeOut} ms while waiting on parametrized response message(s) {string.Join(", ", parametricResponseIds)}"); } else { var completedResults = responseTasks.Where(t => t.Status == TaskStatus.RanToCompletion && t.Result != null).Select(t => t.Result); results.AddRange(completedResults); } _logger.Trace($"{node.Name} Completed getting BIT results for these messages: {string.Join(", ", parametricResponseIds)}, found {results?.Count} results"); return results; } /// /// look for results in the node until either result was found or canceled /// /// /// /// /// private async Task GetBITResultsAsync(CancellationToken token, string responseId, IBit node) { //_logger.Trace($"{node.Name} Start waiting for BIT message: {responseId}."); BitTestResults bitTestResults = null; do { await Task.Delay(_checkForMessageIntervalMs); try { bitTestResults = node.GetBITResults(responseId); } catch (Exception ex) { _logger.Error(ex, ex.Message); break; } } while (bitTestResults == null && !token.IsCancellationRequested); //_logger.Trace($"{node.Name} Done waiting for BIT message: {responseId}."); return bitTestResults; } /// /// builds a command from parameters /// /// /// /// /// /// /// /// /// private static BITCommand BuildBITCommand(string bitCommandId, int retries, uint timeout, IEnumerable> commandParams = null, List parametricResponseIds = null, uint timeout2 = 0, string errorMessageId = null) { BITCommand bitCommand = new BITCommand { Command = bitCommandId, CommandTimeout = timeout, CommandParameters = new List(), CommandResponses = new List(), ParametricResponse = retries.ToString(), ParametricTimeout = timeout2 }; if (commandParams != null) { foreach (var commandParameter in commandParams) { bitCommand.CommandParameters.Add(new BITParameter { Key = commandParameter.Key, Value = commandParameter.Value, ParameterType = ParameterType.U }); } } if(parametricResponseIds != null) { foreach (var response in parametricResponseIds) { bitCommand.CommandResponses.Add(new BITResponseMsg { Name = response, ResponseType = ResponseMessageType.P }); } } if(!string.IsNullOrEmpty(errorMessageId)) { bitCommand.CommandResponses.Add(new BITResponseMsg { Name = errorMessageId, ResponseType = ResponseMessageType.E }); } return bitCommand; } /// /// /// /// /// /// private List RunBITWaitForResultsList(List commands, IBit node) { List results = new List(); foreach (var command in commands) { string bitCommand = command.Command; string bitResponseId = ResponseGroupForFirstBITCall(command); uint timeout = command.CommandTimeout; uint timeout2 = command.ParametricTimeout; string strParamerticResponse = command.ParametricResponse; int retries = 0; if (int.TryParse(strParamerticResponse, out int tmpretries)) { retries = tmpretries; } List> commandParams = ConvertCommandParameters(command.CommandParameters); int runIndex = 0; do { runIndex++; try { var result = node.RunBITWaitForResults(bitCommand, bitResponseId, timeout, commandParams); results.Add(result); } catch (BitTimeoutException) { _logger.Warn($"Timeout after {timeout} ms on BIT command {bitCommand} waiting for result {bitResponseId}, retry number {runIndex}"); if (runIndex == retries) { throw; } } } while (results == null && runIndex <= retries); var responseGroup = ResponseGroupListForParameterBITCall(command); if (responseGroup == null || !responseGroup.Any()) { continue; } CancellationTokenSource cts = new CancellationTokenSource(); ParallelOptions options = new ParallelOptions { CancellationToken = cts.Token, MaxDegreeOfParallelism = Environment.ProcessorCount }; try { Parallel.ForEach(responseGroup, options, item => { var totalWaitTimeMs = _checkForMessageIntervalMs; BitTestResults parametricresults; do { parametricresults = node.GetBITResults(item); if (parametricresults != null) { break; } else { Thread.Sleep(_checkForMessageIntervalMs); totalWaitTimeMs += _checkForMessageIntervalMs; } } while (totalWaitTimeMs < timeout2 && !cts.IsCancellationRequested); if (parametricresults == null && !cts.IsCancellationRequested) { _logger.Warn($"Timed out while waiting on parametrized response message(s) for {command.Command} command"); } if (parametricresults != null) { // replace earlier results (confirmation message) with parametric results results.Add(parametricresults); cts.Cancel(); } }); } catch (OperationCanceledException) { _logger.Trace($"Done waiting on parametrized response message(s) for {command.Command} command"); } finally { cts.Dispose(); } } return results; } /// /// runs bit command and then /// /// /// /// private async Task> RunBITWaitForResultsListAsync(List commands, IBit node) { List results = new List(); foreach (var command in commands) { string bitCommand = command.Command; string bitResponseId = ResponseGroupForFirstBITCall(command); uint timeout = command.CommandTimeout; uint timeout2 = command.ParametricTimeout; string strParamerticResponse = command.ParametricResponse; int retries = 0; if (int.TryParse(strParamerticResponse, out int tmpretries)) { retries = tmpretries; } List> commandParams = ConvertCommandParameters(command.CommandParameters); int runIndex = 0; do { runIndex++; try { var result = await Task.Run(() => node.RunBITWaitForResults(bitCommand, bitResponseId, timeout, commandParams)); results.Add(result); } catch (BitTimeoutException) { _logger.Warn($"Timeout after {timeout} ms on BIT command {bitCommand} waiting for result {bitResponseId}, retry number {runIndex}"); if (runIndex == retries) { throw; } } } while (results == null && runIndex <= retries); var responseGroup = ResponseGroupListForParameterBITCall(command); if (responseGroup == null || !responseGroup.Any()) { continue; } var paramResults = await GetBitResultsListAsync(responseGroup, (int)timeout2, node); if (paramResults != null) { results.AddRange(paramResults); } } return results; } /// /// for the initial BIT message expect to find R (main Response) /// if any responses marked as E (error) add it to the list as alternative expected response, separated by comma /// /// /// private static string ResponseGroupForFirstBITCall(BITCommand command) { StringBuilder resp = new StringBuilder(); var tempMainResponseGroup = command.CommandResponses.Where(m => m.ResponseType != ResponseMessageType.E).ToList(); if (tempMainResponseGroup != null) { // check for Rs first and if no messages marked with Rs than check for Us var mainResponse = tempMainResponseGroup.FirstOrDefault(m => m.ResponseType == ResponseMessageType.R); if (mainResponse != null) { resp.Append(mainResponse.Name); } } // append all error message ids foreach (var item in command.CommandResponses.Where(m => m.ResponseType == ResponseMessageType.E)) { resp.Append(','); resp.Append(item.Name); } return resp.ToString(); } /// /// for subsequent parameter response look for either P (parameterized) or U (undefined) /// if any responses marked as E (error) add it to the list as alternative expected response /// /// /// private static List ResponseGroupListForParameterBITCall(BITCommand command) { List resp = new List(); bool parameterizedRespExpected = false; var tempMainResponseGroup = command.CommandResponses.Where(m => m.ResponseType != ResponseMessageType.E).ToList(); if (tempMainResponseGroup != null) { // check for Rs first and if no messages marked with Rs than check for Us var mainResponse = tempMainResponseGroup.FirstOrDefault(m => m.ResponseType == ResponseMessageType.P || m.ResponseType == ResponseMessageType.U); if (mainResponse != null) { resp.Add(mainResponse.Name); parameterizedRespExpected = true; } } if (parameterizedRespExpected) { // append all error message ids foreach (var item in command.CommandResponses.Where(m => m.ResponseType == ResponseMessageType.E)) { resp.Add(item.Name); } } return resp; } private static List> ConvertCommandParameters(List @in) { List> parameters = new List>(); foreach (var parameter in @in) { parameters.Add(new KeyValuePair(parameter.Key, parameter.Value)); } return parameters; } #endregion } }