// 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;
}
}