using System.Runtime.Serialization; using System.Text.Json; using Akka.Actor; using Akka.Serialization; using ZB.MOM.WW.Secrets.Replicator.AkkaDotNet.Protocol; namespace ZB.MOM.WW.Secrets.Replicator.AkkaDotNet; /// /// Explicit System.Text.Json serializer for the secret-replication wire protocol. /// /// /// /// Registering this is optional but recommended. The protocol DTOs are all primitives, so /// Akka's default JSON serializer round-trips them correctly if an application never wires this up — /// but relying on the default means secret ciphertext is serialized by whatever the cluster's /// fallback happens to be, which is not a decision worth leaving implicit for this payload. Binding /// it explicitly also pins the manifest strings below as the versioning boundary: a future protocol /// change adds a manifest, so a mixed-version cluster fails loudly on an unknown manifest instead of /// silently mis-deserializing a secret. /// /// /// Wire it up by merging into the /// application's Akka configuration — see that member for the HOCON. /// /// /// The actor system this serializer belongs to. public sealed class SecretReplicationSerializer(ExtendedActorSystem system) : SerializerWithStringManifest(system) { /// Stable serializer id. Must not collide with other serializers in the cluster. public const int SerializerId = 7710; private const string RowsManifest = "zbs-rows-v1"; private const string ManifestAnnounceManifest = "zbs-manifest-v1"; private const string PullRequestManifest = "zbs-pull-v1"; private static readonly JsonSerializerOptions JsonOptions = new() { // Explicitly NOT indented and NOT camel-cased: the wire form should be compact and stable, // not pretty. Property names are the C# names, pinned by the manifest version. WriteIndented = false, }; /// public override int Identifier => SerializerId; /// public override string Manifest(object o) => o switch { SecretRowsMessage => RowsManifest, SecretManifestAnnounce => ManifestAnnounceManifest, SecretPullRequest => PullRequestManifest, _ => throw new ArgumentException( $"{nameof(SecretReplicationSerializer)} cannot serialize {o?.GetType().FullName ?? "null"}.", nameof(o)), }; /// public override byte[] ToBinary(object obj) => obj switch { SecretRowsMessage rows => JsonSerializer.SerializeToUtf8Bytes(rows, JsonOptions), SecretManifestAnnounce announce => JsonSerializer.SerializeToUtf8Bytes(announce, JsonOptions), SecretPullRequest pull => JsonSerializer.SerializeToUtf8Bytes(pull, JsonOptions), _ => throw new ArgumentException( $"{nameof(SecretReplicationSerializer)} cannot serialize {obj?.GetType().FullName ?? "null"}.", nameof(obj)), }; /// public override object FromBinary(byte[] bytes, string manifest) => manifest switch { RowsManifest => Deserialize(bytes, manifest), ManifestAnnounceManifest => Deserialize(bytes, manifest), PullRequestManifest => Deserialize(bytes, manifest), _ => throw new SerializationException( $"Unknown secret-replication manifest '{manifest}'. This usually means a peer is running a " + "newer protocol version than this node."), }; private static T Deserialize(byte[] bytes, string manifest) => JsonSerializer.Deserialize(bytes, JsonOptions) ?? throw new SerializationException( $"Secret-replication payload with manifest '{manifest}' deserialized to null."); }