// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Buffers.Binary;
using System.IO;
using EpicGames.Core;
using ICSharpCode.SharpZipLib.BZip2;
using OpenTelemetry.Trace;
namespace HordeServer.Utilities
{
///
/// Extension methods for compression
///
static class CompressionExtensions
{
///
/// Compress a block of data with bzip2
///
/// Memory to compress
/// Compression level, 1 to 9 (where 9 is highest compression)
/// The compressed data
public static byte[] CompressBzip2(this ReadOnlyMemory memory, int compressionLevel = 4)
{
using TelemetrySpan span = OpenTelemetryTracers.Horde.StartActiveSpan($"{nameof(CompressionExtensions)}.{nameof(CompressBzip2)}");
span.SetAttribute("decompressedSize", memory.Length);
byte[] compressedData;
using (MemoryStream stream = new MemoryStream())
{
byte[] decompressedSize = new byte[4];
BinaryPrimitives.WriteInt32LittleEndian(decompressedSize.AsSpan(), memory.Length);
stream.Write(decompressedSize.AsSpan());
using (BZip2OutputStream compressedStream = new(stream, compressionLevel))
{
compressedStream.Write(memory.Span);
}
compressedData = stream.ToArray();
}
span.SetAttribute("compressedSize", compressedData.Length);
return compressedData;
}
///
/// Decompress the data
///
/// The decompressed data
public static byte[] DecompressBzip2(this ReadOnlyMemory memory)
{
int decompressedSize = BinaryPrimitives.ReadInt32LittleEndian(memory.Span);
using TelemetrySpan span = OpenTelemetryTracers.Horde.StartActiveSpan($"{nameof(CompressionExtensions)}.{nameof(DecompressBzip2)}");
span.SetAttribute("compressedSize", memory.Length);
span.SetAttribute("decompressedSize", decompressedSize);
byte[] data = new byte[decompressedSize];
using (ReadOnlyMemoryStream stream = new ReadOnlyMemoryStream(memory.Slice(4)))
{
using (BZip2InputStream decompressedStream = new BZip2InputStream(stream))
{
int readSize = decompressedStream.Read(data.AsSpan());
if (readSize != data.Length)
{
throw new InvalidDataException($"Compressed data is too short (expected {data.Length} bytes, got {readSize} bytes)");
}
}
}
return data;
}
}
}