152 lines
5.3 KiB
C#
152 lines
5.3 KiB
C#
using System.Buffers.Binary;
|
|
|
|
namespace ZB.MOM.WW.CBDD.Core.Compression;
|
|
|
|
/// <summary>
|
|
/// Fixed header prefix for compressed payload blobs.
|
|
/// </summary>
|
|
public readonly struct CompressedPayloadHeader
|
|
{
|
|
public const int Size = 16;
|
|
|
|
/// <summary>
|
|
/// Compression codec used for payload bytes.
|
|
/// </summary>
|
|
public CompressionCodec Codec { get; }
|
|
|
|
/// <summary>
|
|
/// Original uncompressed payload length.
|
|
/// </summary>
|
|
public int OriginalLength { get; }
|
|
|
|
/// <summary>
|
|
/// Compressed payload length.
|
|
/// </summary>
|
|
public int CompressedLength { get; }
|
|
|
|
/// <summary>
|
|
/// CRC32 checksum of compressed payload bytes.
|
|
/// </summary>
|
|
public uint Checksum { get; }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="CompressedPayloadHeader" /> class.
|
|
/// </summary>
|
|
/// <param name="codec">Compression codec used for payload bytes.</param>
|
|
/// <param name="originalLength">Original uncompressed payload length.</param>
|
|
/// <param name="compressedLength">Compressed payload length.</param>
|
|
/// <param name="checksum">CRC32 checksum of compressed payload bytes.</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create.
|
|
/// </summary>
|
|
/// <param name="codec">Compression codec used for payload bytes.</param>
|
|
/// <param name="originalLength">Original uncompressed payload length.</param>
|
|
/// <param name="compressedPayload">Compressed payload bytes.</param>
|
|
public static CompressedPayloadHeader Create(CompressionCodec codec, int originalLength,
|
|
ReadOnlySpan<byte> compressedPayload)
|
|
{
|
|
uint checksum = ComputeChecksum(compressedPayload);
|
|
return new CompressedPayloadHeader(codec, originalLength, compressedPayload.Length, checksum);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write To.
|
|
/// </summary>
|
|
/// <param name="destination">Destination span that receives the serialized header.</param>
|
|
public void WriteTo(Span<byte> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read From.
|
|
/// </summary>
|
|
/// <param name="source">Source span containing a serialized header.</param>
|
|
public static CompressedPayloadHeader ReadFrom(ReadOnlySpan<byte> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate Checksum.
|
|
/// </summary>
|
|
/// <param name="compressedPayload">Compressed payload bytes to validate.</param>
|
|
public bool ValidateChecksum(ReadOnlySpan<byte> compressedPayload)
|
|
{
|
|
return Checksum == ComputeChecksum(compressedPayload);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compute Checksum.
|
|
/// </summary>
|
|
/// <param name="payload">Payload bytes.</param>
|
|
public static uint ComputeChecksum(ReadOnlySpan<byte> payload)
|
|
{
|
|
return Crc32Calculator.Compute(payload);
|
|
}
|
|
|
|
private static class Crc32Calculator
|
|
{
|
|
private const uint Polynomial = 0xEDB88320u;
|
|
private static readonly uint[] Table = CreateTable();
|
|
|
|
/// <summary>
|
|
/// Compute.
|
|
/// </summary>
|
|
/// <param name="payload">Payload bytes.</param>
|
|
public static uint Compute(ReadOnlySpan<byte> 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;
|
|
}
|
|
}
|
|
} |