Issue #31: implement mxstatus proxy and hresult conversion

This commit is contained in:
Joseph Doherty
2026-04-26 17:35:30 -04:00
parent 5511609880
commit 29455fc1f6
7 changed files with 427 additions and 0 deletions
@@ -0,0 +1,22 @@
using MxGateway.Contracts.Proto;
namespace MxGateway.Worker.Conversion;
public sealed class HResultConversion
{
public HResultConversion(
int hresult,
ProtocolStatus protocolStatus,
string diagnosticMessage)
{
HResult = hresult;
ProtocolStatus = protocolStatus;
DiagnosticMessage = diagnosticMessage;
}
public int HResult { get; }
public ProtocolStatus ProtocolStatus { get; }
public string DiagnosticMessage { get; }
}
@@ -0,0 +1,48 @@
using System;
using System.Runtime.InteropServices;
using MxGateway.Contracts.Proto;
namespace MxGateway.Worker.Conversion;
public sealed class HResultConverter
{
public HResultConversion Convert(Exception exception)
{
if (exception is null)
{
throw new ArgumentNullException(nameof(exception));
}
int hresult = exception is COMException comException
? comException.ErrorCode
: exception.HResult;
return new HResultConversion(
hresult,
CreateProtocolStatus(hresult, exception),
CreateSafeDiagnosticMessage(exception));
}
public ProtocolStatus CreateProtocolStatus(
int hresult,
Exception? exception = null)
{
return new ProtocolStatus
{
Code = hresult == 0 ? ProtocolStatusCode.Ok : ProtocolStatusCode.MxaccessFailure,
Message = exception is null
? FormatHResult(hresult)
: $"{exception.GetType().Name}: {FormatHResult(hresult)}",
};
}
private static string CreateSafeDiagnosticMessage(Exception exception)
{
return $"{exception.GetType().FullName}: {FormatHResult(exception.HResult)}";
}
private static string FormatHResult(int hresult)
{
return $"HRESULT 0x{unchecked((uint)hresult):X8}";
}
}
@@ -0,0 +1,11 @@
using System;
namespace MxGateway.Worker.Conversion;
public sealed class MxStatusConversionException : Exception
{
public MxStatusConversionException(string message)
: base(message)
{
}
}
@@ -0,0 +1,54 @@
using System.Collections.Generic;
namespace MxGateway.Worker.Conversion;
internal static class MxStatusDetailText
{
private static readonly IReadOnlyDictionary<int, string> KnownDetails = new Dictionary<int, string>
{
[16] = "Request timed out",
[17] = "Platform communication error",
[18] = "Invalid platform ID",
[19] = "Invalid engine ID",
[20] = "Engine communication error",
[21] = "Invalid reference",
[22] = "No Galaxy Repository",
[23] = "Invalid object ID",
[24] = "Object signature mismatch",
[25] = "Invalid primitive ID",
[26] = "Invalid attribute ID",
[27] = "Invalid property ID",
[28] = "Index out of range",
[29] = "Data out of range",
[30] = "Incorrect data type",
[31] = "Attribute not readable",
[32] = "Attribute not writeable",
[33] = "Write access denied",
[34] = "Unknown error",
[36] = "Wrong data type",
[37] = "Wrong number of dimensions",
[38] = "Invalid index",
[39] = "Index out of order",
[40] = "Dimension does not exist",
[41] = "Conversion not supported",
[42] = "Unable to convert string",
[43] = "Overflow",
[44] = "Attribute signature mismatch",
[47] = "Nmx version mismatch",
[48] = "Nmx command not valid",
[49] = "Lmx version mismatch",
[50] = "Lmx command not valid",
[56] = "Secured Write",
[57] = "Verified Write",
[60] = "User did not have the necessary permissions to write",
[61] = "Verifier did not have the necessary permissions to verify",
[541] = "Conversion to intended data type is not supported",
[542] = "Unable to convert the input string to intended data type",
[8017] = "Object must be offscan to modify attributes that have an MxSecurityConfigure security classification",
};
public static string Lookup(int detail)
{
return KnownDetails.TryGetValue(detail, out string text) ? text : string.Empty;
}
}
@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using MxGateway.Contracts.Proto;
namespace MxGateway.Worker.Conversion;
public sealed class MxStatusProxyConverter
{
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),
};
}
public IReadOnlyList<MxStatusProxy> ConvertMany(Array? statuses)
{
if (statuses is null)
{
return Array.Empty<MxStatusProxy>();
}
List<MxStatusProxy> 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;
}
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,
};
}
}