using System.Buffers.Binary; using System.Text.Json; using NATS.Server.JetStream.Storage; namespace NATS.Server.JetStream.Cluster; /// /// Binary codec for meta-group snapshots. /// Format: [2:version_le][N:S2-compressed JSON of assignment map] /// Go reference: jetstream_cluster.go:2075-2145 (encodeMetaSnapshot/decodeMetaSnapshot) /// internal static class MetaSnapshotCodec { private const ushort CurrentVersion = 1; // Use Populate so the getter-only Consumers dictionary on StreamAssignment // is populated in-place by the deserializer rather than requiring a setter. // Go reference: jetstream_cluster.go streamAssignment consumers map restoration. private static readonly JsonSerializerOptions SerializerOptions = new() { PreferredObjectCreationHandling = System.Text.Json.Serialization.JsonObjectCreationHandling.Populate, }; /// /// Encodes into the versioned, S2-compressed binary format. /// Go reference: jetstream_cluster.go:2075 encodeMetaSnapshot. /// /// Current stream placement assignments to persist into the meta snapshot. public static byte[] Encode(Dictionary assignments) { var json = JsonSerializer.SerializeToUtf8Bytes(assignments, SerializerOptions); var compressed = S2Codec.Compress(json); var result = new byte[2 + compressed.Length]; BinaryPrimitives.WriteUInt16LittleEndian(result, CurrentVersion); compressed.CopyTo(result, 2); return result; } /// /// Decodes a versioned, S2-compressed binary snapshot into a stream assignment map. /// Go reference: jetstream_cluster.go:2100 decodeMetaSnapshot. /// /// Versioned binary snapshot payload received from replicated state. /// /// Thrown when is too short or contains an unrecognised version. /// public static Dictionary Decode(byte[] data) { if (data.Length < 2) throw new InvalidOperationException("Meta snapshot too short to contain version header."); var version = BinaryPrimitives.ReadUInt16LittleEndian(data); if (version != CurrentVersion) throw new InvalidOperationException($"Unknown meta snapshot version: {version}"); var compressed = data.AsSpan(2); var json = S2Codec.Decompress(compressed); return JsonSerializer.Deserialize>(json, SerializerOptions) ?? new Dictionary(); } }