using System; using System.Collections.Generic; using System.Globalization; using System.Reflection; using MxGateway.Contracts.Proto; namespace MxGateway.Worker.Conversion; /// Converts MXAccess MXSTATUS_PROXY COM objects to protobuf MxStatusProxy messages. public sealed class MxStatusProxyConverter { /// Converts a single status object to a protobuf message, reflecting all fields and diagnostics. /// COM status object to convert. public MxStatusProxy Convert(object status) { if (status is null) { throw new ArgumentNullException(nameof(status)); } Type statusType = status.GetType(); int success = ReadInt32Field(status, statusType, "success"); int rawCategory = ReadInt32Field(status, statusType, "category"); int rawDetectedBy = ReadInt32Field(status, statusType, "detectedBy"); int detail = ReadInt32Field(status, statusType, "detail"); return new MxStatusProxy { Success = success, Category = MapCategory(rawCategory), DetectedBy = MapSource(rawDetectedBy), Detail = detail, RawCategory = rawCategory, RawDetectedBy = rawDetectedBy, DiagnosticText = MxStatusDetailText.Lookup(detail), }; } /// Converts an array of status objects, handling nulls gracefully. /// Array of COM status objects; null returns empty list. public IReadOnlyList ConvertMany(Array? statuses) { if (statuses is null) { return Array.Empty(); } List converted = new(statuses.Length); foreach (object? status in statuses) { if (status is null) { converted.Add(new MxStatusProxy { Category = MxStatusCategory.Unknown, DetectedBy = MxStatusSource.Unknown, DiagnosticText = "Null MXSTATUS_PROXY entry.", }); continue; } converted.Add(Convert(status)); } return converted; } /// Preserves completion-only status bytes as a diagnostic hex string since they cannot be unpacked. /// Status bytes to encode as hex string. public string PreserveCompletionOnlyStatusBytes(byte[] statusBytes) { if (statusBytes is null) { throw new ArgumentNullException(nameof(statusBytes)); } return $"completion_only_status_hex={BitConverter.ToString(statusBytes).Replace("-", string.Empty)}"; } private static int ReadInt32Field( object value, Type valueType, string fieldName) { FieldInfo? field = valueType.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public); if (field is null) { throw new MxStatusConversionException( $"Status object type '{valueType.FullName}' does not expose required field '{fieldName}'."); } object? fieldValue = field.GetValue(value); if (fieldValue is null) { throw new MxStatusConversionException( $"Status object field '{fieldName}' on type '{valueType.FullName}' is null."); } return System.Convert.ToInt32(fieldValue, CultureInfo.InvariantCulture); } private static MxStatusCategory MapCategory(int rawCategory) { return rawCategory switch { -1 => MxStatusCategory.Unknown, 0 => MxStatusCategory.Ok, 1 => MxStatusCategory.Pending, 2 => MxStatusCategory.Warning, 3 => MxStatusCategory.CommunicationError, 4 => MxStatusCategory.ConfigurationError, 5 => MxStatusCategory.OperationalError, 6 => MxStatusCategory.SecurityError, 7 => MxStatusCategory.SoftwareError, 8 => MxStatusCategory.OtherError, _ => MxStatusCategory.Unknown, }; } private static MxStatusSource MapSource(int rawDetectedBy) { return rawDetectedBy switch { -1 => MxStatusSource.Unknown, 0 => MxStatusSource.RequestingLmx, 1 => MxStatusSource.RespondingLmx, 2 => MxStatusSource.RequestingNmx, 3 => MxStatusSource.RespondingNmx, 4 => MxStatusSource.RequestingAutomationObject, 5 => MxStatusSource.RespondingAutomationObject, _ => MxStatusSource.Unknown, }; } }