using System; using System.Globalization; using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace ZB.MOM.WW.CBDDC.Core; public enum OperationType { Put, Delete } public static class OplogEntryExtensions { /// /// Computes a deterministic hash for the specified oplog entry. /// /// The oplog entry to hash. /// The lowercase hexadecimal SHA-256 hash of the entry. public static string ComputeHash(this OplogEntry entry) { using var sha256 = SHA256.Create(); var sb = new StringBuilder(); sb.Append(entry.Collection); sb.Append('|'); sb.Append(entry.Key); sb.Append('|'); // Ensure stable string representation for Enum (integer value) sb.Append(((int)entry.Operation).ToString(CultureInfo.InvariantCulture)); sb.Append('|'); // Payload excluded from hash to avoid serialization non-determinism // sb.Append(entry.Payload...); sb.Append('|'); // Timestamp.ToString() is now Invariant sb.Append(entry.Timestamp.ToString()); sb.Append('|'); sb.Append(entry.PreviousHash); byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString()); byte[] hashBytes = sha256.ComputeHash(bytes); // Convert to hex string return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } } public class OplogEntry { /// /// Initializes a new instance of the class. /// /// The collection name. /// The document key. /// The operation type. /// The serialized payload. /// The logical timestamp. /// The previous entry hash. /// The current entry hash. If null, it is computed. public OplogEntry(string collection, string key, OperationType operation, JsonElement? payload, HlcTimestamp timestamp, string previousHash, string? hash = null) { Collection = collection; Key = key; Operation = operation; Payload = payload; Timestamp = timestamp; PreviousHash = previousHash ?? string.Empty; Hash = hash ?? this.ComputeHash(); } /// /// Gets the collection name associated with this entry. /// public string Collection { get; } /// /// Gets the document key associated with this entry. /// public string Key { get; } /// /// Gets the operation represented by this entry. /// public OperationType Operation { get; } /// /// Gets the serialized payload for the operation. /// public JsonElement? Payload { get; } /// /// Gets the logical timestamp for this entry. /// public HlcTimestamp Timestamp { get; } /// /// Gets the hash of this entry. /// public string Hash { get; } /// /// Gets the hash of the previous entry in the chain. /// public string PreviousHash { get; } /// /// Verifies if the stored Hash matches the content. /// public bool IsValid() { return Hash == this.ComputeHash(); } }