From 2b54290c7f37c95f59cc0a981dfd184e890275c5 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 20 May 2026 14:24:13 -0400 Subject: [PATCH] feat(comms): IngestCachedTelemetry RPC + CachedTelemetryPacket proto (#23 M3) --- .../Protos/sitestream.proto | 26 + .../SiteStreamGrpc/Sitestream.cs | 1110 ++++++++++++++++- .../SiteStreamGrpc/SitestreamGrpc.cs | 40 +- .../Protos/CachedTelemetryProtoTests.cs | 173 +++ 4 files changed, 1334 insertions(+), 15 deletions(-) create mode 100644 tests/ScadaLink.Communication.Tests/Protos/CachedTelemetryProtoTests.cs diff --git a/src/ScadaLink.Communication/Protos/sitestream.proto b/src/ScadaLink.Communication/Protos/sitestream.proto index d01852f..43ffbe3 100644 --- a/src/ScadaLink.Communication/Protos/sitestream.proto +++ b/src/ScadaLink.Communication/Protos/sitestream.proto @@ -8,6 +8,7 @@ import "google/protobuf/wrappers.proto"; // Int32Value service SiteStreamService { rpc SubscribeInstance(InstanceStreamRequest) returns (stream SiteStreamEvent); rpc IngestAuditEvents(AuditEventBatch) returns (IngestAck); + rpc IngestCachedTelemetry(CachedTelemetryBatch) returns (IngestAck); } message InstanceStreamRequest { @@ -93,3 +94,28 @@ message AuditEventDto { message AuditEventBatch { repeated AuditEventDto events = 1; } message IngestAck { repeated string accepted_event_ids = 1; } + +// Audit Log (#23) M3 cached-call combined telemetry: a single packet carries +// both the AuditEvent row to insert and the SiteCalls operational-state upsert +// for one lifecycle event of a cached outbound call. Central writes both rows +// in one MS SQL transaction so the audit and operational mirrors never drift. +message SiteCallOperationalDto { + string tracked_operation_id = 1; // GUID string ("D" format) + string channel = 2; // "ApiOutbound" | "DbOutbound" + string target = 3; + string source_site = 4; + string status = 5; // AuditStatus name + int32 retry_count = 6; + string last_error = 7; // empty when null + google.protobuf.Int32Value http_status = 8; + google.protobuf.Timestamp created_at_utc = 9; + google.protobuf.Timestamp updated_at_utc = 10; + google.protobuf.Timestamp terminal_at_utc = 11; // absent when not terminal +} + +message CachedTelemetryPacket { + AuditEventDto audit_event = 1; + SiteCallOperationalDto operational = 2; +} + +message CachedTelemetryBatch { repeated CachedTelemetryPacket packets = 1; } diff --git a/src/ScadaLink.Communication/SiteStreamGrpc/Sitestream.cs b/src/ScadaLink.Communication/SiteStreamGrpc/Sitestream.cs index 3a843eb..9639242 100644 --- a/src/ScadaLink.Communication/SiteStreamGrpc/Sitestream.cs +++ b/src/ScadaLink.Communication/SiteStreamGrpc/Sitestream.cs @@ -55,19 +55,34 @@ namespace ScadaLink.Communication.Grpc { "cGF5bG9hZF90cnVuY2F0ZWQYEiABKAgSDQoFZXh0cmEYEyABKAkiPAoPQXVk", "aXRFdmVudEJhdGNoEikKBmV2ZW50cxgBIAMoCzIZLnNpdGVzdHJlYW0uQXVk", "aXRFdmVudER0byInCglJbmdlc3RBY2sSGgoSYWNjZXB0ZWRfZXZlbnRfaWRz", - "GAEgAygJKlwKB1F1YWxpdHkSFwoTUVVBTElUWV9VTlNQRUNJRklFRBAAEhAK", - "DFFVQUxJVFlfR09PRBABEhUKEVFVQUxJVFlfVU5DRVJUQUlOEAISDwoLUVVB", - "TElUWV9CQUQQAypdCg5BbGFybVN0YXRlRW51bRIbChdBTEFSTV9TVEFURV9V", - "TlNQRUNJRklFRBAAEhYKEkFMQVJNX1NUQVRFX05PUk1BTBABEhYKEkFMQVJN", - "X1NUQVRFX0FDVElWRRACKoUBCg5BbGFybUxldmVsRW51bRIUChBBTEFSTV9M", - "RVZFTF9OT05FEAASEwoPQUxBUk1fTEVWRUxfTE9XEAESFwoTQUxBUk1fTEVW", - "RUxfTE9XX0xPVxACEhQKEEFMQVJNX0xFVkVMX0hJR0gQAxIZChVBTEFSTV9M", - "RVZFTF9ISUdIX0hJR0gQBDKzAQoRU2l0ZVN0cmVhbVNlcnZpY2USVQoRU3Vi", - "c2NyaWJlSW5zdGFuY2USIS5zaXRlc3RyZWFtLkluc3RhbmNlU3RyZWFtUmVx", - "dWVzdBobLnNpdGVzdHJlYW0uU2l0ZVN0cmVhbUV2ZW50MAESRwoRSW5nZXN0", - "QXVkaXRFdmVudHMSGy5zaXRlc3RyZWFtLkF1ZGl0RXZlbnRCYXRjaBoVLnNp", - "dGVzdHJlYW0uSW5nZXN0QWNrQh+qAhxTY2FkYUxpbmsuQ29tbXVuaWNhdGlv", - "bi5HcnBjYgZwcm90bzM=")); + "GAEgAygJIvQCChZTaXRlQ2FsbE9wZXJhdGlvbmFsRHRvEhwKFHRyYWNrZWRf", + "b3BlcmF0aW9uX2lkGAEgASgJEg8KB2NoYW5uZWwYAiABKAkSDgoGdGFyZ2V0", + "GAMgASgJEhMKC3NvdXJjZV9zaXRlGAQgASgJEg4KBnN0YXR1cxgFIAEoCRIT", + "CgtyZXRyeV9jb3VudBgGIAEoBRISCgpsYXN0X2Vycm9yGAcgASgJEjAKC2h0", + "dHBfc3RhdHVzGAggASgLMhsuZ29vZ2xlLnByb3RvYnVmLkludDMyVmFsdWUS", + "MgoOY3JlYXRlZF9hdF91dGMYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGlt", + "ZXN0YW1wEjIKDnVwZGF0ZWRfYXRfdXRjGAogASgLMhouZ29vZ2xlLnByb3Rv", + "YnVmLlRpbWVzdGFtcBIzCg90ZXJtaW5hbF9hdF91dGMYCyABKAsyGi5nb29n", + "bGUucHJvdG9idWYuVGltZXN0YW1wIoABChVDYWNoZWRUZWxlbWV0cnlQYWNr", + "ZXQSLgoLYXVkaXRfZXZlbnQYASABKAsyGS5zaXRlc3RyZWFtLkF1ZGl0RXZl", + "bnREdG8SNwoLb3BlcmF0aW9uYWwYAiABKAsyIi5zaXRlc3RyZWFtLlNpdGVD", + "YWxsT3BlcmF0aW9uYWxEdG8iSgoUQ2FjaGVkVGVsZW1ldHJ5QmF0Y2gSMgoH", + "cGFja2V0cxgBIAMoCzIhLnNpdGVzdHJlYW0uQ2FjaGVkVGVsZW1ldHJ5UGFj", + "a2V0KlwKB1F1YWxpdHkSFwoTUVVBTElUWV9VTlNQRUNJRklFRBAAEhAKDFFV", + "QUxJVFlfR09PRBABEhUKEVFVQUxJVFlfVU5DRVJUQUlOEAISDwoLUVVBTElU", + "WV9CQUQQAypdCg5BbGFybVN0YXRlRW51bRIbChdBTEFSTV9TVEFURV9VTlNQ", + "RUNJRklFRBAAEhYKEkFMQVJNX1NUQVRFX05PUk1BTBABEhYKEkFMQVJNX1NU", + "QVRFX0FDVElWRRACKoUBCg5BbGFybUxldmVsRW51bRIUChBBTEFSTV9MRVZF", + "TF9OT05FEAASEwoPQUxBUk1fTEVWRUxfTE9XEAESFwoTQUxBUk1fTEVWRUxf", + "TE9XX0xPVxACEhQKEEFMQVJNX0xFVkVMX0hJR0gQAxIZChVBTEFSTV9MRVZF", + "TF9ISUdIX0hJR0gQBDKFAgoRU2l0ZVN0cmVhbVNlcnZpY2USVQoRU3Vic2Ny", + "aWJlSW5zdGFuY2USIS5zaXRlc3RyZWFtLkluc3RhbmNlU3RyZWFtUmVxdWVz", + "dBobLnNpdGVzdHJlYW0uU2l0ZVN0cmVhbUV2ZW50MAESRwoRSW5nZXN0QXVk", + "aXRFdmVudHMSGy5zaXRlc3RyZWFtLkF1ZGl0RXZlbnRCYXRjaBoVLnNpdGVz", + "dHJlYW0uSW5nZXN0QWNrElAKFUluZ2VzdENhY2hlZFRlbGVtZXRyeRIgLnNp", + "dGVzdHJlYW0uQ2FjaGVkVGVsZW1ldHJ5QmF0Y2gaFS5zaXRlc3RyZWFtLklu", + "Z2VzdEFja0IfqgIcU2NhZGFMaW5rLkNvbW11bmljYXRpb24uR3JwY2IGcHJv", + "dG8z")); descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.TimestampReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.WrappersReflection.Descriptor, }, new pbr::GeneratedClrTypeInfo(new[] {typeof(global::ScadaLink.Communication.Grpc.Quality), typeof(global::ScadaLink.Communication.Grpc.AlarmStateEnum), typeof(global::ScadaLink.Communication.Grpc.AlarmLevelEnum), }, null, new pbr::GeneratedClrTypeInfo[] { @@ -77,7 +92,10 @@ namespace ScadaLink.Communication.Grpc { new pbr::GeneratedClrTypeInfo(typeof(global::ScadaLink.Communication.Grpc.AlarmStateUpdate), global::ScadaLink.Communication.Grpc.AlarmStateUpdate.Parser, new[]{ "InstanceUniqueName", "AlarmName", "State", "Priority", "Timestamp", "Level", "Message" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::ScadaLink.Communication.Grpc.AuditEventDto), global::ScadaLink.Communication.Grpc.AuditEventDto.Parser, new[]{ "EventId", "OccurredAtUtc", "Channel", "Kind", "CorrelationId", "SourceSiteId", "SourceInstanceId", "SourceScript", "Actor", "Target", "Status", "HttpStatus", "DurationMs", "ErrorMessage", "ErrorDetail", "RequestSummary", "ResponseSummary", "PayloadTruncated", "Extra" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::ScadaLink.Communication.Grpc.AuditEventBatch), global::ScadaLink.Communication.Grpc.AuditEventBatch.Parser, new[]{ "Events" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::ScadaLink.Communication.Grpc.IngestAck), global::ScadaLink.Communication.Grpc.IngestAck.Parser, new[]{ "AcceptedEventIds" }, null, null, null, null) + new pbr::GeneratedClrTypeInfo(typeof(global::ScadaLink.Communication.Grpc.IngestAck), global::ScadaLink.Communication.Grpc.IngestAck.Parser, new[]{ "AcceptedEventIds" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::ScadaLink.Communication.Grpc.SiteCallOperationalDto), global::ScadaLink.Communication.Grpc.SiteCallOperationalDto.Parser, new[]{ "TrackedOperationId", "Channel", "Target", "SourceSite", "Status", "RetryCount", "LastError", "HttpStatus", "CreatedAtUtc", "UpdatedAtUtc", "TerminalAtUtc" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::ScadaLink.Communication.Grpc.CachedTelemetryPacket), global::ScadaLink.Communication.Grpc.CachedTelemetryPacket.Parser, new[]{ "AuditEvent", "Operational" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::ScadaLink.Communication.Grpc.CachedTelemetryBatch), global::ScadaLink.Communication.Grpc.CachedTelemetryBatch.Parser, new[]{ "Packets" }, null, null, null, null) })); } #endregion @@ -2780,6 +2798,1070 @@ namespace ScadaLink.Communication.Grpc { } + /// + /// Audit Log (#23) M3 cached-call combined telemetry: a single packet carries + /// both the AuditEvent row to insert and the SiteCalls operational-state upsert + /// for one lifecycle event of a cached outbound call. Central writes both rows + /// in one MS SQL transaction so the audit and operational mirrors never drift. + /// + [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")] + public sealed partial class SiteCallOperationalDto : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SiteCallOperationalDto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::ScadaLink.Communication.Grpc.SitestreamReflection.Descriptor.MessageTypes[7]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public SiteCallOperationalDto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public SiteCallOperationalDto(SiteCallOperationalDto other) : this() { + trackedOperationId_ = other.trackedOperationId_; + channel_ = other.channel_; + target_ = other.target_; + sourceSite_ = other.sourceSite_; + status_ = other.status_; + retryCount_ = other.retryCount_; + lastError_ = other.lastError_; + HttpStatus = other.HttpStatus; + createdAtUtc_ = other.createdAtUtc_ != null ? other.createdAtUtc_.Clone() : null; + updatedAtUtc_ = other.updatedAtUtc_ != null ? other.updatedAtUtc_.Clone() : null; + terminalAtUtc_ = other.terminalAtUtc_ != null ? other.terminalAtUtc_.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public SiteCallOperationalDto Clone() { + return new SiteCallOperationalDto(this); + } + + /// Field number for the "tracked_operation_id" field. + public const int TrackedOperationIdFieldNumber = 1; + private string trackedOperationId_ = ""; + /// + /// GUID string ("D" format) + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string TrackedOperationId { + get { return trackedOperationId_; } + set { + trackedOperationId_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "channel" field. + public const int ChannelFieldNumber = 2; + private string channel_ = ""; + /// + /// "ApiOutbound" | "DbOutbound" + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string Channel { + get { return channel_; } + set { + channel_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "target" field. + public const int TargetFieldNumber = 3; + private string target_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string Target { + get { return target_; } + set { + target_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "source_site" field. + public const int SourceSiteFieldNumber = 4; + private string sourceSite_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string SourceSite { + get { return sourceSite_; } + set { + sourceSite_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "status" field. + public const int StatusFieldNumber = 5; + private string status_ = ""; + /// + /// AuditStatus name + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string Status { + get { return status_; } + set { + status_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "retry_count" field. + public const int RetryCountFieldNumber = 6; + private int retryCount_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int RetryCount { + get { return retryCount_; } + set { + retryCount_ = value; + } + } + + /// Field number for the "last_error" field. + public const int LastErrorFieldNumber = 7; + private string lastError_ = ""; + /// + /// empty when null + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string LastError { + get { return lastError_; } + set { + lastError_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "http_status" field. + public const int HttpStatusFieldNumber = 8; + private static readonly pb::FieldCodec _single_httpStatus_codec = pb::FieldCodec.ForStructWrapper(66); + private int? httpStatus_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int? HttpStatus { + get { return httpStatus_; } + set { + httpStatus_ = value; + } + } + + + /// Field number for the "created_at_utc" field. + public const int CreatedAtUtcFieldNumber = 9; + private global::Google.Protobuf.WellKnownTypes.Timestamp createdAtUtc_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::Google.Protobuf.WellKnownTypes.Timestamp CreatedAtUtc { + get { return createdAtUtc_; } + set { + createdAtUtc_ = value; + } + } + + /// Field number for the "updated_at_utc" field. + public const int UpdatedAtUtcFieldNumber = 10; + private global::Google.Protobuf.WellKnownTypes.Timestamp updatedAtUtc_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::Google.Protobuf.WellKnownTypes.Timestamp UpdatedAtUtc { + get { return updatedAtUtc_; } + set { + updatedAtUtc_ = value; + } + } + + /// Field number for the "terminal_at_utc" field. + public const int TerminalAtUtcFieldNumber = 11; + private global::Google.Protobuf.WellKnownTypes.Timestamp terminalAtUtc_; + /// + /// absent when not terminal + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::Google.Protobuf.WellKnownTypes.Timestamp TerminalAtUtc { + get { return terminalAtUtc_; } + set { + terminalAtUtc_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as SiteCallOperationalDto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(SiteCallOperationalDto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (TrackedOperationId != other.TrackedOperationId) return false; + if (Channel != other.Channel) return false; + if (Target != other.Target) return false; + if (SourceSite != other.SourceSite) return false; + if (Status != other.Status) return false; + if (RetryCount != other.RetryCount) return false; + if (LastError != other.LastError) return false; + if (HttpStatus != other.HttpStatus) return false; + if (!object.Equals(CreatedAtUtc, other.CreatedAtUtc)) return false; + if (!object.Equals(UpdatedAtUtc, other.UpdatedAtUtc)) return false; + if (!object.Equals(TerminalAtUtc, other.TerminalAtUtc)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (TrackedOperationId.Length != 0) hash ^= TrackedOperationId.GetHashCode(); + if (Channel.Length != 0) hash ^= Channel.GetHashCode(); + if (Target.Length != 0) hash ^= Target.GetHashCode(); + if (SourceSite.Length != 0) hash ^= SourceSite.GetHashCode(); + if (Status.Length != 0) hash ^= Status.GetHashCode(); + if (RetryCount != 0) hash ^= RetryCount.GetHashCode(); + if (LastError.Length != 0) hash ^= LastError.GetHashCode(); + if (httpStatus_ != null) hash ^= HttpStatus.GetHashCode(); + if (createdAtUtc_ != null) hash ^= CreatedAtUtc.GetHashCode(); + if (updatedAtUtc_ != null) hash ^= UpdatedAtUtc.GetHashCode(); + if (terminalAtUtc_ != null) hash ^= TerminalAtUtc.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (TrackedOperationId.Length != 0) { + output.WriteRawTag(10); + output.WriteString(TrackedOperationId); + } + if (Channel.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Channel); + } + if (Target.Length != 0) { + output.WriteRawTag(26); + output.WriteString(Target); + } + if (SourceSite.Length != 0) { + output.WriteRawTag(34); + output.WriteString(SourceSite); + } + if (Status.Length != 0) { + output.WriteRawTag(42); + output.WriteString(Status); + } + if (RetryCount != 0) { + output.WriteRawTag(48); + output.WriteInt32(RetryCount); + } + if (LastError.Length != 0) { + output.WriteRawTag(58); + output.WriteString(LastError); + } + if (httpStatus_ != null) { + _single_httpStatus_codec.WriteTagAndValue(output, HttpStatus); + } + if (createdAtUtc_ != null) { + output.WriteRawTag(74); + output.WriteMessage(CreatedAtUtc); + } + if (updatedAtUtc_ != null) { + output.WriteRawTag(82); + output.WriteMessage(UpdatedAtUtc); + } + if (terminalAtUtc_ != null) { + output.WriteRawTag(90); + output.WriteMessage(TerminalAtUtc); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (TrackedOperationId.Length != 0) { + output.WriteRawTag(10); + output.WriteString(TrackedOperationId); + } + if (Channel.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Channel); + } + if (Target.Length != 0) { + output.WriteRawTag(26); + output.WriteString(Target); + } + if (SourceSite.Length != 0) { + output.WriteRawTag(34); + output.WriteString(SourceSite); + } + if (Status.Length != 0) { + output.WriteRawTag(42); + output.WriteString(Status); + } + if (RetryCount != 0) { + output.WriteRawTag(48); + output.WriteInt32(RetryCount); + } + if (LastError.Length != 0) { + output.WriteRawTag(58); + output.WriteString(LastError); + } + if (httpStatus_ != null) { + _single_httpStatus_codec.WriteTagAndValue(ref output, HttpStatus); + } + if (createdAtUtc_ != null) { + output.WriteRawTag(74); + output.WriteMessage(CreatedAtUtc); + } + if (updatedAtUtc_ != null) { + output.WriteRawTag(82); + output.WriteMessage(UpdatedAtUtc); + } + if (terminalAtUtc_ != null) { + output.WriteRawTag(90); + output.WriteMessage(TerminalAtUtc); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (TrackedOperationId.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(TrackedOperationId); + } + if (Channel.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Channel); + } + if (Target.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Target); + } + if (SourceSite.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(SourceSite); + } + if (Status.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Status); + } + if (RetryCount != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(RetryCount); + } + if (LastError.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(LastError); + } + if (httpStatus_ != null) { + size += _single_httpStatus_codec.CalculateSizeWithTag(HttpStatus); + } + if (createdAtUtc_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(CreatedAtUtc); + } + if (updatedAtUtc_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(UpdatedAtUtc); + } + if (terminalAtUtc_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TerminalAtUtc); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(SiteCallOperationalDto other) { + if (other == null) { + return; + } + if (other.TrackedOperationId.Length != 0) { + TrackedOperationId = other.TrackedOperationId; + } + if (other.Channel.Length != 0) { + Channel = other.Channel; + } + if (other.Target.Length != 0) { + Target = other.Target; + } + if (other.SourceSite.Length != 0) { + SourceSite = other.SourceSite; + } + if (other.Status.Length != 0) { + Status = other.Status; + } + if (other.RetryCount != 0) { + RetryCount = other.RetryCount; + } + if (other.LastError.Length != 0) { + LastError = other.LastError; + } + if (other.httpStatus_ != null) { + if (httpStatus_ == null || other.HttpStatus != 0) { + HttpStatus = other.HttpStatus; + } + } + if (other.createdAtUtc_ != null) { + if (createdAtUtc_ == null) { + CreatedAtUtc = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + CreatedAtUtc.MergeFrom(other.CreatedAtUtc); + } + if (other.updatedAtUtc_ != null) { + if (updatedAtUtc_ == null) { + UpdatedAtUtc = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + UpdatedAtUtc.MergeFrom(other.UpdatedAtUtc); + } + if (other.terminalAtUtc_ != null) { + if (terminalAtUtc_ == null) { + TerminalAtUtc = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + TerminalAtUtc.MergeFrom(other.TerminalAtUtc); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + if ((tag & 7) == 4) { + // Abort on any end group tag. + return; + } + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + TrackedOperationId = input.ReadString(); + break; + } + case 18: { + Channel = input.ReadString(); + break; + } + case 26: { + Target = input.ReadString(); + break; + } + case 34: { + SourceSite = input.ReadString(); + break; + } + case 42: { + Status = input.ReadString(); + break; + } + case 48: { + RetryCount = input.ReadInt32(); + break; + } + case 58: { + LastError = input.ReadString(); + break; + } + case 66: { + int? value = _single_httpStatus_codec.Read(input); + if (httpStatus_ == null || value != 0) { + HttpStatus = value; + } + break; + } + case 74: { + if (createdAtUtc_ == null) { + CreatedAtUtc = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + input.ReadMessage(CreatedAtUtc); + break; + } + case 82: { + if (updatedAtUtc_ == null) { + UpdatedAtUtc = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + input.ReadMessage(UpdatedAtUtc); + break; + } + case 90: { + if (terminalAtUtc_ == null) { + TerminalAtUtc = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + input.ReadMessage(TerminalAtUtc); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + if ((tag & 7) == 4) { + // Abort on any end group tag. + return; + } + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 10: { + TrackedOperationId = input.ReadString(); + break; + } + case 18: { + Channel = input.ReadString(); + break; + } + case 26: { + Target = input.ReadString(); + break; + } + case 34: { + SourceSite = input.ReadString(); + break; + } + case 42: { + Status = input.ReadString(); + break; + } + case 48: { + RetryCount = input.ReadInt32(); + break; + } + case 58: { + LastError = input.ReadString(); + break; + } + case 66: { + int? value = _single_httpStatus_codec.Read(ref input); + if (httpStatus_ == null || value != 0) { + HttpStatus = value; + } + break; + } + case 74: { + if (createdAtUtc_ == null) { + CreatedAtUtc = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + input.ReadMessage(CreatedAtUtc); + break; + } + case 82: { + if (updatedAtUtc_ == null) { + UpdatedAtUtc = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + input.ReadMessage(UpdatedAtUtc); + break; + } + case 90: { + if (terminalAtUtc_ == null) { + TerminalAtUtc = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + input.ReadMessage(TerminalAtUtc); + break; + } + } + } + } + #endif + + } + + [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")] + public sealed partial class CachedTelemetryPacket : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CachedTelemetryPacket()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::ScadaLink.Communication.Grpc.SitestreamReflection.Descriptor.MessageTypes[8]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CachedTelemetryPacket() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CachedTelemetryPacket(CachedTelemetryPacket other) : this() { + auditEvent_ = other.auditEvent_ != null ? other.auditEvent_.Clone() : null; + operational_ = other.operational_ != null ? other.operational_.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CachedTelemetryPacket Clone() { + return new CachedTelemetryPacket(this); + } + + /// Field number for the "audit_event" field. + public const int AuditEventFieldNumber = 1; + private global::ScadaLink.Communication.Grpc.AuditEventDto auditEvent_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::ScadaLink.Communication.Grpc.AuditEventDto AuditEvent { + get { return auditEvent_; } + set { + auditEvent_ = value; + } + } + + /// Field number for the "operational" field. + public const int OperationalFieldNumber = 2; + private global::ScadaLink.Communication.Grpc.SiteCallOperationalDto operational_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::ScadaLink.Communication.Grpc.SiteCallOperationalDto Operational { + get { return operational_; } + set { + operational_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as CachedTelemetryPacket); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(CachedTelemetryPacket other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AuditEvent, other.AuditEvent)) return false; + if (!object.Equals(Operational, other.Operational)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (auditEvent_ != null) hash ^= AuditEvent.GetHashCode(); + if (operational_ != null) hash ^= Operational.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (auditEvent_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AuditEvent); + } + if (operational_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Operational); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (auditEvent_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AuditEvent); + } + if (operational_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Operational); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (auditEvent_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AuditEvent); + } + if (operational_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Operational); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(CachedTelemetryPacket other) { + if (other == null) { + return; + } + if (other.auditEvent_ != null) { + if (auditEvent_ == null) { + AuditEvent = new global::ScadaLink.Communication.Grpc.AuditEventDto(); + } + AuditEvent.MergeFrom(other.AuditEvent); + } + if (other.operational_ != null) { + if (operational_ == null) { + Operational = new global::ScadaLink.Communication.Grpc.SiteCallOperationalDto(); + } + Operational.MergeFrom(other.Operational); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + if ((tag & 7) == 4) { + // Abort on any end group tag. + return; + } + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (auditEvent_ == null) { + AuditEvent = new global::ScadaLink.Communication.Grpc.AuditEventDto(); + } + input.ReadMessage(AuditEvent); + break; + } + case 18: { + if (operational_ == null) { + Operational = new global::ScadaLink.Communication.Grpc.SiteCallOperationalDto(); + } + input.ReadMessage(Operational); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + if ((tag & 7) == 4) { + // Abort on any end group tag. + return; + } + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 10: { + if (auditEvent_ == null) { + AuditEvent = new global::ScadaLink.Communication.Grpc.AuditEventDto(); + } + input.ReadMessage(AuditEvent); + break; + } + case 18: { + if (operational_ == null) { + Operational = new global::ScadaLink.Communication.Grpc.SiteCallOperationalDto(); + } + input.ReadMessage(Operational); + break; + } + } + } + } + #endif + + } + + [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")] + public sealed partial class CachedTelemetryBatch : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CachedTelemetryBatch()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::ScadaLink.Communication.Grpc.SitestreamReflection.Descriptor.MessageTypes[9]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CachedTelemetryBatch() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CachedTelemetryBatch(CachedTelemetryBatch other) : this() { + packets_ = other.packets_.Clone(); + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CachedTelemetryBatch Clone() { + return new CachedTelemetryBatch(this); + } + + /// Field number for the "packets" field. + public const int PacketsFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_packets_codec + = pb::FieldCodec.ForMessage(10, global::ScadaLink.Communication.Grpc.CachedTelemetryPacket.Parser); + private readonly pbc::RepeatedField packets_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pbc::RepeatedField Packets { + get { return packets_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as CachedTelemetryBatch); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(CachedTelemetryBatch other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!packets_.Equals(other.packets_)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + hash ^= packets_.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + packets_.WriteTo(output, _repeated_packets_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + packets_.WriteTo(ref output, _repeated_packets_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + size += packets_.CalculateSize(_repeated_packets_codec); + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(CachedTelemetryBatch other) { + if (other == null) { + return; + } + packets_.Add(other.packets_); + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + if ((tag & 7) == 4) { + // Abort on any end group tag. + return; + } + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + packets_.AddEntriesFrom(input, _repeated_packets_codec); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + if ((tag & 7) == 4) { + // Abort on any end group tag. + return; + } + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 10: { + packets_.AddEntriesFrom(ref input, _repeated_packets_codec); + break; + } + } + } + } + #endif + + } + #endregion } diff --git a/src/ScadaLink.Communication/SiteStreamGrpc/SitestreamGrpc.cs b/src/ScadaLink.Communication/SiteStreamGrpc/SitestreamGrpc.cs index 0a900cb..e7b9b33 100644 --- a/src/ScadaLink.Communication/SiteStreamGrpc/SitestreamGrpc.cs +++ b/src/ScadaLink.Communication/SiteStreamGrpc/SitestreamGrpc.cs @@ -53,6 +53,8 @@ namespace ScadaLink.Communication.Grpc { static readonly grpc::Marshaller __Marshaller_sitestream_AuditEventBatch = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::ScadaLink.Communication.Grpc.AuditEventBatch.Parser)); [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] static readonly grpc::Marshaller __Marshaller_sitestream_IngestAck = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::ScadaLink.Communication.Grpc.IngestAck.Parser)); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Marshaller __Marshaller_sitestream_CachedTelemetryBatch = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::ScadaLink.Communication.Grpc.CachedTelemetryBatch.Parser)); [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] static readonly grpc::Method __Method_SubscribeInstance = new grpc::Method( @@ -70,6 +72,14 @@ namespace ScadaLink.Communication.Grpc { __Marshaller_sitestream_AuditEventBatch, __Marshaller_sitestream_IngestAck); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Method __Method_IngestCachedTelemetry = new grpc::Method( + grpc::MethodType.Unary, + __ServiceName, + "IngestCachedTelemetry", + __Marshaller_sitestream_CachedTelemetryBatch, + __Marshaller_sitestream_IngestAck); + /// Service descriptor public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor { @@ -92,6 +102,12 @@ namespace ScadaLink.Communication.Grpc { throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::System.Threading.Tasks.Task IngestCachedTelemetry(global::ScadaLink.Communication.Grpc.CachedTelemetryBatch request, grpc::ServerCallContext context) + { + throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); + } + } /// Client for SiteStreamService @@ -151,6 +167,26 @@ namespace ScadaLink.Communication.Grpc { { return CallInvoker.AsyncUnaryCall(__Method_IngestAuditEvents, null, options, request); } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::ScadaLink.Communication.Grpc.IngestAck IngestCachedTelemetry(global::ScadaLink.Communication.Grpc.CachedTelemetryBatch request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return IngestCachedTelemetry(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::ScadaLink.Communication.Grpc.IngestAck IngestCachedTelemetry(global::ScadaLink.Communication.Grpc.CachedTelemetryBatch request, grpc::CallOptions options) + { + return CallInvoker.BlockingUnaryCall(__Method_IngestCachedTelemetry, null, options, request); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall IngestCachedTelemetryAsync(global::ScadaLink.Communication.Grpc.CachedTelemetryBatch request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return IngestCachedTelemetryAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall IngestCachedTelemetryAsync(global::ScadaLink.Communication.Grpc.CachedTelemetryBatch request, grpc::CallOptions options) + { + return CallInvoker.AsyncUnaryCall(__Method_IngestCachedTelemetry, null, options, request); + } /// Creates a new instance of client from given ClientBaseConfiguration. [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] protected override SiteStreamServiceClient NewInstance(ClientBaseConfiguration configuration) @@ -166,7 +202,8 @@ namespace ScadaLink.Communication.Grpc { { return grpc::ServerServiceDefinition.CreateBuilder() .AddMethod(__Method_SubscribeInstance, serviceImpl.SubscribeInstance) - .AddMethod(__Method_IngestAuditEvents, serviceImpl.IngestAuditEvents).Build(); + .AddMethod(__Method_IngestAuditEvents, serviceImpl.IngestAuditEvents) + .AddMethod(__Method_IngestCachedTelemetry, serviceImpl.IngestCachedTelemetry).Build(); } /// Register service method with a service binder with or without implementation. Useful when customizing the service binding logic. @@ -178,6 +215,7 @@ namespace ScadaLink.Communication.Grpc { { serviceBinder.AddMethod(__Method_SubscribeInstance, serviceImpl == null ? null : new grpc::ServerStreamingServerMethod(serviceImpl.SubscribeInstance)); serviceBinder.AddMethod(__Method_IngestAuditEvents, serviceImpl == null ? null : new grpc::UnaryServerMethod(serviceImpl.IngestAuditEvents)); + serviceBinder.AddMethod(__Method_IngestCachedTelemetry, serviceImpl == null ? null : new grpc::UnaryServerMethod(serviceImpl.IngestCachedTelemetry)); } } diff --git a/tests/ScadaLink.Communication.Tests/Protos/CachedTelemetryProtoTests.cs b/tests/ScadaLink.Communication.Tests/Protos/CachedTelemetryProtoTests.cs new file mode 100644 index 0000000..4405768 --- /dev/null +++ b/tests/ScadaLink.Communication.Tests/Protos/CachedTelemetryProtoTests.cs @@ -0,0 +1,173 @@ +using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; +using ScadaLink.Communication.Grpc; + +namespace ScadaLink.Communication.Tests.Protos; + +/// +/// Wire-format round-trip tests for the Audit Log (#23) M3 cached-telemetry +/// proto messages (, +/// , ). +/// Locks the additive contract the central dual-write transaction depends on. +/// +public class CachedTelemetryProtoTests +{ + private static AuditEventDto NewAuditDto(Guid? id = null) => new() + { + EventId = (id ?? Guid.NewGuid()).ToString(), + OccurredAtUtc = Timestamp.FromDateTimeOffset( + new DateTimeOffset(2026, 5, 20, 10, 15, 30, 123, TimeSpan.Zero)), + Channel = "ApiOutbound", + Kind = "CachedSubmit", + Status = "Submitted", + SourceSiteId = "site-1", + }; + + [Fact] + public void SiteCallOperationalDto_RoundTrip_PreservesAllFields() + { + var createdAt = Timestamp.FromDateTimeOffset( + new DateTimeOffset(2026, 5, 20, 10, 0, 0, TimeSpan.Zero)); + var updatedAt = Timestamp.FromDateTimeOffset( + new DateTimeOffset(2026, 5, 20, 10, 5, 0, TimeSpan.Zero)); + var terminalAt = Timestamp.FromDateTimeOffset( + new DateTimeOffset(2026, 5, 20, 10, 10, 0, TimeSpan.Zero)); + + var original = new SiteCallOperationalDto + { + TrackedOperationId = Guid.NewGuid().ToString(), + Channel = "ApiOutbound", + Target = "ERP.GetOrder", + SourceSite = "site-melbourne", + Status = "Delivered", + RetryCount = 3, + LastError = "transient 503", + HttpStatus = 200, + CreatedAtUtc = createdAt, + UpdatedAtUtc = updatedAt, + TerminalAtUtc = terminalAt, + }; + + var bytes = original.ToByteArray(); + var deserialized = SiteCallOperationalDto.Parser.ParseFrom(bytes); + + Assert.Equal(original.TrackedOperationId, deserialized.TrackedOperationId); + Assert.Equal(original.Channel, deserialized.Channel); + Assert.Equal(original.Target, deserialized.Target); + Assert.Equal(original.SourceSite, deserialized.SourceSite); + Assert.Equal(original.Status, deserialized.Status); + Assert.Equal(original.RetryCount, deserialized.RetryCount); + Assert.Equal(original.LastError, deserialized.LastError); + Assert.Equal(original.HttpStatus, deserialized.HttpStatus); + Assert.Equal(original.CreatedAtUtc, deserialized.CreatedAtUtc); + Assert.Equal(original.UpdatedAtUtc, deserialized.UpdatedAtUtc); + Assert.Equal(original.TerminalAtUtc, deserialized.TerminalAtUtc); + } + + [Fact] + public void SiteCallOperationalDto_TerminalAt_AbsentWhenNotTerminal() + { + // Lifecycle events prior to the terminal step leave TerminalAtUtc unset; + // the well-known Timestamp wrapper is absent on the wire (null in C#). + var dto = new SiteCallOperationalDto + { + TrackedOperationId = Guid.NewGuid().ToString(), + Channel = "DbOutbound", + Target = "warehouse.dbo.WriteOrder", + SourceSite = "site-brisbane", + Status = "Attempted", + RetryCount = 1, + CreatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), + UpdatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), + }; + + Assert.Null(dto.TerminalAtUtc); + + var bytes = dto.ToByteArray(); + var deserialized = SiteCallOperationalDto.Parser.ParseFrom(bytes); + + Assert.Null(deserialized.TerminalAtUtc); + } + + [Fact] + public void SiteCallOperationalDto_NullableHttpStatus_AbsentByDefault() + { + // Int32Value wrapper-typed http_status — unset round-trips as null, + // matching DB nullable column semantics for non-API cached writes. + var dto = new SiteCallOperationalDto + { + TrackedOperationId = Guid.NewGuid().ToString(), + Channel = "DbOutbound", + Target = "warehouse.dbo.WriteOrder", + SourceSite = "site-brisbane", + Status = "Submitted", + RetryCount = 0, + CreatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), + UpdatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), + }; + + Assert.Null(dto.HttpStatus); + + var bytes = dto.ToByteArray(); + var deserialized = SiteCallOperationalDto.Parser.ParseFrom(bytes); + + Assert.Null(deserialized.HttpStatus); + } + + [Fact] + public void CachedTelemetryPacket_RoundTrip_PreservesNestedEntities() + { + var trackedOpId = Guid.NewGuid().ToString(); + var auditDto = NewAuditDto(); + auditDto.Target = "ERP.GetOrder"; + auditDto.Status = "Attempted"; + + var operationalDto = new SiteCallOperationalDto + { + TrackedOperationId = trackedOpId, + Channel = "ApiOutbound", + Target = "ERP.GetOrder", + SourceSite = "site-1", + Status = "Attempted", + RetryCount = 2, + HttpStatus = 503, + LastError = "Service unavailable", + CreatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), + UpdatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), + }; + + var original = new CachedTelemetryPacket + { + AuditEvent = auditDto, + Operational = operationalDto, + }; + + var bytes = original.ToByteArray(); + var deserialized = CachedTelemetryPacket.Parser.ParseFrom(bytes); + + Assert.NotNull(deserialized.AuditEvent); + Assert.Equal(auditDto.EventId, deserialized.AuditEvent.EventId); + Assert.Equal(auditDto.Target, deserialized.AuditEvent.Target); + Assert.Equal(auditDto.Status, deserialized.AuditEvent.Status); + + Assert.NotNull(deserialized.Operational); + Assert.Equal(trackedOpId, deserialized.Operational.TrackedOperationId); + Assert.Equal(operationalDto.Channel, deserialized.Operational.Channel); + Assert.Equal(operationalDto.Status, deserialized.Operational.Status); + Assert.Equal(operationalDto.RetryCount, deserialized.Operational.RetryCount); + Assert.Equal(operationalDto.HttpStatus, deserialized.Operational.HttpStatus); + Assert.Equal(operationalDto.LastError, deserialized.Operational.LastError); + } + + [Fact] + public void CachedTelemetryBatch_Empty_RoundTrip_Yields_EmptyPackets() + { + var original = new CachedTelemetryBatch(); + Assert.Empty(original.Packets); + + var bytes = original.ToByteArray(); + var deserialized = CachedTelemetryBatch.Parser.ParseFrom(bytes); + + Assert.Empty(deserialized.Packets); + } +}