using System.Buffers.Binary;
namespace ZB.MOM.WW.CBDD.Core.Compression;
///
/// Fixed header prefix for compressed payload blobs.
///
public readonly struct CompressedPayloadHeader
{
public const int Size = 16;
///
/// Compression codec used for payload bytes.
///
public CompressionCodec Codec { get; }
///
/// Original uncompressed payload length.
///
public int OriginalLength { get; }
///
/// Compressed payload length.
///
public int CompressedLength { get; }
///
/// CRC32 checksum of compressed payload bytes.
///
public uint Checksum { get; }
///
/// Initializes a new instance of the class.
///
/// Compression codec used for payload bytes.
/// Original uncompressed payload length.
/// Compressed payload length.
/// CRC32 checksum of compressed payload bytes.
public CompressedPayloadHeader(CompressionCodec codec, int originalLength, int compressedLength, uint checksum)
{
if (originalLength < 0)
throw new ArgumentOutOfRangeException(nameof(originalLength));
if (compressedLength < 0)
throw new ArgumentOutOfRangeException(nameof(compressedLength));
Codec = codec;
OriginalLength = originalLength;
CompressedLength = compressedLength;
Checksum = checksum;
}
///
/// Create.
///
/// Compression codec used for payload bytes.
/// Original uncompressed payload length.
/// Compressed payload bytes.
public static CompressedPayloadHeader Create(CompressionCodec codec, int originalLength,
ReadOnlySpan compressedPayload)
{
uint checksum = ComputeChecksum(compressedPayload);
return new CompressedPayloadHeader(codec, originalLength, compressedPayload.Length, checksum);
}
///
/// Write To.
///
/// Destination span that receives the serialized header.
public void WriteTo(Span destination)
{
if (destination.Length < Size)
throw new ArgumentException($"Destination must be at least {Size} bytes.", nameof(destination));
destination[0] = (byte)Codec;
destination[1] = 0;
destination[2] = 0;
destination[3] = 0;
BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(4, 4), OriginalLength);
BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(8, 4), CompressedLength);
BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(12, 4), Checksum);
}
///
/// Read From.
///
/// Source span containing a serialized header.
public static CompressedPayloadHeader ReadFrom(ReadOnlySpan source)
{
if (source.Length < Size)
throw new ArgumentException($"Source must be at least {Size} bytes.", nameof(source));
var codec = (CompressionCodec)source[0];
int originalLength = BinaryPrimitives.ReadInt32LittleEndian(source.Slice(4, 4));
int compressedLength = BinaryPrimitives.ReadInt32LittleEndian(source.Slice(8, 4));
uint checksum = BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(12, 4));
return new CompressedPayloadHeader(codec, originalLength, compressedLength, checksum);
}
///
/// Validate Checksum.
///
/// Compressed payload bytes to validate.
public bool ValidateChecksum(ReadOnlySpan compressedPayload)
{
return Checksum == ComputeChecksum(compressedPayload);
}
///
/// Compute Checksum.
///
/// Payload bytes.
public static uint ComputeChecksum(ReadOnlySpan payload)
{
return Crc32Calculator.Compute(payload);
}
private static class Crc32Calculator
{
private const uint Polynomial = 0xEDB88320u;
private static readonly uint[] Table = CreateTable();
///
/// Compute.
///
/// Payload bytes.
public static uint Compute(ReadOnlySpan payload)
{
var crc = 0xFFFFFFFFu;
for (var i = 0; i < payload.Length; i++)
{
uint index = (crc ^ payload[i]) & 0xFF;
crc = (crc >> 8) ^ Table[index];
}
return ~crc;
}
private static uint[] CreateTable()
{
var table = new uint[256];
for (uint i = 0; i < table.Length; i++)
{
uint value = i;
for (var bit = 0; bit < 8; bit++) value = (value & 1) != 0 ? (value >> 1) ^ Polynomial : value >> 1;
table[i] = value;
}
return table;
}
}
}