Implement MXAccess write commands in the worker
The .proto contract and MxCommandKind already defined Write, Write2,
WriteSecured, and WriteSecured2, but the worker's MxAccessCommandExecutor
had no case for any of them — every write kind fell through to
CreateInvalidRequestReply ("Unsupported MXAccess command kind Write").
Implement all four:
- VariantConverter.ConvertToComValue projects an MxValue into a
COM-marshalable object (scalars, arrays, null) — the inverse of the
existing COM-to-MxValue projection.
- IMxAccessServer / MxAccessComServer gain Write/Write2/WriteSecured/
WriteSecured2, routed to ILMXProxyServer / ILMXProxyServer4.
- MxAccessSession and MxAccessCommandExecutor add the four write paths,
following the existing ExecuteAdvise pattern; the reply is a plain OK
reply and the outcome surfaces later as an OnWriteComplete event.
Verified live: a Write now returns PROTOCOL_STATUS_CODE_OK and produces
an OnWriteComplete event where it previously returned InvalidRequest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using MxGateway.Contracts.Proto;
|
||||
@@ -118,6 +119,63 @@ public sealed class VariantConverter
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an <see cref="MxValue"/> into a CLR object suitable for an
|
||||
/// MXAccess COM write. The COM marshaler boxes the returned value into the
|
||||
/// matching VARIANT, so this is the inverse of <see cref="Convert(object?)"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">Protobuf value to convert.</param>
|
||||
/// <returns>A COM-marshalable value, or <see langword="null"/> for an MXAccess null.</returns>
|
||||
public object? ConvertToComValue(MxValue value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (value.IsNull)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return value.KindCase switch
|
||||
{
|
||||
MxValue.KindOneofCase.BoolValue => value.BoolValue,
|
||||
MxValue.KindOneofCase.Int32Value => value.Int32Value,
|
||||
MxValue.KindOneofCase.Int64Value => value.Int64Value,
|
||||
MxValue.KindOneofCase.FloatValue => value.FloatValue,
|
||||
MxValue.KindOneofCase.DoubleValue => value.DoubleValue,
|
||||
MxValue.KindOneofCase.StringValue => value.StringValue,
|
||||
// The COM marshaler renders a DateTime as VT_DATE; MXAccess accepts
|
||||
// it as the timestamped-write time argument.
|
||||
MxValue.KindOneofCase.TimestampValue => value.TimestampValue.ToDateTime(),
|
||||
MxValue.KindOneofCase.ArrayValue => ConvertToComArray(value.ArrayValue),
|
||||
MxValue.KindOneofCase.RawValue => throw new ArgumentException(
|
||||
"MxValue raw payloads cannot be written to MXAccess.", nameof(value)),
|
||||
_ => throw new ArgumentException(
|
||||
"MxValue has no value kind set; nothing to write.", nameof(value)),
|
||||
};
|
||||
}
|
||||
|
||||
private static Array ConvertToComArray(MxArray array)
|
||||
{
|
||||
return array.ValuesCase switch
|
||||
{
|
||||
MxArray.ValuesOneofCase.BoolValues => array.BoolValues.Values.ToArray(),
|
||||
MxArray.ValuesOneofCase.Int32Values => array.Int32Values.Values.ToArray(),
|
||||
MxArray.ValuesOneofCase.Int64Values => array.Int64Values.Values.ToArray(),
|
||||
MxArray.ValuesOneofCase.FloatValues => array.FloatValues.Values.ToArray(),
|
||||
MxArray.ValuesOneofCase.DoubleValues => array.DoubleValues.Values.ToArray(),
|
||||
MxArray.ValuesOneofCase.StringValues => array.StringValues.Values.ToArray(),
|
||||
MxArray.ValuesOneofCase.TimestampValues =>
|
||||
array.TimestampValues.Values.Select(timestamp => timestamp.ToDateTime()).ToArray(),
|
||||
MxArray.ValuesOneofCase.RawValues => throw new ArgumentException(
|
||||
"MxArray raw payloads cannot be written to MXAccess.", nameof(array)),
|
||||
_ => throw new ArgumentException(
|
||||
"MxArray has no element values set; nothing to write.", nameof(array)),
|
||||
};
|
||||
}
|
||||
|
||||
private static MxValue ConvertScalar(
|
||||
object value,
|
||||
MxDataType expectedDataType)
|
||||
|
||||
@@ -56,4 +56,56 @@ public interface IMxAccessServer
|
||||
void AdviseSupervisory(
|
||||
int serverHandle,
|
||||
int itemHandle);
|
||||
|
||||
/// <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>
|
||||
/// <param name="value">COM-marshalable value to write; <see langword="null"/> writes an MXAccess null.</param>
|
||||
/// <param name="userId">MXAccess user id (security classification) for the write.</param>
|
||||
void Write(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
object? value,
|
||||
int userId);
|
||||
|
||||
/// <summary>Writes a value with an explicit source timestamp to an item.</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemHandle">Item handle to write to.</param>
|
||||
/// <param name="value">COM-marshalable value to write; <see langword="null"/> writes an MXAccess null.</param>
|
||||
/// <param name="timestamp">COM-marshalable source timestamp for the write.</param>
|
||||
/// <param name="userId">MXAccess user id (security classification) for the write.</param>
|
||||
void Write2(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
object? value,
|
||||
object? timestamp,
|
||||
int userId);
|
||||
|
||||
/// <summary>Performs a secured/verified write to an item.</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemHandle">Item handle to write to.</param>
|
||||
/// <param name="currentUserId">MXAccess user id of the operator performing the write.</param>
|
||||
/// <param name="verifierUserId">MXAccess user id of the verifier authorizing the write.</param>
|
||||
/// <param name="value">COM-marshalable value to write; <see langword="null"/> writes an MXAccess null.</param>
|
||||
void WriteSecured(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
int currentUserId,
|
||||
int verifierUserId,
|
||||
object? value);
|
||||
|
||||
/// <summary>Performs a secured/verified write with an explicit source timestamp.</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemHandle">Item handle to write to.</param>
|
||||
/// <param name="currentUserId">MXAccess user id of the operator performing the write.</param>
|
||||
/// <param name="verifierUserId">MXAccess user id of the verifier authorizing the write.</param>
|
||||
/// <param name="value">COM-marshalable value to write; <see langword="null"/> writes an MXAccess null.</param>
|
||||
/// <param name="timestamp">COM-marshalable source timestamp for the write.</param>
|
||||
void WriteSecured2(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
int currentUserId,
|
||||
int verifierUserId,
|
||||
object? value,
|
||||
object? timestamp);
|
||||
}
|
||||
|
||||
@@ -140,6 +140,74 @@ public sealed class MxAccessComServer : IMxAccessServer
|
||||
AsProxyServer4().AdviseSupervisory(serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Write(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
object? value,
|
||||
int userId)
|
||||
{
|
||||
if (mxAccessComObject is IMxAccessServer typedFake)
|
||||
{
|
||||
typedFake.Write(serverHandle, itemHandle, value, userId);
|
||||
return;
|
||||
}
|
||||
|
||||
AsProxyServer().Write(serverHandle, itemHandle, value!, userId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Write2(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
object? value,
|
||||
object? timestamp,
|
||||
int userId)
|
||||
{
|
||||
if (mxAccessComObject is IMxAccessServer typedFake)
|
||||
{
|
||||
typedFake.Write2(serverHandle, itemHandle, value, timestamp, userId);
|
||||
return;
|
||||
}
|
||||
|
||||
AsProxyServer4().Write2(serverHandle, itemHandle, value!, timestamp!, userId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteSecured(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
int currentUserId,
|
||||
int verifierUserId,
|
||||
object? value)
|
||||
{
|
||||
if (mxAccessComObject is IMxAccessServer typedFake)
|
||||
{
|
||||
typedFake.WriteSecured(serverHandle, itemHandle, currentUserId, verifierUserId, value);
|
||||
return;
|
||||
}
|
||||
|
||||
AsProxyServer().WriteSecured(serverHandle, itemHandle, currentUserId, verifierUserId, value!);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteSecured2(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
int currentUserId,
|
||||
int verifierUserId,
|
||||
object? value,
|
||||
object? timestamp)
|
||||
{
|
||||
if (mxAccessComObject is IMxAccessServer typedFake)
|
||||
{
|
||||
typedFake.WriteSecured2(serverHandle, itemHandle, currentUserId, verifierUserId, value, timestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
AsProxyServer4().WriteSecured2(serverHandle, itemHandle, currentUserId, verifierUserId, value!, timestamp!);
|
||||
}
|
||||
|
||||
private ILMXProxyServer AsProxyServer()
|
||||
{
|
||||
return mxAccessComObject as ILMXProxyServer
|
||||
|
||||
@@ -74,6 +74,10 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
MxCommandKind.Advise => ExecuteAdvise(command),
|
||||
MxCommandKind.UnAdvise => ExecuteUnAdvise(command),
|
||||
MxCommandKind.AdviseSupervisory => ExecuteAdviseSupervisory(command),
|
||||
MxCommandKind.Write => ExecuteWrite(command),
|
||||
MxCommandKind.Write2 => ExecuteWrite2(command),
|
||||
MxCommandKind.WriteSecured => ExecuteWriteSecured(command),
|
||||
MxCommandKind.WriteSecured2 => ExecuteWriteSecured2(command),
|
||||
MxCommandKind.AddItemBulk => ExecuteAddItemBulk(command),
|
||||
MxCommandKind.AdviseItemBulk => ExecuteAdviseItemBulk(command),
|
||||
MxCommandKind.RemoveItemBulk => ExecuteRemoveItemBulk(command),
|
||||
@@ -223,6 +227,108 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWrite(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.Write)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Write command payload is required.");
|
||||
}
|
||||
|
||||
WriteCommand writeCommand = command.Command.Write;
|
||||
if (writeCommand.Value is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Write command value is required.");
|
||||
}
|
||||
|
||||
session.Write(
|
||||
writeCommand.ServerHandle,
|
||||
writeCommand.ItemHandle,
|
||||
variantConverter.ConvertToComValue(writeCommand.Value),
|
||||
writeCommand.UserId);
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWrite2(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.Write2)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Write2 command payload is required.");
|
||||
}
|
||||
|
||||
Write2Command write2Command = command.Command.Write2;
|
||||
if (write2Command.Value is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Write2 command value is required.");
|
||||
}
|
||||
|
||||
if (write2Command.TimestampValue is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Write2 command timestamp value is required.");
|
||||
}
|
||||
|
||||
session.Write2(
|
||||
write2Command.ServerHandle,
|
||||
write2Command.ItemHandle,
|
||||
variantConverter.ConvertToComValue(write2Command.Value),
|
||||
variantConverter.ConvertToComValue(write2Command.TimestampValue),
|
||||
write2Command.UserId);
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWriteSecured(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.WriteSecured)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteSecured command payload is required.");
|
||||
}
|
||||
|
||||
WriteSecuredCommand writeSecuredCommand = command.Command.WriteSecured;
|
||||
if (writeSecuredCommand.Value is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteSecured command value is required.");
|
||||
}
|
||||
|
||||
session.WriteSecured(
|
||||
writeSecuredCommand.ServerHandle,
|
||||
writeSecuredCommand.ItemHandle,
|
||||
writeSecuredCommand.CurrentUserId,
|
||||
writeSecuredCommand.VerifierUserId,
|
||||
variantConverter.ConvertToComValue(writeSecuredCommand.Value));
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWriteSecured2(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.WriteSecured2)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteSecured2 command payload is required.");
|
||||
}
|
||||
|
||||
WriteSecured2Command writeSecured2Command = command.Command.WriteSecured2;
|
||||
if (writeSecured2Command.Value is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteSecured2 command value is required.");
|
||||
}
|
||||
|
||||
if (writeSecured2Command.TimestampValue is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteSecured2 command timestamp value is required.");
|
||||
}
|
||||
|
||||
session.WriteSecured2(
|
||||
writeSecured2Command.ServerHandle,
|
||||
writeSecured2Command.ItemHandle,
|
||||
writeSecured2Command.CurrentUserId,
|
||||
writeSecured2Command.VerifierUserId,
|
||||
variantConverter.ConvertToComValue(writeSecured2Command.Value),
|
||||
variantConverter.ConvertToComValue(writeSecured2Command.TimestampValue));
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAddItemBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AddItemBulk)
|
||||
|
||||
@@ -227,6 +227,78 @@ public sealed class MxAccessSession : IDisposable
|
||||
MxAccessAdviceKind.Supervisory);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="value">COM-marshalable value to write.</param>
|
||||
/// <param name="userId">MXAccess user id (security classification) for the write.</param>
|
||||
public void Write(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
object? value,
|
||||
int userId)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
mxAccessServer.Write(serverHandle, itemHandle, value, userId);
|
||||
}
|
||||
|
||||
/// <summary>Writes a value with an explicit source timestamp to an item.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <param name="value">COM-marshalable value to write.</param>
|
||||
/// <param name="timestamp">COM-marshalable source timestamp for the write.</param>
|
||||
/// <param name="userId">MXAccess user id (security classification) for the write.</param>
|
||||
public void Write2(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
object? value,
|
||||
object? timestamp,
|
||||
int userId)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
mxAccessServer.Write2(serverHandle, itemHandle, value, timestamp, userId);
|
||||
}
|
||||
|
||||
/// <summary>Performs a secured/verified write to an item.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <param name="currentUserId">MXAccess user id of the operator performing the write.</param>
|
||||
/// <param name="verifierUserId">MXAccess user id of the verifier authorizing the write.</param>
|
||||
/// <param name="value">COM-marshalable value to write.</param>
|
||||
public void WriteSecured(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
int currentUserId,
|
||||
int verifierUserId,
|
||||
object? value)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
mxAccessServer.WriteSecured(serverHandle, itemHandle, currentUserId, verifierUserId, value);
|
||||
}
|
||||
|
||||
/// <summary>Performs a secured/verified write with an explicit source timestamp.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <param name="currentUserId">MXAccess user id of the operator performing the write.</param>
|
||||
/// <param name="verifierUserId">MXAccess user id of the verifier authorizing the write.</param>
|
||||
/// <param name="value">COM-marshalable value to write.</param>
|
||||
/// <param name="timestamp">COM-marshalable source timestamp for the write.</param>
|
||||
public void WriteSecured2(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
int currentUserId,
|
||||
int verifierUserId,
|
||||
object? value,
|
||||
object? timestamp)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
mxAccessServer.WriteSecured2(serverHandle, itemHandle, currentUserId, verifierUserId, value, timestamp);
|
||||
}
|
||||
|
||||
/// <summary>Adds multiple items in bulk, returning success/failure results.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="tagAddresses">Enumerable of item definitions to add.</param>
|
||||
|
||||
Reference in New Issue
Block a user