< Summary

Information
Class: Ice.Internal.BZip2
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/Internal/BZip2.cs
Tag: 71_18251537082
Line coverage
73%
Covered lines: 79
Uncovered lines: 29
Coverable lines: 108
Total lines: 307
Line coverage: 73.1%
Branch coverage
67%
Covered branches: 23
Total branches: 34
Branch coverage: 67.6%
Method coverage
100%
Covered methods: 6
Total methods: 6
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
isLoaded(...)66.67%13.49640.74%
compress(...)78.57%14.11492.11%
decompress(...)62.5%8.08889.29%
dllImportResolver(...)50%2.69244.44%
getPlatformNativeLibraryNames()50%5.02460%

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{
 18    internal IntPtr NextIn = IntPtr.Zero;
 19    internal uint AvailIn = 0;
 20    internal uint TotalInLo32 = 0;
 21    internal uint TotalInHi32 = 0;
 22
 23    internal IntPtr NextOut = nextOut;
 24    internal uint AvailOut = availOut;
 25    internal uint TotalOutLo32 = 0;
 26    internal uint TotalOutHi32 = 0;
 27
 28    internal IntPtr State = IntPtr.Zero;
 29    internal IntPtr BzAlloc = IntPtr.Zero;
 30    internal IntPtr BzFree = IntPtr.Zero;
 31    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
 161    private static readonly object _mutex = new object();
 62    private static bool? _isLoaded;
 63
 64    public static bool isLoaded(Ice.Logger logger)
 65    {
 166        lock (_mutex)
 67        {
 168            if (_isLoaded is null)
 69            {
 70                // Register a delegate to load native libraries used by Ice assembly.
 171                NativeLibrary.SetDllImportResolver(Assembly.GetAssembly(typeof(BZip2))!, dllImportResolver);
 172                string libNames = string.Join(", ", getPlatformNativeLibraryNames()).TrimEnd();
 173                bool loaded = false;
 74                try
 75                {
 176                    BZ2_bzLibVersion();
 177                    loaded = true;
 178                }
 079                catch (EntryPointNotFoundException)
 80                {
 081                    logger.warning($"found {libNames} but entry point BZ2_bzlibVersion is missing.");
 082                }
 083                catch (TypeLoadException)
 84                {
 85                    // Expected -- bzip2 lib not installed or not in PATH.
 086                }
 087                catch (BadImageFormatException)
 88                {
 089                    var msg = new StringBuilder();
 090                    msg.AppendLine(
 091                        CultureInfo.InvariantCulture,
 092                        $"{libNames} could not be loaded (likely due to 32/64-bit mismatch).");
 093                    if (IntPtr.Size == 8)
 94                    {
 095                        msg.AppendLine(
 096                            CultureInfo.InvariantCulture,
 097                            $"Make sure the directory containing the 64-bit {libNames} is in your PATH.");
 98                    }
 099                    logger.warning(msg.ToString());
 0100                }
 1101                _isLoaded = loaded;
 102            }
 1103            return _isLoaded.Value;
 104        }
 1105    }
 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.
 1112        int compressedLenMax = (int)((buf.size() * 1.01) + 600 + 4);
 113
 1114        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.
 1117        var compressedHandle = GCHandle.Alloc(compressed, GCHandleType.Pinned);
 1118        var bzStream = new BZStream(
 1119            compressedHandle.AddrOfPinnedObject() + headerSize + 4,
 1120            (uint)(compressed.Length - headerSize - 4));
 121
 122        BzStatus rc;
 123        try
 124        {
 1125            rc = (BzStatus)BZ2_bzCompressInit(ref bzStream, compressionLevel, 0, 0);
 1126            if (rc != BzStatus.Ok)
 127            {
 0128                throw new ProtocolException($"Bzip2 compress init failed {rc}");
 129            }
 130
 131            // Compress the message body, but not the header.
 1132            int decompressedLen = buf.size() - headerSize;
 1133            byte[] decompressed = buf.b.rawBytes();
 1134            var payload = new ArraySegment<byte>(decompressed, headerSize, decompressedLen);
 135
 1136            var payloadHandle = GCHandle.Alloc(payload.Array, GCHandleType.Pinned);
 1137            bzStream.NextIn = payloadHandle.AddrOfPinnedObject() + payload.Offset;
 1138            bzStream.AvailIn = (uint)payload.Count;
 139            Debug.Assert(bzStream.AvailIn > 0);
 140
 141            do
 142            {
 1143                rc = (BzStatus)BZ2_bzCompress(ref bzStream, (int)BzAction.Run);
 144            }
 1145            while (rc == BzStatus.RunOk && bzStream.AvailIn > 0);
 1146            payloadHandle.Free();
 147
 1148            if (rc != BzStatus.RunOk)
 149            {
 0150                throw new ProtocolException($"Bzip2 compress run failed {rc}");
 151            }
 152
 153            do
 154            {
 1155                rc = (BzStatus)BZ2_bzCompress(ref bzStream, (int)BzAction.Finish);
 156            }
 1157            while (rc == BzStatus.FinishOk);
 158
 1159            if (rc != BzStatus.StreamEnd)
 160            {
 0161                throw new ProtocolException($"Bzip2 compress finish failed {rc}");
 162            }
 163
 1164            int compressedLen = compressed.Length - (int)bzStream.AvailOut;
 165
 166            // Don't bother if the compressed data is larger than the decompressed data.
 1167            if (compressedLen >= decompressedLen)
 168            {
 1169                return null;
 170            }
 171
 1172            var r = new Buffer(compressed);
 1173            r.resize(compressedLen, false);
 1174            r.b.position(0);
 175
 176            // Copy the header from the decompressed buffer to the compressed one.
 1177            r.b.put(decompressed, 0, headerSize);
 178
 179            // Add the size of the decompressed stream before the message body
 1180            r.b.putInt(buf.size());
 181
 1182            r.b.position(compressedLen);
 1183            return r;
 184        }
 185        finally
 186        {
 1187            rc = (BzStatus)BZ2_bzCompressEnd(ref bzStream);
 188            Debug.Assert(rc == BzStatus.Ok);
 1189            compressedHandle.Free();
 1190        }
 1191    }
 192
 193    internal static Buffer decompress(Buffer buf, int headerSize, int messageSizeMax)
 194    {
 195        Debug.Assert(_isLoaded is true);
 196
 1197        buf.b.position(headerSize);
 1198        int decompressedSize = buf.b.getInt();
 1199        if (decompressedSize <= headerSize)
 200        {
 0201            throw new MarshalException("compressed size <= header size");
 202        }
 1203        if (decompressedSize > messageSizeMax)
 204        {
 1205            Ex.throwMemoryLimitException(decompressedSize, messageSizeMax);
 206        }
 207
 1208        byte[] compressed = buf.b.rawBytes();
 209
 1210        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.
 1212        var decompressedHandle = GCHandle.Alloc(decompressed, GCHandleType.Pinned);
 1213        var compressedHandle = GCHandle.Alloc(compressed, GCHandleType.Pinned);
 1214        var bzStream = new BZStream(
 1215            decompressedHandle.AddrOfPinnedObject() + headerSize,
 1216            (uint)(decompressedSize - headerSize));
 217
 218        BzStatus rc;
 219        try
 220        {
 1221            rc = (BzStatus)BZ2_bzDecompressInit(ref bzStream, 0, 0);
 1222            if (rc != BzStatus.Ok)
 223            {
 0224                throw new ProtocolException($"bzip2 decompression failed: {rc}");
 225            }
 226
 1227            bzStream.NextIn = compressedHandle.AddrOfPinnedObject() + headerSize + 4;
 1228            bzStream.AvailIn = (uint)(compressed.Length - headerSize - 4);
 1229            rc = (BzStatus)BZ2_bzDecompress(ref bzStream);
 1230            if (rc != BzStatus.StreamEnd)
 231            {
 0232                throw new ProtocolException($"bzip2 decompression failed: {rc}");
 233            }
 1234        }
 235        finally
 236        {
 1237            rc = (BzStatus)BZ2_bzDecompressEnd(ref bzStream);
 238            Debug.Assert(rc == BzStatus.Ok);
 1239            decompressedHandle.Free();
 1240            compressedHandle.Free();
 1241        }
 1242        compressed.AsSpan(0, headerSize).CopyTo(decompressed);
 1243        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    {
 1273        DllNotFoundException? failure = null;
 274        Debug.Assert(libraryName == "bzip2");
 1275        foreach (string name in getPlatformNativeLibraryNames())
 276        {
 277            try
 278            {
 1279                return NativeLibrary.Load(name, assembly, searchPath);
 280            }
 0281            catch (DllNotFoundException ex)
 282            {
 0283                failure = ex;
 0284            }
 285        }
 286        Debug.Assert(failure is not null);
 0287        ExceptionDispatchInfo.Throw(failure);
 288        Debug.Assert(false);
 0289        return IntPtr.Zero;
 1290    }
 291
 292    private static string[] getPlatformNativeLibraryNames()
 293    {
 1294        if (AssemblyUtil.isWindows)
 295        {
 0296            return ["bzip2.dll", "bzip2d.dll"];
 297        }
 1298        else if (AssemblyUtil.isMacOS)
 299        {
 0300            return ["libbz2.dylib"];
 301        }
 302        else
 303        {
 1304            return ["libbz2.so.1.0", "libbz2.so.1", "libbz2.so"];
 305        }
 306    }
 307}