using Google.Protobuf; using Google.Protobuf.Collections; using Grpc.Core; namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc; /// Bounds audit batches using complete protobuf row sizes, length/tag prefixes, and gRPC framing. public static class AuditWireBudget { /// Five-byte gRPC frame plus two bytes reserved for a true MoreAvailable field. public const int InitialBytes = 7; /// /// Appends one row to a repeated-message collection if it fits. A row larger than the /// normal budget travels alone, up to the hard channel ceiling. Oversize rows are retained /// locally and fail explicitly with their identity; they are never acknowledged or truncated. /// /// The protobuf message type of the rows. /// The repeated field to append to. /// The row to append. /// The running wire size of the batch; increased by the row's size when appended. /// Identity of the row, used in the error raised when it exceeds the hard ceiling. /// true when the row was appended; false when it does not fit in the current batch. public static bool TryAppend(RepeatedField rows, T row, ref int wireBytes, string rowIdentity) where T : IMessage { var rowBytes = 1L + CodedOutputStream.ComputeMessageSize(row); if (rows.Count > 0 && wireBytes + rowBytes > GrpcMessageLimits.AuditBatchBytes) return false; if (rowBytes + InitialBytes > GrpcMessageLimits.MaxMessageBytes) throw new RpcException(new Status(StatusCode.ResourceExhausted, $"Audit row {rowIdentity} requires {rowBytes + InitialBytes} wire bytes, exceeding {GrpcMessageLimits.MaxMessageBytes}; retained locally. Reduce its payload or raise coordinated peer limits before retrying.")); rows.Add(row); wireBytes += (int)rowBytes; return true; } }