// Reference: golang/nats-server/server/filestore.go // Go uses S2 (Snappy variant) compression throughout FileStore: // - msgCompress / msgDecompress (filestore.go ~line 840) // - compressBlock / decompressBlock for block-level data // S2 is faster than Deflate and produces comparable ratios for binary payloads. // IronSnappy provides Snappy-format encode/decode, which is compatible with // the Go snappy package used by the S2 library for block compression. using IronSnappy; namespace NATS.Server.JetStream.Storage; /// /// S2/Snappy codec for FileStore payload compression, mirroring the Go /// implementation which uses github.com/klauspost/compress/s2. /// internal static class S2Codec { /// /// Compresses using Snappy block format. /// Returns the compressed bytes, which may be longer than the input for /// very small payloads (Snappy does not guarantee compression for tiny inputs). /// public static byte[] Compress(ReadOnlySpan data) { if (data.IsEmpty) return []; return Snappy.Encode(data); } /// /// Decompresses Snappy-compressed . /// /// If the data is not valid Snappy. public static byte[] Decompress(ReadOnlySpan data) { if (data.IsEmpty) return []; return Snappy.Decode(data); } /// /// Compresses only the body portion of , leaving the /// last bytes uncompressed (appended verbatim). /// /// /// In the Go FileStore the trailing bytes of a stored record can be a raw /// checksum that is not part of the compressed payload. This helper mirrors /// that separation (filestore.go msgCompress, where the CRC lives outside /// the S2 frame). /// public static byte[] CompressWithTrailingChecksum(ReadOnlySpan data, int checksumSize) { if (checksumSize < 0) throw new ArgumentOutOfRangeException(nameof(checksumSize)); if (data.IsEmpty) return []; if (checksumSize == 0) return Compress(data); if (checksumSize >= data.Length) { // Nothing to compress — return a copy as-is (checksum covers everything). return data.ToArray(); } var body = data[..^checksumSize]; var checksum = data[^checksumSize..]; var compressedBody = Compress(body); var result = new byte[compressedBody.Length + checksumSize]; compressedBody.CopyTo(result.AsSpan()); checksum.CopyTo(result.AsSpan(compressedBody.Length)); return result; } /// /// Decompresses only the body portion of , treating /// the last bytes as a raw (uncompressed) checksum. /// public static byte[] DecompressWithTrailingChecksum(ReadOnlySpan data, int checksumSize) { if (checksumSize < 0) throw new ArgumentOutOfRangeException(nameof(checksumSize)); if (data.IsEmpty) return []; if (checksumSize == 0) return Decompress(data); if (checksumSize >= data.Length) { // Nothing was compressed — return a copy as-is. return data.ToArray(); } var compressedBody = data[..^checksumSize]; var checksum = data[^checksumSize..]; var decompressedBody = Decompress(compressedBody); var result = new byte[decompressedBody.Length + checksumSize]; decompressedBody.CopyTo(result.AsSpan()); checksum.CopyTo(result.AsSpan(decompressedBody.Length)); return result; } }