< Summary

Information
Class: Ice.Internal.BZStream
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/Internal/BZip2.cs
Tag: 71_18251537082
Line coverage
100%
Covered lines: 12
Uncovered lines: 0
Coverable lines: 12
Total lines: 307
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 1
Total methods: 1
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%

File(s)

/home/runner/work/ice/ice/csharp/src/Ice/Internal/BZip2.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3#nullable enable
 4
 5using System.Diagnostics;
 6using System.Globalization;
 7using System.Reflection;
 8using System.Runtime.ExceptionServices;
 9using System.Runtime.InteropServices;
 10using System.Text;
 11
 12namespace Ice.Internal;
 13
 14// Map Bzip2 bz_stream struct to a C# struct for using with the Bzip2 low level API.
 15[StructLayout(LayoutKind.Sequential)]
 16internal struct BZStream(IntPtr nextOut, uint availOut)
 17{
 118    internal IntPtr NextIn = IntPtr.Zero;
 119    internal uint AvailIn = 0;
 120    internal uint TotalInLo32 = 0;
 121    internal uint TotalInHi32 = 0;
 22
 123    internal IntPtr NextOut = nextOut;
 124    internal uint AvailOut = availOut;
 125    internal uint TotalOutLo32 = 0;
 126    internal uint TotalOutHi32 = 0;
 27
 128    internal IntPtr State = IntPtr.Zero;
 129    internal IntPtr BzAlloc = IntPtr.Zero;
 130    internal IntPtr BzFree = IntPtr.Zero;
 131    internal IntPtr Opaque = IntPtr.Zero;
 32}
 33
 34public static class BZip2
 35{
 36    private enum BzAction
 37    {
 38        Run = 0,
 39        Flush = 1,
 40        Finish = 2
 41    }
 42
 43    private enum BzStatus
 44    {
 45        Ok = 0,
 46        RunOk = 1,
 47        FinishOk = 3,
 48        StreamEnd = 4,
 49
 50        SequenceError = -1,
 51        ParamError = -2,
 52        MemError = -3,
 53        DataError = -4,
 54        DataErrorMagic = -5,
 55        IoError = -6,
 56        UnexpectedEof = -7,
 57        OutbuffFull = -8,
 58        ConfigError = -9
 59    }
 60
 61    private static readonly object _mutex = new object();
 62    private static bool? _isLoaded;
 63
 64    public static bool isLoaded(Ice.Logger logger)
 65    {
 66        lock (_mutex)
 67        {
 68            if (_isLoaded is null)
 69            {
 70                // Register a delegate to load native libraries used by Ice assembly.
 71                NativeLibrary.SetDllImportResolver(Assembly.GetAssembly(typeof(BZip2))!, dllImportResolver);
 72                string libNames = string.Join(", ", getPlatformNativeLibraryNames()).TrimEnd();
 73                bool loaded = false;
 74                try
 75                {
 76                    BZ2_bzLibVersion();
 77                    loaded = true;
 78                }
 79                catch (EntryPointNotFoundException)
 80                {
 81                    logger.warning($"found {libNames} but entry point BZ2_bzlibVersion is missing.");
 82                }
 83                catch (TypeLoadException)
 84                {
 85                    // Expected -- bzip2 lib not installed or not in PATH.
 86                }
 87                catch (BadImageFormatException)
 88                {
 89                    var msg = new StringBuilder();
 90                    msg.AppendLine(
 91                        CultureInfo.InvariantCulture,
 92                        $"{libNames} could not be loaded (likely due to 32/64-bit mismatch).");
 93                    if (IntPtr.Size == 8)
 94                    {
 95                        msg.AppendLine(
 96                            CultureInfo.InvariantCulture,
 97                            $"Make sure the directory containing the 64-bit {libNames} is in your PATH.");
 98                    }
 99                    logger.warning(msg.ToString());
 100                }
 101                _isLoaded = loaded;
 102            }
 103            return _isLoaded.Value;
 104        }
 105    }
 106
 107    internal static Buffer? compress(Buffer buf, int headerSize, int compressionLevel)
 108    {
 109        Debug.Assert(_isLoaded is true);
 110        // In the worst case, the compressed buffer will be 1% larger than the decompressed buffer plus 600 bytes
 111        // for the bzip2 header, plus 4 bytes for the decompressed size added by Ice protocol.
 112        int compressedLenMax = (int)((buf.size() * 1.01) + 600 + 4);
 113
 114        byte[] compressed = new byte[compressedLenMax];
 115
 116        // Prevent GC from moving the byte array, this allow to take the object address and pass it to bzip2 calls.
 117        var compressedHandle = GCHandle.Alloc(compressed, GCHandleType.Pinned);
 118        var bzStream = new BZStream(
 119            compressedHandle.AddrOfPinnedObject() + headerSize + 4,
 120            (uint)(compressed.Length - headerSize - 4));
 121
 122        BzStatus rc;
 123        try
 124        {
 125            rc = (BzStatus)BZ2_bzCompressInit(ref bzStream, compressionLevel, 0, 0);
 126            if (rc != BzStatus.Ok)
 127            {
 128                throw new ProtocolException($"Bzip2 compress init failed {rc}");
 129            }
 130
 131            // Compress the message body, but not the header.
 132            int decompressedLen = buf.size() - headerSize;
 133            byte[] decompressed = buf.b.rawBytes();
 134            var payload = new ArraySegment<byte>(decompressed, headerSize, decompressedLen);
 135
 136            var payloadHandle = GCHandle.Alloc(payload.Array, GCHandleType.Pinned);
 137            bzStream.NextIn = payloadHandle.AddrOfPinnedObject() + payload.Offset;
 138            bzStream.AvailIn = (uint)payload.Count;
 139            Debug.Assert(bzStream.AvailIn > 0);
 140
 141            do
 142            {
 143                rc = (BzStatus)BZ2_bzCompress(ref bzStream, (int)BzAction.Run);
 144            }
 145            while (rc == BzStatus.RunOk && bzStream.AvailIn > 0);
 146            payloadHandle.Free();
 147
 148            if (rc != BzStatus.RunOk)
 149            {
 150                throw new ProtocolException($"Bzip2 compress run failed {rc}");
 151            }
 152
 153            do
 154            {
 155                rc = (BzStatus)BZ2_bzCompress(ref bzStream, (int)BzAction.Finish);
 156            }
 157            while (rc == BzStatus.FinishOk);
 158
 159            if (rc != BzStatus.StreamEnd)
 160            {
 161                throw new ProtocolException($"Bzip2 compress finish failed {rc}");
 162            }
 163
 164            int compressedLen = compressed.Length - (int)bzStream.AvailOut;
 165
 166            // Don't bother if the compressed data is larger than the decompressed data.
 167            if (compressedLen >= decompressedLen)
 168            {
 169                return null;
 170            }
 171
 172            var r = new Buffer(compressed);
 173            r.resize(compressedLen, false);
 174            r.b.position(0);
 175
 176            // Copy the header from the decompressed buffer to the compressed one.
 177            r.b.put(decompressed, 0, headerSize);
 178
 179            // Add the size of the decompressed stream before the message body
 180            r.b.putInt(buf.size());
 181
 182            r.b.position(compressedLen);
 183            return r;
 184        }
 185        finally
 186        {
 187            rc = (BzStatus)BZ2_bzCompressEnd(ref bzStream);
 188            Debug.Assert(rc == BzStatus.Ok);
 189            compressedHandle.Free();
 190        }
 191    }
 192
 193    internal static Buffer decompress(Buffer buf, int headerSize, int messageSizeMax)
 194    {
 195        Debug.Assert(_isLoaded is true);
 196
 197        buf.b.position(headerSize);
 198        int decompressedSize = buf.b.getInt();
 199        if (decompressedSize <= headerSize)
 200        {
 201            throw new MarshalException("compressed size <= header size");
 202        }
 203        if (decompressedSize > messageSizeMax)
 204        {
 205            Ex.throwMemoryLimitException(decompressedSize, messageSizeMax);
 206        }
 207
 208        byte[] compressed = buf.b.rawBytes();
 209
 210        byte[] decompressed = new byte[decompressedSize];
 211        // Prevent GC from moving the byte array, this allow to take the object address and pass it to bzip2 calls.
 212        var decompressedHandle = GCHandle.Alloc(decompressed, GCHandleType.Pinned);
 213        var compressedHandle = GCHandle.Alloc(compressed, GCHandleType.Pinned);
 214        var bzStream = new BZStream(
 215            decompressedHandle.AddrOfPinnedObject() + headerSize,
 216            (uint)(decompressedSize - headerSize));
 217
 218        BzStatus rc;
 219        try
 220        {
 221            rc = (BzStatus)BZ2_bzDecompressInit(ref bzStream, 0, 0);
 222            if (rc != BzStatus.Ok)
 223            {
 224                throw new ProtocolException($"bzip2 decompression failed: {rc}");
 225            }
 226
 227            bzStream.NextIn = compressedHandle.AddrOfPinnedObject() + headerSize + 4;
 228            bzStream.AvailIn = (uint)(compressed.Length - headerSize - 4);
 229            rc = (BzStatus)BZ2_bzDecompress(ref bzStream);
 230            if (rc != BzStatus.StreamEnd)
 231            {
 232                throw new ProtocolException($"bzip2 decompression failed: {rc}");
 233            }
 234        }
 235        finally
 236        {
 237            rc = (BzStatus)BZ2_bzDecompressEnd(ref bzStream);
 238            Debug.Assert(rc == BzStatus.Ok);
 239            decompressedHandle.Free();
 240            compressedHandle.Free();
 241        }
 242        compressed.AsSpan(0, headerSize).CopyTo(decompressed);
 243        return new Buffer(decompressed);
 244    }
 245
 246    [DllImport("bzip2", EntryPoint = "BZ2_bzlibVersion", ExactSpelling = true)]
 247    private static extern IntPtr BZ2_bzLibVersion();
 248
 249    [DllImport("bzip2", EntryPoint = "BZ2_bzCompressInit", ExactSpelling = true)]
 250    private static extern int BZ2_bzCompressInit(
 251        ref BZStream stream,
 252        int blockSize100k,
 253        int verbosity,
 254        int workFactor);
 255
 256    [DllImport("bzip2", EntryPoint = "BZ2_bzCompress", ExactSpelling = true)]
 257    private static extern int BZ2_bzCompress(ref BZStream stream, int action);
 258
 259    [DllImport("bzip2", EntryPoint = "BZ2_bzCompressEnd", ExactSpelling = true)]
 260    private static extern int BZ2_bzCompressEnd(ref BZStream stream);
 261
 262    [DllImport("bzip2", EntryPoint = "BZ2_bzDecompressInit", ExactSpelling = true)]
 263    private static extern int BZ2_bzDecompressInit(ref BZStream stream, int verbosity, int small);
 264
 265    [DllImport("bzip2", EntryPoint = "BZ2_bzDecompress", ExactSpelling = true)]
 266    private static extern int BZ2_bzDecompress(ref BZStream stream);
 267
 268    [DllImport("bzip2", EntryPoint = "BZ2_bzDecompressEnd", ExactSpelling = true)]
 269    private static extern int BZ2_bzDecompressEnd(ref BZStream stream);
 270
 271    private static IntPtr dllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
 272    {
 273        DllNotFoundException? failure = null;
 274        Debug.Assert(libraryName == "bzip2");
 275        foreach (string name in getPlatformNativeLibraryNames())
 276        {
 277            try
 278            {
 279                return NativeLibrary.Load(name, assembly, searchPath);
 280            }
 281            catch (DllNotFoundException ex)
 282            {
 283                failure = ex;
 284            }
 285        }
 286        Debug.Assert(failure is not null);
 287        ExceptionDispatchInfo.Throw(failure);
 288        Debug.Assert(false);
 289        return IntPtr.Zero;
 290    }
 291
 292    private static string[] getPlatformNativeLibraryNames()
 293    {
 294        if (AssemblyUtil.isWindows)
 295        {
 296            return ["bzip2.dll", "bzip2d.dll"];
 297        }
 298        else if (AssemblyUtil.isMacOS)
 299        {
 300            return ["libbz2.dylib"];
 301        }
 302        else
 303        {
 304            return ["libbz2.so.1.0", "libbz2.so.1", "libbz2.so"];
 305        }
 306    }
 307}

Methods/Properties

.ctor(native int, uint)