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