using Google.Protobuf;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.DebugView;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Lifecycle;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.RemoteQuery;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using BoolValue = Google.Protobuf.WellKnownTypes.BoolValue;
using Duration = Google.Protobuf.WellKnownTypes.Duration;
using Int32Value = Google.Protobuf.WellKnownTypes.Int32Value;
using StringValue = Google.Protobuf.WellKnownTypes.StringValue;
using Timestamp = Google.Protobuf.WellKnownTypes.Timestamp;
namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc;
///
/// Canonical bridge between the in-process central→site command/reply records
/// (Commons Messages.*) and the wire messages of
/// Protos/site_command.proto. Every one of the 28 migrated commands and
/// all 22 reply shapes pass through here; nothing else may hand-roll the
/// translation, so the contract has exactly one definition.
///
///
///
/// Mirrors the sibling /
/// and lives in Communication for the same reason: this project owns the
/// generated wire types and already references Commons, so both the site
/// server (T1B.2) and the central transport (T1B.3) can share one implementation
/// without a project-reference cycle.
///
/// Null conventions. proto3 has no presence for scalars, so:
///
/// -
/// Nullable strings ride as plain string and the empty string
/// represents null — the convention
/// already established. It is a normalisation, not a loss: every such member
/// here is an optional error message, token, filter or id where "" and
/// null carry the same meaning. The one exception is
/// , where
/// "wait for the empty string" is a real target distinct from "no target";
/// that field uses a StringValue wrapper instead.
///
/// -
/// Nullable ints/bools use the Int32Value/BoolValue
/// wrappers, and nullable enums use a one-field message of the same
/// shape (proto3 enums have no presence and no stock wrapper exists).
///
/// -
/// Nullable collections are wrapped in a per-collection message, so
/// the null/empty distinction becomes a message-presence question. proto3
/// repeated/map alone collapses the two — precisely the silent
/// data-loss class the transport round-trip guard caught in the transport round-trip tests.
///
/// -
/// Guid? members ride as a string holding the "D" form, with
/// the empty string representing null.
/// (a non-nullable struct over a
/// ) rides as the same "D" string.
///
///
///
/// Time. DTO members declared use
/// google.protobuf.Timestamp. That normalises to UTC, which is lossless
/// under equality (it compares instants), and every
/// timestamp in this contract is documented UTC anyway. Members declared
/// (the certificate validity bounds) are coerced to
/// first, matching their …Utc names.
/// Loosely-typed object? values are the exception and keep their exact
/// kind/offset — see .
///
///
/// Enums translate through explicit switches in both directions, never by
/// ordinal, so reordering a C# enum cannot silently re-map the wire. Every wire
/// enum reserves 0 for _UNSPECIFIED; decoding an unspecified or unknown
/// value yields the documented safe default for that enum (never a throw — a
/// version-skewed peer must not be able to fault a whole command).
///
///
public static class SiteCommandDtoMapper
{
// ─────────────────────────────────────────────────────────────────────
// Group classification — the authoritative 28-command inventory in code.
// ─────────────────────────────────────────────────────────────────────
///
/// Classifies a central→site command onto the RPC group that carries it.
/// The central transport (T1B.3) uses this to pick both the RPC and the
/// deadline; the coverage tests use it as the command inventory.
///
/// The command record to classify.
/// The whose RPC carries the command.
/// The type is not part of the migrated command set.
public static SiteCommandGroup GroupOf(object command)
{
ArgumentNullException.ThrowIfNull(command);
return command switch
{
RefreshDeploymentCommand or EnableInstanceCommand or DisableInstanceCommand
or DeleteInstanceCommand or DeploymentStateQueryRequest or DeployArtifactsCommand
=> SiteCommandGroup.Lifecycle,
BrowseNodeCommand or SearchAddressSpaceCommand or ReadTagValuesCommand
or VerifyEndpointCommand or TrustServerCertCommand or ListServerCertsCommand
or RemoveServerCertCommand or WriteTagRequest
=> SiteCommandGroup.OpcUa,
EventLogQueryRequest or DebugSnapshotRequest or SubscribeDebugViewRequest
or UnsubscribeDebugViewRequest
=> SiteCommandGroup.Query,
ParkedMessageQueryRequest or ParkedMessageRetryRequest or ParkedMessageDiscardRequest
or RetryParkedOperation or DiscardParkedOperation
=> SiteCommandGroup.Parked,
RouteToCallRequest or RouteToGetAttributesRequest or RouteToSetAttributesRequest
or RouteToWaitForAttributeRequest
=> SiteCommandGroup.Route,
TriggerSiteFailover => SiteCommandGroup.Failover,
_ => throw new ArgumentException(
$"'{command.GetType().Name}' is not a migrated site command.", nameof(command))
};
}
///
/// Classifies a site→central reply onto the RPC group whose reply envelope
/// carries it — the mirror of , used by the site
/// dispatcher to pack whatever a handler answered into the right envelope.
///
/// The reply record to classify.
/// The whose reply envelope carries it.
/// The type is not a migrated reply shape.
public static SiteCommandGroup GroupOfReply(object reply)
{
ArgumentNullException.ThrowIfNull(reply);
return reply switch
{
DeploymentStatusResponse or InstanceLifecycleResponse or DeploymentStateQueryResponse
or ArtifactDeploymentResponse
=> SiteCommandGroup.Lifecycle,
BrowseNodeResult or SearchAddressSpaceResult or ReadTagValuesResult or VerifyEndpointResult
or CertTrustResult or WriteTagResponse
=> SiteCommandGroup.OpcUa,
EventLogQueryResponse or DebugViewSnapshot or UnsubscribeDebugViewAck
=> SiteCommandGroup.Query,
ParkedMessageQueryResponse or ParkedMessageRetryResponse or ParkedMessageDiscardResponse
or ParkedOperationActionAck
=> SiteCommandGroup.Parked,
RouteToCallResponse or RouteToGetAttributesResponse or RouteToSetAttributesResponse
or RouteToWaitForAttributeResponse
=> SiteCommandGroup.Route,
SiteFailoverAck => SiteCommandGroup.Failover,
_ => throw new ArgumentException(
$"'{reply.GetType().Name}' is not a migrated site reply.", nameof(reply))
};
}
// ─────────────────────────────────────────────────────────────────────
// RPC 1 — ExecuteLifecycle: envelopes
// ─────────────────────────────────────────────────────────────────────
/// Packs a lifecycle-group command into its request envelope.
/// One of the six lifecycle-group commands.
/// The populated request envelope.
/// The command does not belong to this group.
public static LifecycleRequest ToLifecycleRequest(object command)
{
ArgumentNullException.ThrowIfNull(command);
return command switch
{
RefreshDeploymentCommand c => new LifecycleRequest { RefreshDeployment = ToProto(c) },
EnableInstanceCommand c => new LifecycleRequest { EnableInstance = ToProto(c) },
DisableInstanceCommand c => new LifecycleRequest { DisableInstance = ToProto(c) },
DeleteInstanceCommand c => new LifecycleRequest { DeleteInstance = ToProto(c) },
DeploymentStateQueryRequest c => new LifecycleRequest { DeploymentStateQuery = ToProto(c) },
DeployArtifactsCommand c => new LifecycleRequest { DeployArtifacts = ToProto(c) },
_ => throw NotInGroup(command, SiteCommandGroup.Lifecycle)
};
}
/// Unpacks a lifecycle-group request envelope back to its command record.
/// The request envelope received on the wire.
/// The decoded command record.
/// The envelope carries no case this build knows.
public static object FromLifecycleRequest(LifecycleRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return request.CommandCase switch
{
LifecycleRequest.CommandOneofCase.RefreshDeployment => FromProto(request.RefreshDeployment),
LifecycleRequest.CommandOneofCase.EnableInstance => FromProto(request.EnableInstance),
LifecycleRequest.CommandOneofCase.DisableInstance => FromProto(request.DisableInstance),
LifecycleRequest.CommandOneofCase.DeleteInstance => FromProto(request.DeleteInstance),
LifecycleRequest.CommandOneofCase.DeploymentStateQuery => FromProto(request.DeploymentStateQuery),
LifecycleRequest.CommandOneofCase.DeployArtifacts => FromProto(request.DeployArtifacts),
_ => throw UnknownCase(request.CommandCase, nameof(LifecycleRequest))
};
}
/// Packs a lifecycle-group reply into its reply envelope.
/// One of the four lifecycle-group reply records.
/// The populated reply envelope.
/// The reply does not belong to this group.
public static LifecycleReply ToLifecycleReply(object reply)
{
ArgumentNullException.ThrowIfNull(reply);
return reply switch
{
DeploymentStatusResponse r => new LifecycleReply { DeploymentStatus = ToProto(r) },
InstanceLifecycleResponse r => new LifecycleReply { InstanceLifecycle = ToProto(r) },
DeploymentStateQueryResponse r => new LifecycleReply { DeploymentStateQuery = ToProto(r) },
ArtifactDeploymentResponse r => new LifecycleReply { ArtifactDeployment = ToProto(r) },
_ => throw NotInGroup(reply, SiteCommandGroup.Lifecycle)
};
}
/// Unpacks a lifecycle-group reply envelope back to its reply record.
/// The reply envelope received on the wire.
/// The decoded reply record.
/// The envelope carries no case this build knows.
public static object FromLifecycleReply(LifecycleReply reply)
{
ArgumentNullException.ThrowIfNull(reply);
return reply.ReplyCase switch
{
LifecycleReply.ReplyOneofCase.DeploymentStatus => FromProto(reply.DeploymentStatus),
LifecycleReply.ReplyOneofCase.InstanceLifecycle => FromProto(reply.InstanceLifecycle),
LifecycleReply.ReplyOneofCase.DeploymentStateQuery => FromProto(reply.DeploymentStateQuery),
LifecycleReply.ReplyOneofCase.ArtifactDeployment => FromProto(reply.ArtifactDeployment),
_ => throw UnknownCase(reply.ReplyCase, nameof(LifecycleReply))
};
}
// ── Lifecycle commands ──
/// Projects a onto the wire.
/// The command to project.
/// The wire message.
public static RefreshDeploymentCommandDto ToProto(RefreshDeploymentCommand command)
{
ArgumentNullException.ThrowIfNull(command);
return new RefreshDeploymentCommandDto
{
DeploymentId = command.DeploymentId,
InstanceUniqueName = command.InstanceUniqueName,
RevisionHash = command.RevisionHash,
DeployedBy = command.DeployedBy,
Timestamp = Ts(command.Timestamp),
CentralFetchBaseUrl = command.CentralFetchBaseUrl,
FetchToken = command.FetchToken
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded command record.
public static RefreshDeploymentCommand FromProto(RefreshDeploymentCommandDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new RefreshDeploymentCommand(
dto.DeploymentId,
dto.InstanceUniqueName,
dto.RevisionHash,
dto.DeployedBy,
Dto(dto.Timestamp),
dto.CentralFetchBaseUrl,
dto.FetchToken);
}
/// Projects an onto the wire.
/// The command to project.
/// The wire message.
public static EnableInstanceCommandDto ToProto(EnableInstanceCommand command)
{
ArgumentNullException.ThrowIfNull(command);
return new EnableInstanceCommandDto
{
CommandId = command.CommandId,
InstanceUniqueName = command.InstanceUniqueName,
Timestamp = Ts(command.Timestamp)
};
}
/// Rehydrates an from the wire.
/// The wire message to decode.
/// The decoded command record.
public static EnableInstanceCommand FromProto(EnableInstanceCommandDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new EnableInstanceCommand(dto.CommandId, dto.InstanceUniqueName, Dto(dto.Timestamp));
}
/// Projects a onto the wire.
/// The command to project.
/// The wire message.
public static DisableInstanceCommandDto ToProto(DisableInstanceCommand command)
{
ArgumentNullException.ThrowIfNull(command);
return new DisableInstanceCommandDto
{
CommandId = command.CommandId,
InstanceUniqueName = command.InstanceUniqueName,
Timestamp = Ts(command.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded command record.
public static DisableInstanceCommand FromProto(DisableInstanceCommandDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new DisableInstanceCommand(dto.CommandId, dto.InstanceUniqueName, Dto(dto.Timestamp));
}
/// Projects a onto the wire.
/// The command to project.
/// The wire message.
public static DeleteInstanceCommandDto ToProto(DeleteInstanceCommand command)
{
ArgumentNullException.ThrowIfNull(command);
return new DeleteInstanceCommandDto
{
CommandId = command.CommandId,
InstanceUniqueName = command.InstanceUniqueName,
Timestamp = Ts(command.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded command record.
public static DeleteInstanceCommand FromProto(DeleteInstanceCommandDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new DeleteInstanceCommand(dto.CommandId, dto.InstanceUniqueName, Dto(dto.Timestamp));
}
/// Projects a onto the wire.
/// The request to project.
/// The wire message.
public static DeploymentStateQueryRequestDto ToProto(DeploymentStateQueryRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return new DeploymentStateQueryRequestDto
{
CorrelationId = request.CorrelationId,
InstanceUniqueName = request.InstanceUniqueName,
Timestamp = Ts(request.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded request record.
public static DeploymentStateQueryRequest FromProto(DeploymentStateQueryRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new DeploymentStateQueryRequest(dto.CorrelationId, dto.InstanceUniqueName, Dto(dto.Timestamp));
}
/// Projects a onto the wire.
/// Each artifact collection is nullable; a null collection leaves its wrapper message unset.
/// The command to project.
/// The wire message.
public static DeployArtifactsCommandDto ToProto(DeployArtifactsCommand command)
{
ArgumentNullException.ThrowIfNull(command);
var dto = new DeployArtifactsCommandDto
{
DeploymentId = command.DeploymentId,
Timestamp = Ts(command.Timestamp)
};
if (command.SharedScripts is not null)
{
var list = new SharedScriptArtifactListDto();
list.Items.AddRange(command.SharedScripts.Select(ToProto));
dto.SharedScripts = list;
}
if (command.ExternalSystems is not null)
{
var list = new ExternalSystemArtifactListDto();
list.Items.AddRange(command.ExternalSystems.Select(ToProto));
dto.ExternalSystems = list;
}
if (command.DatabaseConnections is not null)
{
var list = new DatabaseConnectionArtifactListDto();
list.Items.AddRange(command.DatabaseConnections.Select(ToProto));
dto.DatabaseConnections = list;
}
if (command.NotificationLists is not null)
{
var list = new NotificationListArtifactListDto();
list.Items.AddRange(command.NotificationLists.Select(ToProto));
dto.NotificationLists = list;
}
if (command.DataConnections is not null)
{
var list = new DataConnectionArtifactListDto();
list.Items.AddRange(command.DataConnections.Select(ToProto));
dto.DataConnections = list;
}
if (command.SmtpConfigurations is not null)
{
var list = new SmtpConfigurationArtifactListDto();
list.Items.AddRange(command.SmtpConfigurations.Select(ToProto));
dto.SmtpConfigurations = list;
}
if (command.Secrets is not null)
{
var list = new SecretArtifactListDto();
list.Items.AddRange(command.Secrets.Select(ToProto));
dto.Secrets = list;
}
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded command record; unset collection wrappers rehydrate as null.
public static DeployArtifactsCommand FromProto(DeployArtifactsCommandDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new DeployArtifactsCommand(
dto.DeploymentId,
dto.SharedScripts is null ? null : dto.SharedScripts.Items.Select(FromProto).ToList(),
dto.ExternalSystems is null ? null : dto.ExternalSystems.Items.Select(FromProto).ToList(),
dto.DatabaseConnections is null ? null : dto.DatabaseConnections.Items.Select(FromProto).ToList(),
dto.NotificationLists is null ? null : dto.NotificationLists.Items.Select(FromProto).ToList(),
dto.DataConnections is null ? null : dto.DataConnections.Items.Select(FromProto).ToList(),
dto.SmtpConfigurations is null ? null : dto.SmtpConfigurations.Items.Select(FromProto).ToList(),
Dto(dto.Timestamp),
dto.Secrets is null ? null : dto.Secrets.Items.Select(FromProto).ToList());
}
/// Projects a onto the wire.
/// The artifact to project.
/// The wire message.
public static SecretArtifactDto ToProto(SecretArtifact artifact)
{
ArgumentNullException.ThrowIfNull(artifact);
return new SecretArtifactDto
{
Name = artifact.Name,
ContentType = artifact.ContentType,
Ciphertext = ByteString.CopyFrom(artifact.Ciphertext),
Nonce = ByteString.CopyFrom(artifact.Nonce),
Tag = ByteString.CopyFrom(artifact.Tag),
WrappedDek = ByteString.CopyFrom(artifact.WrappedDek),
WrapNonce = ByteString.CopyFrom(artifact.WrapNonce),
WrapTag = ByteString.CopyFrom(artifact.WrapTag),
KekId = artifact.KekId,
Revision = artifact.Revision,
CreatedUtc = Ts(artifact.CreatedUtc),
UpdatedUtc = Ts(artifact.UpdatedUtc),
Description = artifact.Description
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded artifact.
public static SecretArtifact FromProto(SecretArtifactDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new SecretArtifact(
dto.Name,
dto.ContentType,
dto.Ciphertext.ToByteArray(),
dto.Nonce.ToByteArray(),
dto.Tag.ToByteArray(),
dto.WrappedDek.ToByteArray(),
dto.WrapNonce.ToByteArray(),
dto.WrapTag.ToByteArray(),
dto.KekId,
dto.Revision,
Dto(dto.CreatedUtc),
Dto(dto.UpdatedUtc),
dto.Description);
}
/// Projects a onto the wire.
/// The artifact to project.
/// The wire message.
public static SharedScriptArtifactDto ToProto(SharedScriptArtifact artifact)
{
ArgumentNullException.ThrowIfNull(artifact);
return new SharedScriptArtifactDto
{
Name = artifact.Name,
Code = artifact.Code,
ParameterDefinitions = OrEmpty(artifact.ParameterDefinitions),
ReturnDefinition = OrEmpty(artifact.ReturnDefinition)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded artifact.
public static SharedScriptArtifact FromProto(SharedScriptArtifactDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new SharedScriptArtifact(
dto.Name, dto.Code, NullIfEmpty(dto.ParameterDefinitions), NullIfEmpty(dto.ReturnDefinition));
}
/// Projects an onto the wire.
/// The artifact to project.
/// The wire message.
public static ExternalSystemArtifactDto ToProto(ExternalSystemArtifact artifact)
{
ArgumentNullException.ThrowIfNull(artifact);
return new ExternalSystemArtifactDto
{
Name = artifact.Name,
EndpointUrl = artifact.EndpointUrl,
AuthType = artifact.AuthType,
AuthConfiguration = OrEmpty(artifact.AuthConfiguration),
MethodDefinitionsJson = OrEmpty(artifact.MethodDefinitionsJson),
TimeoutSeconds = artifact.TimeoutSeconds,
MaxRetries = artifact.MaxRetries,
RetryDelay = Duration.FromTimeSpan(artifact.RetryDelay)
};
}
/// Rehydrates an from the wire.
/// The wire message to decode.
/// The decoded artifact.
public static ExternalSystemArtifact FromProto(ExternalSystemArtifactDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ExternalSystemArtifact(
dto.Name,
dto.EndpointUrl,
dto.AuthType,
NullIfEmpty(dto.AuthConfiguration),
NullIfEmpty(dto.MethodDefinitionsJson),
dto.TimeoutSeconds,
dto.MaxRetries,
dto.RetryDelay?.ToTimeSpan() ?? TimeSpan.Zero);
}
/// Projects a onto the wire.
/// The artifact to project.
/// The wire message.
public static DatabaseConnectionArtifactDto ToProto(DatabaseConnectionArtifact artifact)
{
ArgumentNullException.ThrowIfNull(artifact);
return new DatabaseConnectionArtifactDto
{
Name = artifact.Name,
ConnectionString = artifact.ConnectionString,
MaxRetries = artifact.MaxRetries,
RetryDelay = Duration.FromTimeSpan(artifact.RetryDelay)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded artifact.
public static DatabaseConnectionArtifact FromProto(DatabaseConnectionArtifactDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new DatabaseConnectionArtifact(
dto.Name, dto.ConnectionString, dto.MaxRetries, dto.RetryDelay?.ToTimeSpan() ?? TimeSpan.Zero);
}
/// Projects a onto the wire.
/// The artifact to project.
/// The wire message.
public static NotificationListArtifactDto ToProto(NotificationListArtifact artifact)
{
ArgumentNullException.ThrowIfNull(artifact);
var dto = new NotificationListArtifactDto { Name = artifact.Name };
dto.RecipientEmails.AddRange(artifact.RecipientEmails);
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded artifact.
public static NotificationListArtifact FromProto(NotificationListArtifactDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new NotificationListArtifact(dto.Name, dto.RecipientEmails.ToList());
}
/// Projects a onto the wire.
/// The artifact to project.
/// The wire message.
public static DataConnectionArtifactDto ToProto(DataConnectionArtifact artifact)
{
ArgumentNullException.ThrowIfNull(artifact);
return new DataConnectionArtifactDto
{
Name = artifact.Name,
Protocol = artifact.Protocol,
PrimaryConfigurationJson = OrEmpty(artifact.PrimaryConfigurationJson),
BackupConfigurationJson = OrEmpty(artifact.BackupConfigurationJson),
FailoverRetryCount = artifact.FailoverRetryCount
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded artifact.
public static DataConnectionArtifact FromProto(DataConnectionArtifactDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new DataConnectionArtifact(
dto.Name,
dto.Protocol,
NullIfEmpty(dto.PrimaryConfigurationJson),
NullIfEmpty(dto.BackupConfigurationJson),
dto.FailoverRetryCount);
}
/// Projects an onto the wire.
/// The artifact to project.
/// The wire message.
public static SmtpConfigurationArtifactDto ToProto(SmtpConfigurationArtifact artifact)
{
ArgumentNullException.ThrowIfNull(artifact);
return new SmtpConfigurationArtifactDto
{
Name = artifact.Name,
Server = artifact.Server,
Port = artifact.Port,
AuthMode = artifact.AuthMode,
FromAddress = artifact.FromAddress,
Username = OrEmpty(artifact.Username),
Password = OrEmpty(artifact.Password),
OauthConfig = OrEmpty(artifact.OAuthConfig)
};
}
/// Rehydrates an from the wire.
/// The wire message to decode.
/// The decoded artifact.
public static SmtpConfigurationArtifact FromProto(SmtpConfigurationArtifactDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new SmtpConfigurationArtifact(
dto.Name,
dto.Server,
dto.Port,
dto.AuthMode,
dto.FromAddress,
NullIfEmpty(dto.Username),
NullIfEmpty(dto.Password),
NullIfEmpty(dto.OauthConfig));
}
// ── Lifecycle replies ──
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static DeploymentStatusResponseDto ToProto(DeploymentStatusResponse response)
{
ArgumentNullException.ThrowIfNull(response);
return new DeploymentStatusResponseDto
{
DeploymentId = response.DeploymentId,
InstanceUniqueName = response.InstanceUniqueName,
Status = ToProto(response.Status),
ErrorMessage = OrEmpty(response.ErrorMessage),
Timestamp = Ts(response.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static DeploymentStatusResponse FromProto(DeploymentStatusResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new DeploymentStatusResponse(
dto.DeploymentId,
dto.InstanceUniqueName,
FromProto(dto.Status),
NullIfEmpty(dto.ErrorMessage),
Dto(dto.Timestamp));
}
/// Projects an onto the wire.
/// The reply to project.
/// The wire message.
public static InstanceLifecycleResponseDto ToProto(InstanceLifecycleResponse response)
{
ArgumentNullException.ThrowIfNull(response);
return new InstanceLifecycleResponseDto
{
CommandId = response.CommandId,
InstanceUniqueName = response.InstanceUniqueName,
Success = response.Success,
ErrorMessage = OrEmpty(response.ErrorMessage),
Timestamp = Ts(response.Timestamp)
};
}
/// Rehydrates an from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static InstanceLifecycleResponse FromProto(InstanceLifecycleResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new InstanceLifecycleResponse(
dto.CommandId,
dto.InstanceUniqueName,
dto.Success,
NullIfEmpty(dto.ErrorMessage),
Dto(dto.Timestamp));
}
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static DeploymentStateQueryResponseDto ToProto(DeploymentStateQueryResponse response)
{
ArgumentNullException.ThrowIfNull(response);
return new DeploymentStateQueryResponseDto
{
CorrelationId = response.CorrelationId,
InstanceUniqueName = response.InstanceUniqueName,
IsDeployed = response.IsDeployed,
AppliedDeploymentId = OrEmpty(response.AppliedDeploymentId),
AppliedRevisionHash = OrEmpty(response.AppliedRevisionHash),
Timestamp = Ts(response.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static DeploymentStateQueryResponse FromProto(DeploymentStateQueryResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new DeploymentStateQueryResponse(
dto.CorrelationId,
dto.InstanceUniqueName,
dto.IsDeployed,
NullIfEmpty(dto.AppliedDeploymentId),
NullIfEmpty(dto.AppliedRevisionHash),
Dto(dto.Timestamp));
}
/// Projects an onto the wire.
/// The reply to project.
/// The wire message.
public static ArtifactDeploymentResponseDto ToProto(ArtifactDeploymentResponse response)
{
ArgumentNullException.ThrowIfNull(response);
var dto = new ArtifactDeploymentResponseDto
{
DeploymentId = response.DeploymentId,
SiteId = response.SiteId,
Success = response.Success,
ErrorMessage = OrEmpty(response.ErrorMessage),
Timestamp = Ts(response.Timestamp)
};
if (response.SecretsApplied.HasValue)
{
dto.SecretsApplied = response.SecretsApplied.Value;
}
if (response.SecretsRemoved is { Count: > 0 })
{
dto.SecretsRemoved.AddRange(response.SecretsRemoved);
}
return dto;
}
/// Rehydrates an from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static ArtifactDeploymentResponse FromProto(ArtifactDeploymentResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ArtifactDeploymentResponse(
dto.DeploymentId, dto.SiteId, dto.Success, NullIfEmpty(dto.ErrorMessage), Dto(dto.Timestamp),
dto.SecretsApplied,
dto.SecretsRemoved.Count > 0 ? dto.SecretsRemoved.ToList() : null);
}
// ─────────────────────────────────────────────────────────────────────
// RPC 2 — ExecuteOpcUa: envelopes
// ─────────────────────────────────────────────────────────────────────
/// Packs an OPC UA-group command into its request envelope.
/// One of the eight OPC UA-group commands.
/// The populated request envelope.
/// The command does not belong to this group.
public static OpcUaRequest ToOpcUaRequest(object command)
{
ArgumentNullException.ThrowIfNull(command);
return command switch
{
BrowseNodeCommand c => new OpcUaRequest { BrowseNode = ToProto(c) },
SearchAddressSpaceCommand c => new OpcUaRequest { SearchAddressSpace = ToProto(c) },
ReadTagValuesCommand c => new OpcUaRequest { ReadTagValues = ToProto(c) },
VerifyEndpointCommand c => new OpcUaRequest { VerifyEndpoint = ToProto(c) },
TrustServerCertCommand c => new OpcUaRequest { TrustServerCert = ToProto(c) },
ListServerCertsCommand c => new OpcUaRequest { ListServerCerts = ToProto(c) },
RemoveServerCertCommand c => new OpcUaRequest { RemoveServerCert = ToProto(c) },
WriteTagRequest c => new OpcUaRequest { WriteTag = ToProto(c) },
_ => throw NotInGroup(command, SiteCommandGroup.OpcUa)
};
}
/// Unpacks an OPC UA-group request envelope back to its command record.
/// The request envelope received on the wire.
/// The decoded command record.
/// The envelope carries no case this build knows.
public static object FromOpcUaRequest(OpcUaRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return request.CommandCase switch
{
OpcUaRequest.CommandOneofCase.BrowseNode => FromProto(request.BrowseNode),
OpcUaRequest.CommandOneofCase.SearchAddressSpace => FromProto(request.SearchAddressSpace),
OpcUaRequest.CommandOneofCase.ReadTagValues => FromProto(request.ReadTagValues),
OpcUaRequest.CommandOneofCase.VerifyEndpoint => FromProto(request.VerifyEndpoint),
OpcUaRequest.CommandOneofCase.TrustServerCert => FromProto(request.TrustServerCert),
OpcUaRequest.CommandOneofCase.ListServerCerts => FromProto(request.ListServerCerts),
OpcUaRequest.CommandOneofCase.RemoveServerCert => FromProto(request.RemoveServerCert),
OpcUaRequest.CommandOneofCase.WriteTag => FromProto(request.WriteTag),
_ => throw UnknownCase(request.CommandCase, nameof(OpcUaRequest))
};
}
/// Packs an OPC UA-group reply into its reply envelope.
/// One of the six OPC UA-group reply records.
/// The populated reply envelope.
/// The reply does not belong to this group.
public static OpcUaReply ToOpcUaReply(object reply)
{
ArgumentNullException.ThrowIfNull(reply);
return reply switch
{
BrowseNodeResult r => new OpcUaReply { BrowseNode = ToProto(r) },
SearchAddressSpaceResult r => new OpcUaReply { SearchAddressSpace = ToProto(r) },
ReadTagValuesResult r => new OpcUaReply { ReadTagValues = ToProto(r) },
VerifyEndpointResult r => new OpcUaReply { VerifyEndpoint = ToProto(r) },
CertTrustResult r => new OpcUaReply { CertTrust = ToProto(r) },
WriteTagResponse r => new OpcUaReply { WriteTag = ToProto(r) },
_ => throw NotInGroup(reply, SiteCommandGroup.OpcUa)
};
}
/// Unpacks an OPC UA-group reply envelope back to its reply record.
/// The reply envelope received on the wire.
/// The decoded reply record.
/// The envelope carries no case this build knows.
public static object FromOpcUaReply(OpcUaReply reply)
{
ArgumentNullException.ThrowIfNull(reply);
return reply.ReplyCase switch
{
OpcUaReply.ReplyOneofCase.BrowseNode => FromProto(reply.BrowseNode),
OpcUaReply.ReplyOneofCase.SearchAddressSpace => FromProto(reply.SearchAddressSpace),
OpcUaReply.ReplyOneofCase.ReadTagValues => FromProto(reply.ReadTagValues),
OpcUaReply.ReplyOneofCase.VerifyEndpoint => FromProto(reply.VerifyEndpoint),
OpcUaReply.ReplyOneofCase.CertTrust => FromProto(reply.CertTrust),
OpcUaReply.ReplyOneofCase.WriteTag => FromProto(reply.WriteTag),
_ => throw UnknownCase(reply.ReplyCase, nameof(OpcUaReply))
};
}
// ── OPC UA commands ──
/// Projects a onto the wire.
/// The command to project.
/// The wire message.
public static BrowseNodeCommandDto ToProto(BrowseNodeCommand command)
{
ArgumentNullException.ThrowIfNull(command);
return new BrowseNodeCommandDto
{
ConnectionName = command.ConnectionName,
ParentNodeId = OrEmpty(command.ParentNodeId),
ContinuationToken = OrEmpty(command.ContinuationToken),
SiteIdentifier = OrEmpty(command.SiteIdentifier)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded command record.
public static BrowseNodeCommand FromProto(BrowseNodeCommandDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new BrowseNodeCommand(
dto.ConnectionName,
NullIfEmpty(dto.ParentNodeId),
NullIfEmpty(dto.ContinuationToken),
NullIfEmpty(dto.SiteIdentifier));
}
/// Projects a onto the wire.
/// The command to project.
/// The wire message.
public static SearchAddressSpaceCommandDto ToProto(SearchAddressSpaceCommand command)
{
ArgumentNullException.ThrowIfNull(command);
return new SearchAddressSpaceCommandDto
{
ConnectionName = command.ConnectionName,
Query = command.Query,
MaxDepth = command.MaxDepth,
MaxResults = command.MaxResults,
SiteIdentifier = OrEmpty(command.SiteIdentifier)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded command record.
public static SearchAddressSpaceCommand FromProto(SearchAddressSpaceCommandDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new SearchAddressSpaceCommand(
dto.ConnectionName, dto.Query, dto.MaxDepth, dto.MaxResults, NullIfEmpty(dto.SiteIdentifier));
}
/// Projects a onto the wire.
/// The command to project.
/// The wire message.
public static ReadTagValuesCommandDto ToProto(ReadTagValuesCommand command)
{
ArgumentNullException.ThrowIfNull(command);
var dto = new ReadTagValuesCommandDto { ConnectionName = command.ConnectionName };
dto.TagPaths.AddRange(command.TagPaths);
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded command record.
public static ReadTagValuesCommand FromProto(ReadTagValuesCommandDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ReadTagValuesCommand(dto.ConnectionName, dto.TagPaths.ToList());
}
/// Projects a onto the wire.
/// The command to project.
/// The wire message.
public static VerifyEndpointCommandDto ToProto(VerifyEndpointCommand command)
{
ArgumentNullException.ThrowIfNull(command);
return new VerifyEndpointCommandDto
{
ConnectionName = command.ConnectionName,
Protocol = command.Protocol,
ConfigJson = command.ConfigJson,
SiteIdentifier = OrEmpty(command.SiteIdentifier)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded command record.
public static VerifyEndpointCommand FromProto(VerifyEndpointCommandDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new VerifyEndpointCommand(
dto.ConnectionName, dto.Protocol, dto.ConfigJson, NullIfEmpty(dto.SiteIdentifier));
}
/// Projects a onto the wire.
/// The command to project.
/// The wire message.
public static TrustServerCertCommandDto ToProto(TrustServerCertCommand command)
{
ArgumentNullException.ThrowIfNull(command);
return new TrustServerCertCommandDto
{
ConnectionName = command.ConnectionName,
DerBase64 = command.DerBase64,
Thumbprint = command.Thumbprint,
SiteIdentifier = OrEmpty(command.SiteIdentifier)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded command record.
public static TrustServerCertCommand FromProto(TrustServerCertCommandDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new TrustServerCertCommand(
dto.ConnectionName, dto.DerBase64, dto.Thumbprint, NullIfEmpty(dto.SiteIdentifier));
}
/// Projects a onto the wire.
/// The command to project.
/// The wire message.
public static ListServerCertsCommandDto ToProto(ListServerCertsCommand command)
{
ArgumentNullException.ThrowIfNull(command);
return new ListServerCertsCommandDto { SiteIdentifier = OrEmpty(command.SiteIdentifier) };
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded command record.
public static ListServerCertsCommand FromProto(ListServerCertsCommandDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ListServerCertsCommand(NullIfEmpty(dto.SiteIdentifier));
}
/// Projects a onto the wire.
/// The command to project.
/// The wire message.
public static RemoveServerCertCommandDto ToProto(RemoveServerCertCommand command)
{
ArgumentNullException.ThrowIfNull(command);
return new RemoveServerCertCommandDto
{
Thumbprint = command.Thumbprint,
SiteIdentifier = OrEmpty(command.SiteIdentifier)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded command record.
public static RemoveServerCertCommand FromProto(RemoveServerCertCommandDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new RemoveServerCertCommand(dto.Thumbprint, NullIfEmpty(dto.SiteIdentifier));
}
/// Projects a onto the wire.
/// The request to project.
/// The wire message.
public static WriteTagRequestDto ToProto(WriteTagRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return new WriteTagRequestDto
{
CorrelationId = request.CorrelationId,
ConnectionName = request.ConnectionName,
TagPath = request.TagPath,
Value = LooseValueCodec.ToProtoOrNull(request.Value),
Timestamp = Ts(request.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded request record.
public static WriteTagRequest FromProto(WriteTagRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new WriteTagRequest(
dto.CorrelationId,
dto.ConnectionName,
dto.TagPath,
LooseValueCodec.FromProto(dto.Value),
Dto(dto.Timestamp));
}
// ── OPC UA replies ──
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static BrowseNodeResultDto ToProto(BrowseNodeResult result)
{
ArgumentNullException.ThrowIfNull(result);
var dto = new BrowseNodeResultDto
{
Truncated = result.Truncated,
ContinuationToken = OrEmpty(result.ContinuationToken)
};
dto.Children.AddRange(result.Children.Select(ToProto));
if (result.Failure is not null)
{
dto.Failure = ToProto(result.Failure);
}
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static BrowseNodeResult FromProto(BrowseNodeResultDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new BrowseNodeResult(
dto.Children.Select(FromProto).ToList(),
dto.Truncated,
dto.Failure is null ? null : FromProto(dto.Failure),
NullIfEmpty(dto.ContinuationToken));
}
/// Projects a onto the wire.
/// The address-space node to project.
/// The wire message.
public static BrowseNodeDto ToProto(BrowseNode node)
{
ArgumentNullException.ThrowIfNull(node);
var dto = new BrowseNodeDto
{
NodeId = node.NodeId,
DisplayName = node.DisplayName,
NodeClass = ToProto(node.NodeClass),
HasChildren = node.HasChildren,
DataType = OrEmpty(node.DataType)
};
if (node.ValueRank.HasValue)
{
dto.ValueRank = node.ValueRank.Value;
}
if (node.Writable.HasValue)
{
dto.Writable = node.Writable.Value;
}
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded address-space node.
public static BrowseNode FromProto(BrowseNodeDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new BrowseNode(
dto.NodeId,
dto.DisplayName,
FromProto(dto.NodeClass),
dto.HasChildren,
NullIfEmpty(dto.DataType),
dto.ValueRank,
dto.Writable);
}
/// Projects a onto the wire.
/// The structured browse failure to project.
/// The wire message.
public static BrowseFailureDto ToProto(BrowseFailure failure)
{
ArgumentNullException.ThrowIfNull(failure);
return new BrowseFailureDto { Kind = ToProto(failure.Kind), Message = failure.Message };
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded structured browse failure.
public static BrowseFailure FromProto(BrowseFailureDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new BrowseFailure(FromProto(dto.Kind), dto.Message);
}
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static SearchAddressSpaceResultDto ToProto(SearchAddressSpaceResult result)
{
ArgumentNullException.ThrowIfNull(result);
var dto = new SearchAddressSpaceResultDto { CapReached = result.CapReached };
dto.Matches.AddRange(result.Matches.Select(ToProto));
if (result.Failure is not null)
{
dto.Failure = ToProto(result.Failure);
}
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static SearchAddressSpaceResult FromProto(SearchAddressSpaceResultDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new SearchAddressSpaceResult(
dto.Matches.Select(FromProto).ToList(),
dto.CapReached,
dto.Failure is null ? null : FromProto(dto.Failure));
}
/// Projects an onto the wire.
/// The search match to project.
/// The wire message.
public static AddressSpaceMatchDto ToProto(AddressSpaceMatch match)
{
ArgumentNullException.ThrowIfNull(match);
return new AddressSpaceMatchDto { Node = ToProto(match.Node), Path = match.Path };
}
/// Rehydrates an from the wire.
/// The wire message to decode.
/// The decoded search match.
public static AddressSpaceMatch FromProto(AddressSpaceMatchDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new AddressSpaceMatch(FromProto(dto.Node), dto.Path);
}
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static ReadTagValuesResultDto ToProto(ReadTagValuesResult result)
{
ArgumentNullException.ThrowIfNull(result);
var dto = new ReadTagValuesResultDto();
dto.Outcomes.AddRange(result.Outcomes.Select(ToProto));
if (result.Failure is not null)
{
dto.Failure = ToProto(result.Failure);
}
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static ReadTagValuesResult FromProto(ReadTagValuesResultDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ReadTagValuesResult(
dto.Outcomes.Select(FromProto).ToList(),
dto.Failure is null ? null : FromProto(dto.Failure));
}
/// Projects a onto the wire.
/// The per-tag read outcome to project.
/// The wire message.
public static TagReadOutcomeDto ToProto(TagReadOutcome outcome)
{
ArgumentNullException.ThrowIfNull(outcome);
return new TagReadOutcomeDto
{
TagPath = outcome.TagPath,
Success = outcome.Success,
Value = LooseValueCodec.ToProtoOrNull(outcome.Value),
Quality = outcome.Quality,
Timestamp = Ts(outcome.Timestamp),
ErrorMessage = OrEmpty(outcome.ErrorMessage)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded per-tag read outcome.
public static TagReadOutcome FromProto(TagReadOutcomeDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new TagReadOutcome(
dto.TagPath,
dto.Success,
LooseValueCodec.FromProto(dto.Value),
dto.Quality,
Dto(dto.Timestamp),
NullIfEmpty(dto.ErrorMessage));
}
/// Projects a onto the wire.
/// The connection-level read failure to project.
/// The wire message.
public static ReadTagValuesFailureDto ToProto(ReadTagValuesFailure failure)
{
ArgumentNullException.ThrowIfNull(failure);
return new ReadTagValuesFailureDto { Kind = ToProto(failure.Kind), Message = failure.Message };
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded connection-level read failure.
public static ReadTagValuesFailure FromProto(ReadTagValuesFailureDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ReadTagValuesFailure(FromProto(dto.Kind), dto.Message);
}
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static VerifyEndpointResultDto ToProto(VerifyEndpointResult result)
{
ArgumentNullException.ThrowIfNull(result);
var dto = new VerifyEndpointResultDto
{
Success = result.Success,
Error = OrEmpty(result.Error)
};
if (result.FailureKind.HasValue)
{
dto.FailureKind = new VerifyFailureKindValue { Value = ToProto(result.FailureKind.Value) };
}
if (result.Cert is not null)
{
dto.Cert = ToProto(result.Cert);
}
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static VerifyEndpointResult FromProto(VerifyEndpointResultDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new VerifyEndpointResult(
dto.Success,
dto.FailureKind is null ? null : FromProto(dto.FailureKind.Value),
NullIfEmpty(dto.Error),
dto.Cert is null ? null : FromProto(dto.Cert));
}
/// Projects a onto the wire.
/// The captured server certificate to project.
/// The wire message.
public static ServerCertInfoDto ToProto(ServerCertInfo cert)
{
ArgumentNullException.ThrowIfNull(cert);
return new ServerCertInfoDto
{
Thumbprint = cert.Thumbprint,
Subject = cert.Subject,
Issuer = cert.Issuer,
NotBeforeUtc = TsUtc(cert.NotBeforeUtc),
NotAfterUtc = TsUtc(cert.NotAfterUtc),
DerBase64 = cert.DerBase64
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded captured server certificate.
public static ServerCertInfo FromProto(ServerCertInfoDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ServerCertInfo(
dto.Thumbprint,
dto.Subject,
dto.Issuer,
DtUtc(dto.NotBeforeUtc),
DtUtc(dto.NotAfterUtc),
dto.DerBase64);
}
/// Projects a onto the wire.
/// A null (trust/remove) leaves the list wrapper unset.
/// The reply to project.
/// The wire message.
public static CertTrustResultDto ToProto(CertTrustResult result)
{
ArgumentNullException.ThrowIfNull(result);
var dto = new CertTrustResultDto { Success = result.Success, Error = OrEmpty(result.Error) };
if (result.Certs is not null)
{
var list = new TrustedCertInfoListDto();
list.Items.AddRange(result.Certs.Select(ToProto));
dto.Certs = list;
}
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static CertTrustResult FromProto(CertTrustResultDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new CertTrustResult(
dto.Success,
NullIfEmpty(dto.Error),
dto.Certs is null ? null : dto.Certs.Items.Select(FromProto).ToList());
}
/// Projects a onto the wire.
/// The PKI store entry to project.
/// The wire message.
public static TrustedCertInfoDto ToProto(TrustedCertInfo cert)
{
ArgumentNullException.ThrowIfNull(cert);
return new TrustedCertInfoDto
{
Thumbprint = cert.Thumbprint,
Subject = cert.Subject,
Issuer = cert.Issuer,
NotBeforeUtc = TsUtc(cert.NotBeforeUtc),
NotAfterUtc = TsUtc(cert.NotAfterUtc),
Rejected = cert.Rejected
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded PKI store entry.
public static TrustedCertInfo FromProto(TrustedCertInfoDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new TrustedCertInfo(
dto.Thumbprint,
dto.Subject,
dto.Issuer,
DtUtc(dto.NotBeforeUtc),
DtUtc(dto.NotAfterUtc),
dto.Rejected);
}
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static WriteTagResponseDto ToProto(WriteTagResponse response)
{
ArgumentNullException.ThrowIfNull(response);
return new WriteTagResponseDto
{
CorrelationId = response.CorrelationId,
Success = response.Success,
ErrorMessage = OrEmpty(response.ErrorMessage),
Timestamp = Ts(response.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static WriteTagResponse FromProto(WriteTagResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new WriteTagResponse(
dto.CorrelationId, dto.Success, NullIfEmpty(dto.ErrorMessage), Dto(dto.Timestamp));
}
// ─────────────────────────────────────────────────────────────────────
// RPC 3 — ExecuteQuery: envelopes
// ─────────────────────────────────────────────────────────────────────
/// Packs a query-group command into its request envelope.
/// One of the four query-group commands.
/// The populated request envelope.
/// The command does not belong to this group.
public static QueryRequest ToQueryRequest(object command)
{
ArgumentNullException.ThrowIfNull(command);
return command switch
{
EventLogQueryRequest c => new QueryRequest { EventLogQuery = ToProto(c) },
DebugSnapshotRequest c => new QueryRequest { DebugSnapshot = ToProto(c) },
SubscribeDebugViewRequest c => new QueryRequest { SubscribeDebugView = ToProto(c) },
UnsubscribeDebugViewRequest c => new QueryRequest { UnsubscribeDebugView = ToProto(c) },
_ => throw NotInGroup(command, SiteCommandGroup.Query)
};
}
/// Unpacks a query-group request envelope back to its command record.
/// The request envelope received on the wire.
/// The decoded command record.
/// The envelope carries no case this build knows.
public static object FromQueryRequest(QueryRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return request.CommandCase switch
{
QueryRequest.CommandOneofCase.EventLogQuery => FromProto(request.EventLogQuery),
QueryRequest.CommandOneofCase.DebugSnapshot => FromProto(request.DebugSnapshot),
QueryRequest.CommandOneofCase.SubscribeDebugView => FromProto(request.SubscribeDebugView),
QueryRequest.CommandOneofCase.UnsubscribeDebugView => FromProto(request.UnsubscribeDebugView),
_ => throw UnknownCase(request.CommandCase, nameof(QueryRequest))
};
}
///
/// Packs a query-group reply into its reply envelope. Pass
/// for the unsubscribe path, which is a
/// Tell today and carries no payload.
///
/// One of the query-group reply records.
/// The populated reply envelope.
/// The reply does not belong to this group.
public static QueryReply ToQueryReply(object reply)
{
ArgumentNullException.ThrowIfNull(reply);
return reply switch
{
EventLogQueryResponse r => new QueryReply { EventLogQuery = ToProto(r) },
DebugViewSnapshot r => new QueryReply { DebugViewSnapshot = ToProto(r) },
UnsubscribeDebugViewAck => new QueryReply { UnsubscribeDebugView = new UnsubscribeDebugViewAckDto() },
_ => throw NotInGroup(reply, SiteCommandGroup.Query)
};
}
/// Unpacks a query-group reply envelope back to its reply record.
/// The reply envelope received on the wire.
/// The decoded reply record.
/// The envelope carries no case this build knows.
public static object FromQueryReply(QueryReply reply)
{
ArgumentNullException.ThrowIfNull(reply);
return reply.ReplyCase switch
{
QueryReply.ReplyOneofCase.EventLogQuery => FromProto(reply.EventLogQuery),
QueryReply.ReplyOneofCase.DebugViewSnapshot => FromProto(reply.DebugViewSnapshot),
QueryReply.ReplyOneofCase.UnsubscribeDebugView => UnsubscribeDebugViewAck.Instance,
_ => throw UnknownCase(reply.ReplyCase, nameof(QueryReply))
};
}
// ── Query commands ──
/// Projects an onto the wire.
/// The request to project.
/// The wire message.
public static EventLogQueryRequestDto ToProto(EventLogQueryRequest request)
{
ArgumentNullException.ThrowIfNull(request);
var dto = new EventLogQueryRequestDto
{
CorrelationId = request.CorrelationId,
SiteId = request.SiteId,
EventType = OrEmpty(request.EventType),
Severity = OrEmpty(request.Severity),
InstanceId = OrEmpty(request.InstanceId),
KeywordFilter = OrEmpty(request.KeywordFilter),
ContinuationToken = OrEmpty(request.ContinuationToken),
PageSize = request.PageSize,
Timestamp = Ts(request.Timestamp)
};
if (request.From.HasValue)
{
dto.From = Ts(request.From.Value);
}
if (request.To.HasValue)
{
dto.To = Ts(request.To.Value);
}
return dto;
}
/// Rehydrates an from the wire.
/// The wire message to decode.
/// The decoded request record.
public static EventLogQueryRequest FromProto(EventLogQueryRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new EventLogQueryRequest(
dto.CorrelationId,
dto.SiteId,
DtoOrNull(dto.From),
DtoOrNull(dto.To),
NullIfEmpty(dto.EventType),
NullIfEmpty(dto.Severity),
NullIfEmpty(dto.InstanceId),
NullIfEmpty(dto.KeywordFilter),
NullIfEmpty(dto.ContinuationToken),
dto.PageSize,
Dto(dto.Timestamp));
}
/// Projects a onto the wire.
/// The request to project.
/// The wire message.
public static DebugSnapshotRequestDto ToProto(DebugSnapshotRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return new DebugSnapshotRequestDto
{
InstanceUniqueName = request.InstanceUniqueName,
CorrelationId = request.CorrelationId,
// Additive field 3 — proto3 false default means an older site that does
// not know the field simply returns the full snapshot (correct, just fatter).
AlarmsOnly = request.AlarmsOnly
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded request record.
public static DebugSnapshotRequest FromProto(DebugSnapshotRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new DebugSnapshotRequest(dto.InstanceUniqueName, dto.CorrelationId, dto.AlarmsOnly);
}
/// Projects a onto the wire.
/// The request to project.
/// The wire message.
public static SubscribeDebugViewRequestDto ToProto(SubscribeDebugViewRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return new SubscribeDebugViewRequestDto
{
InstanceUniqueName = request.InstanceUniqueName,
CorrelationId = request.CorrelationId
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded request record.
public static SubscribeDebugViewRequest FromProto(SubscribeDebugViewRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new SubscribeDebugViewRequest(dto.InstanceUniqueName, dto.CorrelationId);
}
/// Projects an onto the wire.
/// The request to project.
/// The wire message.
public static UnsubscribeDebugViewRequestDto ToProto(UnsubscribeDebugViewRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return new UnsubscribeDebugViewRequestDto
{
InstanceUniqueName = request.InstanceUniqueName,
CorrelationId = request.CorrelationId
};
}
/// Rehydrates an from the wire.
/// The wire message to decode.
/// The decoded request record.
public static UnsubscribeDebugViewRequest FromProto(UnsubscribeDebugViewRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new UnsubscribeDebugViewRequest(dto.InstanceUniqueName, dto.CorrelationId);
}
// ── Query replies ──
/// Projects an onto the wire.
/// The reply to project.
/// The wire message.
public static EventLogQueryResponseDto ToProto(EventLogQueryResponse response)
{
ArgumentNullException.ThrowIfNull(response);
var dto = new EventLogQueryResponseDto
{
CorrelationId = response.CorrelationId,
SiteId = response.SiteId,
ContinuationToken = OrEmpty(response.ContinuationToken),
HasMore = response.HasMore,
Success = response.Success,
ErrorMessage = OrEmpty(response.ErrorMessage),
Timestamp = Ts(response.Timestamp)
};
dto.Entries.AddRange(response.Entries.Select(ToProto));
return dto;
}
/// Rehydrates an from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static EventLogQueryResponse FromProto(EventLogQueryResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new EventLogQueryResponse(
dto.CorrelationId,
dto.SiteId,
dto.Entries.Select(FromProto).ToList(),
NullIfEmpty(dto.ContinuationToken),
dto.HasMore,
dto.Success,
NullIfEmpty(dto.ErrorMessage),
Dto(dto.Timestamp));
}
/// Projects an onto the wire.
/// The event-log row to project.
/// The wire message.
public static EventLogEntryDto ToProto(EventLogEntry entry)
{
ArgumentNullException.ThrowIfNull(entry);
return new EventLogEntryDto
{
Id = entry.Id,
Timestamp = Ts(entry.Timestamp),
EventType = entry.EventType,
Severity = entry.Severity,
InstanceId = OrEmpty(entry.InstanceId),
Source = entry.Source,
Message = entry.Message,
Details = OrEmpty(entry.Details)
};
}
/// Rehydrates an from the wire.
/// The wire message to decode.
/// The decoded event-log row.
public static EventLogEntry FromProto(EventLogEntryDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new EventLogEntry(
dto.Id,
Dto(dto.Timestamp),
dto.EventType,
dto.Severity,
NullIfEmpty(dto.InstanceId),
dto.Source,
dto.Message,
NullIfEmpty(dto.Details));
}
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static DebugViewSnapshotDto ToProto(DebugViewSnapshot snapshot)
{
ArgumentNullException.ThrowIfNull(snapshot);
var dto = new DebugViewSnapshotDto
{
InstanceUniqueName = snapshot.InstanceUniqueName,
SnapshotTimestamp = Ts(snapshot.SnapshotTimestamp),
InstanceNotFound = snapshot.InstanceNotFound
};
dto.AttributeValues.AddRange(snapshot.AttributeValues.Select(ToProto));
dto.AlarmStates.AddRange(snapshot.AlarmStates.Select(ToProto));
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static DebugViewSnapshot FromProto(DebugViewSnapshotDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new DebugViewSnapshot(
dto.InstanceUniqueName,
dto.AttributeValues.Select(FromProto).ToList(),
dto.AlarmStates.Select(FromProto).ToList(),
Dto(dto.SnapshotTimestamp),
dto.InstanceNotFound);
}
/// Projects an snapshot row onto the wire.
/// The attribute snapshot row to project.
/// The wire message.
public static DebugAttributeValueDto ToProto(AttributeValueChanged value)
{
ArgumentNullException.ThrowIfNull(value);
return new DebugAttributeValueDto
{
InstanceUniqueName = value.InstanceUniqueName,
AttributePath = value.AttributePath,
AttributeName = value.AttributeName,
Value = LooseValueCodec.ToProtoOrNull(value.Value),
Quality = value.Quality,
Timestamp = Ts(value.Timestamp)
};
}
/// Rehydrates an snapshot row from the wire.
/// The wire message to decode.
/// The decoded attribute snapshot row.
public static AttributeValueChanged FromProto(DebugAttributeValueDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new AttributeValueChanged(
dto.InstanceUniqueName,
dto.AttributePath,
dto.AttributeName,
LooseValueCodec.FromProto(dto.Value),
dto.Quality,
Dto(dto.Timestamp));
}
/// Projects an snapshot row onto the wire.
///
/// is only written when it differs
/// from the value the record itself derives from State + Priority.
/// The record stores the condition in a nullable backing field and synthesises
/// the computed default on read, so writing it unconditionally would turn an
/// implicit condition into an explicit one — indistinguishable through the
/// property, but a difference under record equality. Omitting the redundant
/// value keeps the round trip exact for computed alarms, which are the common
/// case, and is information-preserving for the rest.
///
/// The alarm snapshot row to project.
/// The wire message.
public static DebugAlarmStateDto ToProto(AlarmStateChanged alarm)
{
ArgumentNullException.ThrowIfNull(alarm);
var dto = new DebugAlarmStateDto
{
InstanceUniqueName = alarm.InstanceUniqueName,
AlarmName = alarm.AlarmName,
State = ToProto(alarm.State),
Priority = alarm.Priority,
Timestamp = Ts(alarm.Timestamp),
Level = ToProto(alarm.Level),
Message = alarm.Message,
Kind = ToProto(alarm.Kind),
SourceReference = alarm.SourceReference,
AlarmTypeName = alarm.AlarmTypeName,
Category = alarm.Category,
OperatorUser = alarm.OperatorUser,
OperatorComment = alarm.OperatorComment,
CurrentValue = alarm.CurrentValue,
LimitValue = alarm.LimitValue,
NativeSourceCanonicalName = alarm.NativeSourceCanonicalName,
IsConfiguredPlaceholder = alarm.IsConfiguredPlaceholder,
IsMesReportable = alarm.IsMesReportable
};
if (alarm.Condition != AlarmConditionStateFactory.ForComputed(alarm.State, alarm.Priority))
{
dto.Condition = ToProto(alarm.Condition);
}
if (alarm.OriginalRaiseTime.HasValue)
{
dto.OriginalRaiseTime = Ts(alarm.OriginalRaiseTime.Value);
}
if (alarm.AckTime.HasValue)
{
dto.AckTime = Ts(alarm.AckTime.Value);
}
return dto;
}
/// Rehydrates an snapshot row from the wire.
/// The wire message to decode.
/// The decoded alarm snapshot row.
public static AlarmStateChanged FromProto(DebugAlarmStateDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
var alarm = new AlarmStateChanged(
dto.InstanceUniqueName,
dto.AlarmName,
FromProto(dto.State),
dto.Priority,
Dto(dto.Timestamp))
{
Level = FromProto(dto.Level),
Message = dto.Message,
Kind = FromProto(dto.Kind),
SourceReference = dto.SourceReference,
AlarmTypeName = dto.AlarmTypeName,
Category = dto.Category,
OperatorUser = dto.OperatorUser,
OperatorComment = dto.OperatorComment,
OriginalRaiseTime = DtoOrNull(dto.OriginalRaiseTime),
CurrentValue = dto.CurrentValue,
LimitValue = dto.LimitValue,
NativeSourceCanonicalName = dto.NativeSourceCanonicalName,
IsConfiguredPlaceholder = dto.IsConfiguredPlaceholder,
IsMesReportable = dto.IsMesReportable,
AckTime = DtoOrNull(dto.AckTime)
};
// AlarmConditionStateDto carries Severity only; the raw priority (Gitea #129) travels as the
// row's own Priority, which the site always sets equal to Condition.Priority, so restore it here.
return dto.Condition is null
? alarm
: alarm with { Condition = FromProto(dto.Condition) with { Priority = dto.Priority } };
}
/// Projects an onto the wire.
/// The unified alarm condition to project.
/// The wire message.
public static AlarmConditionStateDto ToProto(AlarmConditionState condition)
{
ArgumentNullException.ThrowIfNull(condition);
var dto = new AlarmConditionStateDto
{
Active = condition.Active,
Acknowledged = condition.Acknowledged,
Shelve = ToProto(condition.Shelve),
Suppressed = condition.Suppressed,
Severity = condition.Severity
};
if (condition.Confirmed.HasValue)
{
dto.Confirmed = condition.Confirmed.Value;
}
return dto;
}
/// Rehydrates an from the wire.
/// The wire message to decode.
/// The decoded unified alarm condition.
public static AlarmConditionState FromProto(AlarmConditionStateDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new AlarmConditionState(
dto.Active,
dto.Acknowledged,
dto.Confirmed,
FromProto(dto.Shelve),
dto.Suppressed,
dto.Severity);
}
// ─────────────────────────────────────────────────────────────────────
// RPC 4 — ExecuteParked: envelopes
// ─────────────────────────────────────────────────────────────────────
/// Packs a parked-group command into its request envelope.
/// One of the five parked-group commands.
/// The populated request envelope.
/// The command does not belong to this group.
public static ParkedRequest ToParkedRequest(object command)
{
ArgumentNullException.ThrowIfNull(command);
return command switch
{
ParkedMessageQueryRequest c => new ParkedRequest { ParkedMessageQuery = ToProto(c) },
ParkedMessageRetryRequest c => new ParkedRequest { ParkedMessageRetry = ToProto(c) },
ParkedMessageDiscardRequest c => new ParkedRequest { ParkedMessageDiscard = ToProto(c) },
RetryParkedOperation c => new ParkedRequest { RetryParkedOperation = ToProto(c) },
DiscardParkedOperation c => new ParkedRequest { DiscardParkedOperation = ToProto(c) },
_ => throw NotInGroup(command, SiteCommandGroup.Parked)
};
}
/// Unpacks a parked-group request envelope back to its command record.
/// The request envelope received on the wire.
/// The decoded command record.
/// The envelope carries no case this build knows.
public static object FromParkedRequest(ParkedRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return request.CommandCase switch
{
ParkedRequest.CommandOneofCase.ParkedMessageQuery => FromProto(request.ParkedMessageQuery),
ParkedRequest.CommandOneofCase.ParkedMessageRetry => FromProto(request.ParkedMessageRetry),
ParkedRequest.CommandOneofCase.ParkedMessageDiscard => FromProto(request.ParkedMessageDiscard),
ParkedRequest.CommandOneofCase.RetryParkedOperation => FromProto(request.RetryParkedOperation),
ParkedRequest.CommandOneofCase.DiscardParkedOperation => FromProto(request.DiscardParkedOperation),
_ => throw UnknownCase(request.CommandCase, nameof(ParkedRequest))
};
}
/// Packs a parked-group reply into its reply envelope.
/// One of the four parked-group reply records.
/// The populated reply envelope.
/// The reply does not belong to this group.
public static ParkedReply ToParkedReply(object reply)
{
ArgumentNullException.ThrowIfNull(reply);
return reply switch
{
ParkedMessageQueryResponse r => new ParkedReply { ParkedMessageQuery = ToProto(r) },
ParkedMessageRetryResponse r => new ParkedReply { ParkedMessageRetry = ToProto(r) },
ParkedMessageDiscardResponse r => new ParkedReply { ParkedMessageDiscard = ToProto(r) },
ParkedOperationActionAck r => new ParkedReply { ParkedOperationAction = ToProto(r) },
_ => throw NotInGroup(reply, SiteCommandGroup.Parked)
};
}
/// Unpacks a parked-group reply envelope back to its reply record.
/// The reply envelope received on the wire.
/// The decoded reply record.
/// The envelope carries no case this build knows.
public static object FromParkedReply(ParkedReply reply)
{
ArgumentNullException.ThrowIfNull(reply);
return reply.ReplyCase switch
{
ParkedReply.ReplyOneofCase.ParkedMessageQuery => FromProto(reply.ParkedMessageQuery),
ParkedReply.ReplyOneofCase.ParkedMessageRetry => FromProto(reply.ParkedMessageRetry),
ParkedReply.ReplyOneofCase.ParkedMessageDiscard => FromProto(reply.ParkedMessageDiscard),
ParkedReply.ReplyOneofCase.ParkedOperationAction => FromProto(reply.ParkedOperationAction),
_ => throw UnknownCase(reply.ReplyCase, nameof(ParkedReply))
};
}
// ── Parked commands ──
/// Projects a onto the wire.
/// The request to project.
/// The wire message.
public static ParkedMessageQueryRequestDto ToProto(ParkedMessageQueryRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return new ParkedMessageQueryRequestDto
{
CorrelationId = request.CorrelationId,
SiteId = request.SiteId,
PageNumber = request.PageNumber,
PageSize = request.PageSize,
Timestamp = Ts(request.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded request record.
public static ParkedMessageQueryRequest FromProto(ParkedMessageQueryRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ParkedMessageQueryRequest(
dto.CorrelationId, dto.SiteId, dto.PageNumber, dto.PageSize, Dto(dto.Timestamp));
}
/// Projects a onto the wire.
/// The request to project.
/// The wire message.
public static ParkedMessageRetryRequestDto ToProto(ParkedMessageRetryRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return new ParkedMessageRetryRequestDto
{
CorrelationId = request.CorrelationId,
SiteId = request.SiteId,
MessageId = request.MessageId,
Timestamp = Ts(request.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded request record.
public static ParkedMessageRetryRequest FromProto(ParkedMessageRetryRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ParkedMessageRetryRequest(
dto.CorrelationId, dto.SiteId, dto.MessageId, Dto(dto.Timestamp));
}
/// Projects a onto the wire.
/// The request to project.
/// The wire message.
public static ParkedMessageDiscardRequestDto ToProto(ParkedMessageDiscardRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return new ParkedMessageDiscardRequestDto
{
CorrelationId = request.CorrelationId,
SiteId = request.SiteId,
MessageId = request.MessageId,
Timestamp = Ts(request.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded request record.
public static ParkedMessageDiscardRequest FromProto(ParkedMessageDiscardRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ParkedMessageDiscardRequest(
dto.CorrelationId, dto.SiteId, dto.MessageId, Dto(dto.Timestamp));
}
/// Projects a relay onto the wire.
/// The relay command to project.
/// The wire message.
public static RetryParkedOperationDto ToProto(RetryParkedOperation command)
{
ArgumentNullException.ThrowIfNull(command);
return new RetryParkedOperationDto
{
CorrelationId = command.CorrelationId,
TrackedOperationId = command.TrackedOperationId.ToString()
};
}
/// Rehydrates a relay from the wire.
/// The wire message to decode.
/// The decoded relay command.
public static RetryParkedOperation FromProto(RetryParkedOperationDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new RetryParkedOperation(dto.CorrelationId, TrackedId(dto.TrackedOperationId));
}
/// Projects a relay onto the wire.
/// The relay command to project.
/// The wire message.
public static DiscardParkedOperationDto ToProto(DiscardParkedOperation command)
{
ArgumentNullException.ThrowIfNull(command);
return new DiscardParkedOperationDto
{
CorrelationId = command.CorrelationId,
TrackedOperationId = command.TrackedOperationId.ToString()
};
}
/// Rehydrates a relay from the wire.
/// The wire message to decode.
/// The decoded relay command.
public static DiscardParkedOperation FromProto(DiscardParkedOperationDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new DiscardParkedOperation(dto.CorrelationId, TrackedId(dto.TrackedOperationId));
}
// ── Parked replies ──
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static ParkedMessageQueryResponseDto ToProto(ParkedMessageQueryResponse response)
{
ArgumentNullException.ThrowIfNull(response);
var dto = new ParkedMessageQueryResponseDto
{
CorrelationId = response.CorrelationId,
SiteId = response.SiteId,
TotalCount = response.TotalCount,
PageNumber = response.PageNumber,
PageSize = response.PageSize,
Success = response.Success,
ErrorMessage = OrEmpty(response.ErrorMessage),
Timestamp = Ts(response.Timestamp)
};
dto.Messages.AddRange(response.Messages.Select(ToProto));
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static ParkedMessageQueryResponse FromProto(ParkedMessageQueryResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ParkedMessageQueryResponse(
dto.CorrelationId,
dto.SiteId,
dto.Messages.Select(FromProto).ToList(),
dto.TotalCount,
dto.PageNumber,
dto.PageSize,
dto.Success,
NullIfEmpty(dto.ErrorMessage),
Dto(dto.Timestamp));
}
/// Projects a onto the wire.
/// The parked store-and-forward row to project.
/// The wire message.
public static ParkedMessageEntryDto ToProto(ParkedMessageEntry entry)
{
ArgumentNullException.ThrowIfNull(entry);
return new ParkedMessageEntryDto
{
MessageId = entry.MessageId,
TargetSystem = entry.TargetSystem,
MethodName = entry.MethodName,
ErrorMessage = entry.ErrorMessage,
AttemptCount = entry.AttemptCount,
OriginalTimestamp = Ts(entry.OriginalTimestamp),
LastAttemptTimestamp = Ts(entry.LastAttemptTimestamp),
MaxAttempts = entry.MaxAttempts,
Category = ToProto(entry.Category),
OriginInstance = OrEmpty(entry.OriginInstance)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded parked store-and-forward row.
public static ParkedMessageEntry FromProto(ParkedMessageEntryDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ParkedMessageEntry(
dto.MessageId,
dto.TargetSystem,
dto.MethodName,
dto.ErrorMessage,
dto.AttemptCount,
Dto(dto.OriginalTimestamp),
Dto(dto.LastAttemptTimestamp),
dto.MaxAttempts,
FromProto(dto.Category),
NullIfEmpty(dto.OriginInstance));
}
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static ParkedMessageRetryResponseDto ToProto(ParkedMessageRetryResponse response)
{
ArgumentNullException.ThrowIfNull(response);
return new ParkedMessageRetryResponseDto
{
CorrelationId = response.CorrelationId,
Success = response.Success,
ErrorMessage = OrEmpty(response.ErrorMessage)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static ParkedMessageRetryResponse FromProto(ParkedMessageRetryResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ParkedMessageRetryResponse(dto.CorrelationId, dto.Success, NullIfEmpty(dto.ErrorMessage));
}
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static ParkedMessageDiscardResponseDto ToProto(ParkedMessageDiscardResponse response)
{
ArgumentNullException.ThrowIfNull(response);
return new ParkedMessageDiscardResponseDto
{
CorrelationId = response.CorrelationId,
Success = response.Success,
ErrorMessage = OrEmpty(response.ErrorMessage)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static ParkedMessageDiscardResponse FromProto(ParkedMessageDiscardResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ParkedMessageDiscardResponse(dto.CorrelationId, dto.Success, NullIfEmpty(dto.ErrorMessage));
}
/// Projects a onto the wire.
/// The relay ack to project.
/// The wire message.
public static ParkedOperationActionAckDto ToProto(ParkedOperationActionAck ack)
{
ArgumentNullException.ThrowIfNull(ack);
return new ParkedOperationActionAckDto
{
CorrelationId = ack.CorrelationId,
Applied = ack.Applied,
ErrorMessage = OrEmpty(ack.ErrorMessage)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded relay ack.
public static ParkedOperationActionAck FromProto(ParkedOperationActionAckDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new ParkedOperationActionAck(dto.CorrelationId, dto.Applied, NullIfEmpty(dto.ErrorMessage));
}
// ─────────────────────────────────────────────────────────────────────
// RPC 5 — ExecuteRoute: envelopes
// ─────────────────────────────────────────────────────────────────────
/// Packs a route-group command into its request envelope.
/// One of the four route-group commands.
/// The populated request envelope.
/// The command does not belong to this group.
public static RouteRequest ToRouteRequest(object command)
{
ArgumentNullException.ThrowIfNull(command);
return command switch
{
RouteToCallRequest c => new RouteRequest { RouteToCall = ToProto(c) },
RouteToGetAttributesRequest c => new RouteRequest { RouteToGetAttributes = ToProto(c) },
RouteToSetAttributesRequest c => new RouteRequest { RouteToSetAttributes = ToProto(c) },
RouteToWaitForAttributeRequest c => new RouteRequest { RouteToWaitForAttribute = ToProto(c) },
_ => throw NotInGroup(command, SiteCommandGroup.Route)
};
}
/// Unpacks a route-group request envelope back to its command record.
/// The request envelope received on the wire.
/// The decoded command record.
/// The envelope carries no case this build knows.
public static object FromRouteRequest(RouteRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return request.CommandCase switch
{
RouteRequest.CommandOneofCase.RouteToCall => FromProto(request.RouteToCall),
RouteRequest.CommandOneofCase.RouteToGetAttributes => FromProto(request.RouteToGetAttributes),
RouteRequest.CommandOneofCase.RouteToSetAttributes => FromProto(request.RouteToSetAttributes),
RouteRequest.CommandOneofCase.RouteToWaitForAttribute => FromProto(request.RouteToWaitForAttribute),
_ => throw UnknownCase(request.CommandCase, nameof(RouteRequest))
};
}
/// Packs a route-group reply into its reply envelope.
/// One of the four route-group reply records.
/// The populated reply envelope.
/// The reply does not belong to this group.
public static RouteReply ToRouteReply(object reply)
{
ArgumentNullException.ThrowIfNull(reply);
return reply switch
{
RouteToCallResponse r => new RouteReply { RouteToCall = ToProto(r) },
RouteToGetAttributesResponse r => new RouteReply { RouteToGetAttributes = ToProto(r) },
RouteToSetAttributesResponse r => new RouteReply { RouteToSetAttributes = ToProto(r) },
RouteToWaitForAttributeResponse r => new RouteReply { RouteToWaitForAttribute = ToProto(r) },
_ => throw NotInGroup(reply, SiteCommandGroup.Route)
};
}
/// Unpacks a route-group reply envelope back to its reply record.
/// The reply envelope received on the wire.
/// The decoded reply record.
/// The envelope carries no case this build knows.
public static object FromRouteReply(RouteReply reply)
{
ArgumentNullException.ThrowIfNull(reply);
return reply.ReplyCase switch
{
RouteReply.ReplyOneofCase.RouteToCall => FromProto(reply.RouteToCall),
RouteReply.ReplyOneofCase.RouteToGetAttributes => FromProto(reply.RouteToGetAttributes),
RouteReply.ReplyOneofCase.RouteToSetAttributes => FromProto(reply.RouteToSetAttributes),
RouteReply.ReplyOneofCase.RouteToWaitForAttribute => FromProto(reply.RouteToWaitForAttribute),
_ => throw UnknownCase(reply.ReplyCase, nameof(RouteReply))
};
}
// ── Route commands ──
/// Projects a onto the wire.
/// The request to project.
/// The wire message.
public static RouteToCallRequestDto ToProto(RouteToCallRequest request)
{
ArgumentNullException.ThrowIfNull(request);
return new RouteToCallRequestDto
{
CorrelationId = request.CorrelationId,
InstanceUniqueName = request.InstanceUniqueName,
ScriptName = request.ScriptName,
Parameters = LooseValueCodec.ToProtoMapOrNull(request.Parameters),
Timestamp = Ts(request.Timestamp),
ParentExecutionId = GuidToWire(request.ParentExecutionId),
Trace = request.Trace
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded request record.
public static RouteToCallRequest FromProto(RouteToCallRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new RouteToCallRequest(
dto.CorrelationId,
dto.InstanceUniqueName,
dto.ScriptName,
LooseValueCodec.FromProtoMapOrNull(dto.Parameters),
Dto(dto.Timestamp),
GuidFromWire(dto.ParentExecutionId),
dto.Trace);
}
/// Projects a onto the wire.
/// The request to project.
/// The wire message.
public static RouteToGetAttributesRequestDto ToProto(RouteToGetAttributesRequest request)
{
ArgumentNullException.ThrowIfNull(request);
var dto = new RouteToGetAttributesRequestDto
{
CorrelationId = request.CorrelationId,
InstanceUniqueName = request.InstanceUniqueName,
Timestamp = Ts(request.Timestamp),
ParentExecutionId = GuidToWire(request.ParentExecutionId)
};
dto.AttributeNames.AddRange(request.AttributeNames);
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded request record.
public static RouteToGetAttributesRequest FromProto(RouteToGetAttributesRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new RouteToGetAttributesRequest(
dto.CorrelationId,
dto.InstanceUniqueName,
dto.AttributeNames.ToList(),
Dto(dto.Timestamp),
GuidFromWire(dto.ParentExecutionId));
}
/// Projects a onto the wire.
/// The request to project.
/// The wire message.
public static RouteToSetAttributesRequestDto ToProto(RouteToSetAttributesRequest request)
{
ArgumentNullException.ThrowIfNull(request);
var dto = new RouteToSetAttributesRequestDto
{
CorrelationId = request.CorrelationId,
InstanceUniqueName = request.InstanceUniqueName,
Timestamp = Ts(request.Timestamp),
ParentExecutionId = GuidToWire(request.ParentExecutionId),
Trace = request.Trace
};
foreach (var (key, value) in request.AttributeValues)
{
dto.AttributeValues[key] = value;
}
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded request record.
public static RouteToSetAttributesRequest FromProto(RouteToSetAttributesRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new RouteToSetAttributesRequest(
dto.CorrelationId,
dto.InstanceUniqueName,
dto.AttributeValues.ToDictionary(kv => kv.Key, kv => kv.Value),
Dto(dto.Timestamp),
GuidFromWire(dto.ParentExecutionId),
dto.Trace);
}
/// Projects a onto the wire.
/// The request to project.
/// The wire message.
public static RouteToWaitForAttributeRequestDto ToProto(RouteToWaitForAttributeRequest request)
{
ArgumentNullException.ThrowIfNull(request);
var dto = new RouteToWaitForAttributeRequestDto
{
CorrelationId = request.CorrelationId,
InstanceUniqueName = request.InstanceUniqueName,
AttributeName = request.AttributeName,
Timeout = Duration.FromTimeSpan(request.Timeout),
Timestamp = Ts(request.Timestamp),
ParentExecutionId = GuidToWire(request.ParentExecutionId),
RequireGoodQuality = request.RequireGoodQuality
};
if (request.TargetValueEncoded is not null)
{
dto.TargetValueEncoded = request.TargetValueEncoded;
}
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded request record.
public static RouteToWaitForAttributeRequest FromProto(RouteToWaitForAttributeRequestDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new RouteToWaitForAttributeRequest(
dto.CorrelationId,
dto.InstanceUniqueName,
dto.AttributeName,
dto.TargetValueEncoded,
dto.Timeout?.ToTimeSpan() ?? TimeSpan.Zero,
Dto(dto.Timestamp),
GuidFromWire(dto.ParentExecutionId),
dto.RequireGoodQuality);
}
// ── Route replies ──
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static RouteToCallResponseDto ToProto(RouteToCallResponse response)
{
ArgumentNullException.ThrowIfNull(response);
var dto = new RouteToCallResponseDto
{
CorrelationId = response.CorrelationId,
Success = response.Success,
ReturnValue = LooseValueCodec.ToProtoOrNull(response.ReturnValue),
ErrorMessage = OrEmpty(response.ErrorMessage),
Timestamp = Ts(response.Timestamp),
// Flow Debugger (design 2026-09-05 §5.4): additive fields 6/7.
ChildExecutionId = GuidToWire(response.ChildExecutionId),
// Gitea #54: additive fields 8/9 — the site collector's truncation flags.
Truncated = response.Truncated,
LogsTruncated = response.LogsTruncated
};
if (response.TraceEvents is { Count: > 0 } traceEvents)
{
foreach (var evt in traceEvents)
{
dto.TraceEvents.Add(AuditEventDtoMapper.ToDto(evt));
}
}
return dto;
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static RouteToCallResponse FromProto(RouteToCallResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new RouteToCallResponse(
dto.CorrelationId,
dto.Success,
LooseValueCodec.FromProto(dto.ReturnValue),
NullIfEmpty(dto.ErrorMessage),
Dto(dto.Timestamp),
GuidFromWire(dto.ChildExecutionId),
// An untraced run — or an older site that never sets the field — sends no
// events, which maps to null so the UI can label the lane "site not traced".
dto.TraceEvents.Count == 0 ? null : dto.TraceEvents.Select(AuditEventDtoMapper.FromDto).ToList(),
dto.Truncated,
dto.LogsTruncated);
}
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static RouteToGetAttributesResponseDto ToProto(RouteToGetAttributesResponse response)
{
ArgumentNullException.ThrowIfNull(response);
return new RouteToGetAttributesResponseDto
{
CorrelationId = response.CorrelationId,
Values = LooseValueCodec.ToProtoMap(response.Values),
Success = response.Success,
ErrorMessage = OrEmpty(response.ErrorMessage),
Timestamp = Ts(response.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static RouteToGetAttributesResponse FromProto(RouteToGetAttributesResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new RouteToGetAttributesResponse(
dto.CorrelationId,
LooseValueCodec.FromProtoMap(dto.Values),
dto.Success,
NullIfEmpty(dto.ErrorMessage),
Dto(dto.Timestamp));
}
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static RouteToSetAttributesResponseDto ToProto(RouteToSetAttributesResponse response)
{
ArgumentNullException.ThrowIfNull(response);
return new RouteToSetAttributesResponseDto
{
CorrelationId = response.CorrelationId,
Success = response.Success,
ErrorMessage = OrEmpty(response.ErrorMessage),
Timestamp = Ts(response.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static RouteToSetAttributesResponse FromProto(RouteToSetAttributesResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new RouteToSetAttributesResponse(
dto.CorrelationId, dto.Success, NullIfEmpty(dto.ErrorMessage), Dto(dto.Timestamp));
}
/// Projects a onto the wire.
/// The reply to project.
/// The wire message.
public static RouteToWaitForAttributeResponseDto ToProto(RouteToWaitForAttributeResponse response)
{
ArgumentNullException.ThrowIfNull(response);
return new RouteToWaitForAttributeResponseDto
{
CorrelationId = response.CorrelationId,
Matched = response.Matched,
Value = LooseValueCodec.ToProtoOrNull(response.Value),
Quality = OrEmpty(response.Quality),
TimedOut = response.TimedOut,
Success = response.Success,
ErrorMessage = OrEmpty(response.ErrorMessage),
Timestamp = Ts(response.Timestamp)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded reply record.
public static RouteToWaitForAttributeResponse FromProto(RouteToWaitForAttributeResponseDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new RouteToWaitForAttributeResponse(
dto.CorrelationId,
dto.Matched,
LooseValueCodec.FromProto(dto.Value),
NullIfEmpty(dto.Quality),
dto.TimedOut,
dto.Success,
NullIfEmpty(dto.ErrorMessage),
Dto(dto.Timestamp));
}
// ─────────────────────────────────────────────────────────────────────
// RPC 6 — TriggerFailover
// ─────────────────────────────────────────────────────────────────────
/// Projects a onto the wire.
/// The relay command to project.
/// The wire message.
public static TriggerSiteFailoverDto ToProto(TriggerSiteFailover command)
{
ArgumentNullException.ThrowIfNull(command);
return new TriggerSiteFailoverDto
{
CorrelationId = command.CorrelationId,
SiteId = command.SiteId
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded relay command.
public static TriggerSiteFailover FromProto(TriggerSiteFailoverDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new TriggerSiteFailover(dto.CorrelationId, dto.SiteId);
}
/// Projects a onto the wire.
/// The relay ack to project.
/// The wire message.
public static SiteFailoverAckDto ToProto(SiteFailoverAck ack)
{
ArgumentNullException.ThrowIfNull(ack);
return new SiteFailoverAckDto
{
CorrelationId = ack.CorrelationId,
Accepted = ack.Accepted,
TargetAddress = OrEmpty(ack.TargetAddress),
ErrorMessage = OrEmpty(ack.ErrorMessage)
};
}
/// Rehydrates a from the wire.
/// The wire message to decode.
/// The decoded relay ack.
public static SiteFailoverAck FromProto(SiteFailoverAckDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
return new SiteFailoverAck(
dto.CorrelationId,
dto.Accepted,
NullIfEmpty(dto.TargetAddress),
NullIfEmpty(dto.ErrorMessage));
}
// ─────────────────────────────────────────────────────────────────────
// Enum translation — explicit both ways, never by ordinal.
// ─────────────────────────────────────────────────────────────────────
/// Projects a onto its wire enum.
/// The status to project.
/// The wire enum value.
public static DeploymentStatusDto ToProto(DeploymentStatus status) => status switch
{
DeploymentStatus.Pending => DeploymentStatusDto.Pending,
DeploymentStatus.InProgress => DeploymentStatusDto.InProgress,
DeploymentStatus.Success => DeploymentStatusDto.Success,
DeploymentStatus.Failed => DeploymentStatusDto.Failed,
_ => DeploymentStatusDto.Unspecified
};
/// Rehydrates a ; unknown decodes to Failed (never claim success).
/// The wire enum value.
/// The decoded status.
public static DeploymentStatus FromProto(DeploymentStatusDto status) => status switch
{
DeploymentStatusDto.Pending => DeploymentStatus.Pending,
DeploymentStatusDto.InProgress => DeploymentStatus.InProgress,
DeploymentStatusDto.Success => DeploymentStatus.Success,
_ => DeploymentStatus.Failed
};
/// Projects a onto its wire enum.
/// The node class to project.
/// The wire enum value.
public static BrowseNodeClassDto ToProto(BrowseNodeClass nodeClass) => nodeClass switch
{
BrowseNodeClass.Object => BrowseNodeClassDto.Object,
BrowseNodeClass.Variable => BrowseNodeClassDto.Variable,
BrowseNodeClass.Method => BrowseNodeClassDto.Method,
BrowseNodeClass.Other => BrowseNodeClassDto.Other,
_ => BrowseNodeClassDto.Unspecified
};
/// Rehydrates a ; unknown decodes to Other.
/// The wire enum value.
/// The decoded node class.
public static BrowseNodeClass FromProto(BrowseNodeClassDto nodeClass) => nodeClass switch
{
BrowseNodeClassDto.Object => BrowseNodeClass.Object,
BrowseNodeClassDto.Variable => BrowseNodeClass.Variable,
BrowseNodeClassDto.Method => BrowseNodeClass.Method,
_ => BrowseNodeClass.Other
};
/// Projects a onto its wire enum.
/// The failure kind to project.
/// The wire enum value.
public static BrowseFailureKindDto ToProto(BrowseFailureKind kind) => kind switch
{
BrowseFailureKind.ConnectionNotFound => BrowseFailureKindDto.ConnectionNotFound,
BrowseFailureKind.ConnectionNotConnected => BrowseFailureKindDto.ConnectionNotConnected,
BrowseFailureKind.NotBrowsable => BrowseFailureKindDto.NotBrowsable,
BrowseFailureKind.Timeout => BrowseFailureKindDto.Timeout,
BrowseFailureKind.ServerError => BrowseFailureKindDto.ServerError,
_ => BrowseFailureKindDto.Unspecified
};
/// Rehydrates a ; unknown decodes to ServerError.
/// The wire enum value.
/// The decoded failure kind.
public static BrowseFailureKind FromProto(BrowseFailureKindDto kind) => kind switch
{
BrowseFailureKindDto.ConnectionNotFound => BrowseFailureKind.ConnectionNotFound,
BrowseFailureKindDto.ConnectionNotConnected => BrowseFailureKind.ConnectionNotConnected,
BrowseFailureKindDto.NotBrowsable => BrowseFailureKind.NotBrowsable,
BrowseFailureKindDto.Timeout => BrowseFailureKind.Timeout,
_ => BrowseFailureKind.ServerError
};
/// Projects a onto its wire enum.
/// The failure kind to project.
/// The wire enum value.
public static ReadTagValuesFailureKindDto ToProto(ReadTagValuesFailureKind kind) => kind switch
{
ReadTagValuesFailureKind.ConnectionNotFound => ReadTagValuesFailureKindDto.ConnectionNotFound,
ReadTagValuesFailureKind.ConnectionNotConnected => ReadTagValuesFailureKindDto.ConnectionNotConnected,
ReadTagValuesFailureKind.Timeout => ReadTagValuesFailureKindDto.Timeout,
ReadTagValuesFailureKind.ServerError => ReadTagValuesFailureKindDto.ServerError,
_ => ReadTagValuesFailureKindDto.Unspecified
};
/// Rehydrates a ; unknown decodes to ServerError.
/// The wire enum value.
/// The decoded failure kind.
public static ReadTagValuesFailureKind FromProto(ReadTagValuesFailureKindDto kind) => kind switch
{
ReadTagValuesFailureKindDto.ConnectionNotFound => ReadTagValuesFailureKind.ConnectionNotFound,
ReadTagValuesFailureKindDto.ConnectionNotConnected => ReadTagValuesFailureKind.ConnectionNotConnected,
ReadTagValuesFailureKindDto.Timeout => ReadTagValuesFailureKind.Timeout,
_ => ReadTagValuesFailureKind.ServerError
};
/// Projects a onto its wire enum.
/// The failure kind to project.
/// The wire enum value.
public static VerifyFailureKindDto ToProto(VerifyFailureKind kind) => kind switch
{
VerifyFailureKind.Unreachable => VerifyFailureKindDto.Unreachable,
VerifyFailureKind.AuthFailed => VerifyFailureKindDto.AuthFailed,
VerifyFailureKind.UntrustedCertificate => VerifyFailureKindDto.UntrustedCertificate,
VerifyFailureKind.Timeout => VerifyFailureKindDto.Timeout,
VerifyFailureKind.ServerError => VerifyFailureKindDto.ServerError,
_ => VerifyFailureKindDto.Unspecified
};
/// Rehydrates a ; unknown decodes to ServerError.
/// The wire enum value.
/// The decoded failure kind.
public static VerifyFailureKind FromProto(VerifyFailureKindDto kind) => kind switch
{
VerifyFailureKindDto.Unreachable => VerifyFailureKind.Unreachable,
VerifyFailureKindDto.AuthFailed => VerifyFailureKind.AuthFailed,
VerifyFailureKindDto.UntrustedCertificate => VerifyFailureKind.UntrustedCertificate,
VerifyFailureKindDto.Timeout => VerifyFailureKind.Timeout,
_ => VerifyFailureKind.ServerError
};
/// Projects a onto its wire enum.
/// The category to project.
/// The wire enum value.
public static StoreAndForwardCategoryDto ToProto(StoreAndForwardCategory category) => category switch
{
StoreAndForwardCategory.ExternalSystem => StoreAndForwardCategoryDto.ExternalSystem,
StoreAndForwardCategory.Notification => StoreAndForwardCategoryDto.Notification,
StoreAndForwardCategory.CachedDbWrite => StoreAndForwardCategoryDto.CachedDbWrite,
_ => StoreAndForwardCategoryDto.Unspecified
};
/// Rehydrates a ; unknown decodes to ExternalSystem (the DTO's own default).
/// The wire enum value.
/// The decoded category.
public static StoreAndForwardCategory FromProto(StoreAndForwardCategoryDto category) => category switch
{
StoreAndForwardCategoryDto.Notification => StoreAndForwardCategory.Notification,
StoreAndForwardCategoryDto.CachedDbWrite => StoreAndForwardCategory.CachedDbWrite,
_ => StoreAndForwardCategory.ExternalSystem
};
/// Projects an onto its wire enum.
/// The alarm state to project.
/// The wire enum value.
public static AlarmStateDto ToProto(AlarmState state) => state switch
{
AlarmState.Active => AlarmStateDto.Active,
AlarmState.Normal => AlarmStateDto.Normal,
_ => AlarmStateDto.Unspecified
};
/// Rehydrates an ; unknown decodes to Normal (never invent an alarm).
/// The wire enum value.
/// The decoded alarm state.
public static AlarmState FromProto(AlarmStateDto state) =>
state == AlarmStateDto.Active ? AlarmState.Active : AlarmState.Normal;
/// Projects an onto its wire enum.
/// The alarm level to project.
/// The wire enum value.
public static AlarmLevelDto ToProto(AlarmLevel level) => level switch
{
AlarmLevel.None => AlarmLevelDto.None,
AlarmLevel.Low => AlarmLevelDto.Low,
AlarmLevel.LowLow => AlarmLevelDto.LowLow,
AlarmLevel.High => AlarmLevelDto.High,
AlarmLevel.HighHigh => AlarmLevelDto.HighHigh,
_ => AlarmLevelDto.Unspecified
};
/// Rehydrates an ; unknown decodes to None.
/// The wire enum value.
/// The decoded alarm level.
public static AlarmLevel FromProto(AlarmLevelDto level) => level switch
{
AlarmLevelDto.Low => AlarmLevel.Low,
AlarmLevelDto.LowLow => AlarmLevel.LowLow,
AlarmLevelDto.High => AlarmLevel.High,
AlarmLevelDto.HighHigh => AlarmLevel.HighHigh,
_ => AlarmLevel.None
};
/// Projects an onto its wire enum.
/// The alarm kind to project.
/// The wire enum value.
public static AlarmKindDto ToProto(AlarmKind kind) => kind switch
{
AlarmKind.Computed => AlarmKindDto.Computed,
AlarmKind.NativeOpcUa => AlarmKindDto.NativeOpcUa,
AlarmKind.NativeMxAccess => AlarmKindDto.NativeMxAccess,
_ => AlarmKindDto.Unspecified
};
/// Rehydrates an ; unknown decodes to Computed (the record's own default).
/// The wire enum value.
/// The decoded alarm kind.
public static AlarmKind FromProto(AlarmKindDto kind) => kind switch
{
AlarmKindDto.NativeOpcUa => AlarmKind.NativeOpcUa,
AlarmKindDto.NativeMxAccess => AlarmKind.NativeMxAccess,
_ => AlarmKind.Computed
};
/// Projects an onto its wire enum.
/// The shelve state to project.
/// The wire enum value.
public static AlarmShelveStateDto ToProto(AlarmShelveState shelve) => shelve switch
{
AlarmShelveState.Unshelved => AlarmShelveStateDto.Unshelved,
AlarmShelveState.OneShotShelved => AlarmShelveStateDto.OneShotShelved,
AlarmShelveState.TimedShelved => AlarmShelveStateDto.TimedShelved,
AlarmShelveState.PermanentShelved => AlarmShelveStateDto.PermanentShelved,
_ => AlarmShelveStateDto.Unspecified
};
///
/// Rehydrates an ; unknown decodes to
/// Unshelved — the same safe default
/// applies on the streaming path.
///
/// The wire enum value.
/// The decoded shelve state.
public static AlarmShelveState FromProto(AlarmShelveStateDto shelve) => shelve switch
{
AlarmShelveStateDto.OneShotShelved => AlarmShelveState.OneShotShelved,
AlarmShelveStateDto.TimedShelved => AlarmShelveState.TimedShelved,
AlarmShelveStateDto.PermanentShelved => AlarmShelveState.PermanentShelved,
_ => AlarmShelveState.Unshelved
};
// ─────────────────────────────────────────────────────────────────────
// Primitive helpers
// ─────────────────────────────────────────────────────────────────────
private static Timestamp Ts(DateTimeOffset value) => Timestamp.FromDateTimeOffset(value);
private static DateTimeOffset Dto(Timestamp? value) =>
value?.ToDateTimeOffset() ?? default;
private static DateTimeOffset? DtoOrNull(Timestamp? value) =>
value?.ToDateTimeOffset();
private static Timestamp TsUtc(DateTime value) => Timestamp.FromDateTime(EnsureUtc(value));
private static DateTime DtUtc(Timestamp? value) => value?.ToDateTime() ?? default;
private static DateTime EnsureUtc(DateTime value) =>
value.Kind == DateTimeKind.Utc
? value
: DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc);
private static string OrEmpty(string? value) => value ?? string.Empty;
private static string? NullIfEmpty(string value) => string.IsNullOrEmpty(value) ? null : value;
private static string GuidToWire(Guid? value) => value?.ToString("D") ?? string.Empty;
private static Guid? GuidFromWire(string value) =>
Guid.TryParse(value, out var parsed) ? parsed : null;
private static TrackedOperationId TrackedId(string value) =>
TrackedOperationId.TryParse(value, out var parsed) ? parsed : default;
private static ArgumentException NotInGroup(object message, SiteCommandGroup group) =>
new($"'{message.GetType().Name}' is not part of the {group} RPC group.", nameof(message));
private static NotSupportedException UnknownCase(TCase oneofCase, string envelope)
where TCase : struct, Enum =>
new($"{envelope} carries oneof case '{oneofCase}', which this build does not understand. "
+ "The peer is running a newer contract (message evolution is additive-only).");
}
///
/// Marker reply for the fire-and-forget UnsubscribeDebugView command.
///
///
/// The Akka path Tells the unsubscribe and expects nothing back, but a unary RPC
/// must answer something. This singleton is what the dispatcher returns and what
/// produces, so the two
/// transports present the same shape; the central transport discards it and keeps
/// the caller-visible fire-and-forget semantics.
///
public sealed record UnsubscribeDebugViewAck
{
private UnsubscribeDebugViewAck()
{
}
/// The single instance.
public static UnsubscribeDebugViewAck Instance { get; } = new();
}