feat(worker): implement 6 MXAccess COM commands in executor
Wire up the previously-unimplemented Suspend, Activate, AuthenticateUser, ArchestrAUserToId, AddBufferedItem, and SetBufferedUpdateInterval command kinds in MxAccessCommandExecutor. These are real COM calls and run on the STA via the executor. - IMxAccessServer gains the 6 methods; MxAccessComServer routes them to the right interface version (Suspend/Activate -> ILMXProxyServer4 out MxStatus, AuthenticateUser -> base ILMXProxyServer, ArchestrAUserToId -> ILMXProxyServer2, AddBufferedItem/SetBufferedUpdateInterval -> ILMXProxyServer5). - Suspend/Activate surface the native MxStatus, converted to MxStatusProxy via the existing MxStatusProxyConverter. - AuthenticateUser hands the credential straight to MXAccess and never logs it; native HResult failures propagate via the dispatcher. - MxAccessSession gains matching pass-throughs; AddBufferedItem registers the item handle in the handle registry. - Unit tests (fake IMxAccessServer / fake COM object) cover each arm plus a password-non-leak assertion; existing IMxAccessServer fakes updated. No proto changes (all request/reply messages already exist).
This commit is contained in:
@@ -57,6 +57,68 @@ public interface IMxAccessServer
|
||||
int serverHandle,
|
||||
int itemHandle);
|
||||
|
||||
/// <summary>Suspends data acquisition for an advised item (ILMXProxyServer4).</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemHandle">Item handle to suspend.</param>
|
||||
/// <returns>
|
||||
/// The native MXAccess <c>MxStatus</c> value (boxed) produced by the call.
|
||||
/// Callers convert it to a protobuf <c>MxStatusProxy</c> via the worker's
|
||||
/// status converter; the underlying type is reflected over, not cast.
|
||||
/// </returns>
|
||||
object Suspend(
|
||||
int serverHandle,
|
||||
int itemHandle);
|
||||
|
||||
/// <summary>Reactivates data acquisition for a suspended item (ILMXProxyServer4).</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemHandle">Item handle to activate.</param>
|
||||
/// <returns>
|
||||
/// The native MXAccess <c>MxStatus</c> value (boxed) produced by the call.
|
||||
/// Callers convert it to a protobuf <c>MxStatusProxy</c> via the worker's
|
||||
/// status converter; the underlying type is reflected over, not cast.
|
||||
/// </returns>
|
||||
object Activate(
|
||||
int serverHandle,
|
||||
int itemHandle);
|
||||
|
||||
/// <summary>Authenticates an MXAccess user and returns its user id (base ILMXProxyServer).</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="verifyUser">MXAccess user name to authenticate.</param>
|
||||
/// <param name="verifyUserPassword">
|
||||
/// Raw MXAccess credential. Implementations must keep this value out of
|
||||
/// logs, metrics, command lines, and diagnostics.
|
||||
/// </param>
|
||||
/// <returns>The MXAccess user id for the authenticated user.</returns>
|
||||
int AuthenticateUser(
|
||||
int serverHandle,
|
||||
string verifyUser,
|
||||
string verifyUserPassword);
|
||||
|
||||
/// <summary>Resolves an ArchestrA user GUID to an MXAccess user id (ILMXProxyServer2).</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="userIdGuid">ArchestrA user GUID to resolve.</param>
|
||||
/// <returns>The MXAccess user id for the resolved user.</returns>
|
||||
int ArchestrAUserToId(
|
||||
int serverHandle,
|
||||
string userIdGuid);
|
||||
|
||||
/// <summary>Adds a buffered item to a server and returns an item handle (ILMXProxyServer5).</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemDefinition">Item definition string.</param>
|
||||
/// <param name="itemContext">Item context string.</param>
|
||||
/// <returns>Item handle for the added buffered item.</returns>
|
||||
int AddBufferedItem(
|
||||
int serverHandle,
|
||||
string itemDefinition,
|
||||
string itemContext);
|
||||
|
||||
/// <summary>Sets the buffered-update interval for a server (ILMXProxyServer5).</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="updateIntervalMilliseconds">Buffered update interval in milliseconds.</param>
|
||||
void SetBufferedUpdateInterval(
|
||||
int serverHandle,
|
||||
int updateIntervalMilliseconds);
|
||||
|
||||
/// <summary>Writes a value to an item.</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemHandle">Item handle to write to.</param>
|
||||
|
||||
@@ -140,6 +140,89 @@ public sealed class MxAccessComServer : IMxAccessServer
|
||||
AsProxyServer4().AdviseSupervisory(serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Suspend(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
{
|
||||
if (mxAccessComObject is IMxAccessServer typedFake)
|
||||
{
|
||||
return typedFake.Suspend(serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
AsProxyServer4().Suspend(serverHandle, itemHandle, out MxStatus status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Activate(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
{
|
||||
if (mxAccessComObject is IMxAccessServer typedFake)
|
||||
{
|
||||
return typedFake.Activate(serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
AsProxyServer4().Activate(serverHandle, itemHandle, out MxStatus status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AuthenticateUser(
|
||||
int serverHandle,
|
||||
string verifyUser,
|
||||
string verifyUserPassword)
|
||||
{
|
||||
if (mxAccessComObject is IMxAccessServer typedFake)
|
||||
{
|
||||
return typedFake.AuthenticateUser(serverHandle, verifyUser, verifyUserPassword);
|
||||
}
|
||||
|
||||
return AsProxyServer().AuthenticateUser(serverHandle, verifyUser, verifyUserPassword);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int ArchestrAUserToId(
|
||||
int serverHandle,
|
||||
string userIdGuid)
|
||||
{
|
||||
if (mxAccessComObject is IMxAccessServer typedFake)
|
||||
{
|
||||
return typedFake.ArchestrAUserToId(serverHandle, userIdGuid);
|
||||
}
|
||||
|
||||
return AsProxyServer2().ArchestrAUserToId(serverHandle, userIdGuid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AddBufferedItem(
|
||||
int serverHandle,
|
||||
string itemDefinition,
|
||||
string itemContext)
|
||||
{
|
||||
if (mxAccessComObject is IMxAccessServer typedFake)
|
||||
{
|
||||
return typedFake.AddBufferedItem(serverHandle, itemDefinition, itemContext);
|
||||
}
|
||||
|
||||
return AsProxyServer5().AddBufferedItem(serverHandle, itemDefinition, itemContext);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetBufferedUpdateInterval(
|
||||
int serverHandle,
|
||||
int updateIntervalMilliseconds)
|
||||
{
|
||||
if (mxAccessComObject is IMxAccessServer typedFake)
|
||||
{
|
||||
typedFake.SetBufferedUpdateInterval(serverHandle, updateIntervalMilliseconds);
|
||||
return;
|
||||
}
|
||||
|
||||
AsProxyServer5().SetBufferedUpdateInterval(serverHandle, updateIntervalMilliseconds);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Write(
|
||||
int serverHandle,
|
||||
@@ -216,6 +299,14 @@ public sealed class MxAccessComServer : IMxAccessServer
|
||||
+ $"{nameof(ILMXProxyServer)} or {nameof(IMxAccessServer)}.");
|
||||
}
|
||||
|
||||
private ILMXProxyServer2 AsProxyServer2()
|
||||
{
|
||||
return mxAccessComObject as ILMXProxyServer2
|
||||
?? throw new InvalidOperationException(
|
||||
$"MXAccess COM object of type '{mxAccessComObject.GetType().FullName}' does not implement "
|
||||
+ $"{nameof(ILMXProxyServer2)} or {nameof(IMxAccessServer)}.");
|
||||
}
|
||||
|
||||
private ILMXProxyServer3 AsProxyServer3()
|
||||
{
|
||||
return mxAccessComObject as ILMXProxyServer3
|
||||
@@ -231,4 +322,12 @@ public sealed class MxAccessComServer : IMxAccessServer
|
||||
$"MXAccess COM object of type '{mxAccessComObject.GetType().FullName}' does not implement "
|
||||
+ $"{nameof(ILMXProxyServer4)} or {nameof(IMxAccessServer)}.");
|
||||
}
|
||||
|
||||
private ILMXProxyServer5 AsProxyServer5()
|
||||
{
|
||||
return mxAccessComObject as ILMXProxyServer5
|
||||
?? throw new InvalidOperationException(
|
||||
$"MXAccess COM object of type '{mxAccessComObject.GetType().FullName}' does not implement "
|
||||
+ $"{nameof(ILMXProxyServer5)} or {nameof(IMxAccessServer)}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
|
||||
private readonly MxAccessSession session;
|
||||
private readonly VariantConverter variantConverter;
|
||||
private readonly MxStatusProxyConverter statusProxyConverter;
|
||||
private readonly IAlarmCommandHandler? alarmCommandHandler;
|
||||
private readonly Action pumpStep;
|
||||
|
||||
@@ -78,6 +79,7 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
{
|
||||
this.session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
this.variantConverter = variantConverter ?? throw new ArgumentNullException(nameof(variantConverter));
|
||||
this.statusProxyConverter = new MxStatusProxyConverter();
|
||||
this.alarmCommandHandler = alarmCommandHandler;
|
||||
this.pumpStep = pumpStep ?? (static () => { });
|
||||
}
|
||||
@@ -104,6 +106,12 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
MxCommandKind.Advise => ExecuteAdvise(command),
|
||||
MxCommandKind.UnAdvise => ExecuteUnAdvise(command),
|
||||
MxCommandKind.AdviseSupervisory => ExecuteAdviseSupervisory(command),
|
||||
MxCommandKind.Suspend => ExecuteSuspend(command),
|
||||
MxCommandKind.Activate => ExecuteActivate(command),
|
||||
MxCommandKind.AuthenticateUser => ExecuteAuthenticateUser(command),
|
||||
MxCommandKind.ArchestraUserToId => ExecuteArchestrAUserToId(command),
|
||||
MxCommandKind.AddBufferedItem => ExecuteAddBufferedItem(command),
|
||||
MxCommandKind.SetBufferedUpdateInterval => ExecuteSetBufferedUpdateInterval(command),
|
||||
MxCommandKind.Write => ExecuteWrite(command),
|
||||
MxCommandKind.Write2 => ExecuteWrite2(command),
|
||||
MxCommandKind.WriteSecured => ExecuteWriteSecured(command),
|
||||
@@ -262,6 +270,134 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteSuspend(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.Suspend)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Suspend command payload is required.");
|
||||
}
|
||||
|
||||
SuspendCommand suspendCommand = command.Command.Suspend;
|
||||
object nativeStatus = session.Suspend(
|
||||
suspendCommand.ServerHandle,
|
||||
suspendCommand.ItemHandle);
|
||||
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
reply.Suspend = new SuspendReply
|
||||
{
|
||||
Status = statusProxyConverter.Convert(nativeStatus),
|
||||
};
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteActivate(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.Activate)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Activate command payload is required.");
|
||||
}
|
||||
|
||||
ActivateCommand activateCommand = command.Command.Activate;
|
||||
object nativeStatus = session.Activate(
|
||||
activateCommand.ServerHandle,
|
||||
activateCommand.ItemHandle);
|
||||
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
reply.Activate = new ActivateReply
|
||||
{
|
||||
Status = statusProxyConverter.Convert(nativeStatus),
|
||||
};
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAuthenticateUser(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AuthenticateUser)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "AuthenticateUser command payload is required.");
|
||||
}
|
||||
|
||||
AuthenticateUserCommand authenticateUserCommand = command.Command.AuthenticateUser;
|
||||
|
||||
// The credential (verify_user_password) is passed straight to MXAccess
|
||||
// and is never written to logs, diagnostics, or the reply. MXAccess is
|
||||
// allowed to fail authentication; the native HResult is surfaced by the
|
||||
// dispatcher's exception path.
|
||||
int userId = session.AuthenticateUser(
|
||||
authenticateUserCommand.ServerHandle,
|
||||
authenticateUserCommand.VerifyUser,
|
||||
authenticateUserCommand.VerifyUserPassword);
|
||||
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
reply.AuthenticateUser = new AuthenticateUserReply
|
||||
{
|
||||
UserId = userId,
|
||||
};
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteArchestrAUserToId(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.ArchestraUserToId)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "ArchestrAUserToId command payload is required.");
|
||||
}
|
||||
|
||||
ArchestrAUserToIdCommand archestrAUserToIdCommand = command.Command.ArchestraUserToId;
|
||||
int userId = session.ArchestrAUserToId(
|
||||
archestrAUserToIdCommand.ServerHandle,
|
||||
archestrAUserToIdCommand.UserIdGuid);
|
||||
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
reply.ArchestraUserToId = new ArchestrAUserToIdReply
|
||||
{
|
||||
UserId = userId,
|
||||
};
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAddBufferedItem(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AddBufferedItem)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "AddBufferedItem command payload is required.");
|
||||
}
|
||||
|
||||
AddBufferedItemCommand addBufferedItemCommand = command.Command.AddBufferedItem;
|
||||
int itemHandle = session.AddBufferedItem(
|
||||
addBufferedItemCommand.ServerHandle,
|
||||
addBufferedItemCommand.ItemDefinition,
|
||||
addBufferedItemCommand.ItemContext);
|
||||
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
reply.ReturnValue = variantConverter.Convert(itemHandle);
|
||||
reply.AddBufferedItem = new AddBufferedItemReply
|
||||
{
|
||||
ItemHandle = itemHandle,
|
||||
};
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteSetBufferedUpdateInterval(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.SetBufferedUpdateInterval)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "SetBufferedUpdateInterval command payload is required.");
|
||||
}
|
||||
|
||||
SetBufferedUpdateIntervalCommand setBufferedUpdateIntervalCommand = command.Command.SetBufferedUpdateInterval;
|
||||
session.SetBufferedUpdateInterval(
|
||||
setBufferedUpdateIntervalCommand.ServerHandle,
|
||||
setBufferedUpdateIntervalCommand.UpdateIntervalMilliseconds);
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWrite(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.Write)
|
||||
|
||||
@@ -300,6 +300,94 @@ public sealed class MxAccessSession : IDisposable
|
||||
MxAccessAdviceKind.Supervisory);
|
||||
}
|
||||
|
||||
/// <summary>Suspends data acquisition for an advised item and returns the native MXAccess status.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <returns>The boxed native MXAccess status produced by the call.</returns>
|
||||
public object Suspend(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return mxAccessServer.Suspend(serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
/// <summary>Reactivates data acquisition for a suspended item and returns the native MXAccess status.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <returns>The boxed native MXAccess status produced by the call.</returns>
|
||||
public object Activate(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return mxAccessServer.Activate(serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
/// <summary>Authenticates an MXAccess user and returns its user id.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="verifyUser">MXAccess user name to authenticate.</param>
|
||||
/// <param name="verifyUserPassword">Raw MXAccess credential; never logged.</param>
|
||||
/// <returns>The MXAccess user id for the authenticated user.</returns>
|
||||
public int AuthenticateUser(
|
||||
int serverHandle,
|
||||
string verifyUser,
|
||||
string verifyUserPassword)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return mxAccessServer.AuthenticateUser(serverHandle, verifyUser, verifyUserPassword);
|
||||
}
|
||||
|
||||
/// <summary>Resolves an ArchestrA user GUID to an MXAccess user id.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="userIdGuid">ArchestrA user GUID to resolve.</param>
|
||||
/// <returns>The MXAccess user id for the resolved user.</returns>
|
||||
public int ArchestrAUserToId(
|
||||
int serverHandle,
|
||||
string userIdGuid)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return mxAccessServer.ArchestrAUserToId(serverHandle, userIdGuid);
|
||||
}
|
||||
|
||||
/// <summary>Adds a buffered item to an MXAccess server and returns the item handle.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemDefinition">Definition or address of the item to add.</param>
|
||||
/// <param name="itemContext">Context string for the item.</param>
|
||||
public int AddBufferedItem(
|
||||
int serverHandle,
|
||||
string itemDefinition,
|
||||
string itemContext)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
int itemHandle = mxAccessServer.AddBufferedItem(serverHandle, itemDefinition, itemContext);
|
||||
handleRegistry.RegisterItemHandle(
|
||||
serverHandle,
|
||||
itemHandle,
|
||||
itemDefinition,
|
||||
itemContext,
|
||||
hasItemContext: true);
|
||||
|
||||
return itemHandle;
|
||||
}
|
||||
|
||||
/// <summary>Sets the buffered-update interval for an MXAccess server.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="updateIntervalMilliseconds">Buffered update interval in milliseconds.</param>
|
||||
public void SetBufferedUpdateInterval(
|
||||
int serverHandle,
|
||||
int updateIntervalMilliseconds)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
mxAccessServer.SetBufferedUpdateInterval(serverHandle, updateIntervalMilliseconds);
|
||||
}
|
||||
|
||||
/// <summary>Writes a value to an item.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
|
||||
Reference in New Issue
Block a user