< Summary

Information
Class: Ice.Internal.BZip2
Assembly: Ice
File(s): /_/csharp/src/Ice/Internal/BZip2.cs
Tag: 125_37167941578
Line coverage
73%
Covered lines: 79
Uncovered lines: 29
Coverable lines: 108
Total lines: 308
Line coverage: 73.1%
Branch coverage
67%
Covered branches: 23
Total branches: 34
Branch coverage: 67.6%
Method coverage
100%
Covered methods: 6
Fully covered methods: 1
Total methods: 6
Method coverage: 100%
Full method coverage: 16.6%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
isLoaded(...)66.67%13640.74%
compress(...)78.57%141492.11%
decompress(...)62.5%8889.29%
dllImportResolver(...)50%3244.44%
getPlatformNativeLibraryNames()50%5460%

File(s)

/_/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        Finish = 2
 40    }
 41
 42    private enum BzStatus
 43    {
 44        Ok = 0,
 45        RunOk = 1,
 46        FinishOk = 3,
 47        StreamEnd = 4,
 48
 49        SequenceError = -1,
 50        ParamError = -2,
 51        MemError = -3,
 52        DataError = -4,
 53        DataErrorMagic = -5,
 54        IoError = -6,
 55        UnexpectedEof = -7,
 56        OutbuffFull = -8,
 57        ConfigError = -9
 58    }
 59
 160    private static readonly object _mutex = new object();
 61    private static bool? _isLoaded;
 62
 63    public static bool isLoaded(Ice.Logger logger)
 64    {
 165        lock (_mutex)
 66        {
 167            if (_isLoaded is null)
 68            {
 69                // Register a delegate to load native libraries used by Ice assembly.
 170                NativeLibrary.SetDllImportResolver(Assembly.GetAssembly(typeof(BZip2))!, dllImportResolver);
 171                string libNames = string.Join(", ", getPlatformNativeLibraryNames()).TrimEnd();
 172                bool loaded = false;
 73                try
 74                {
 175                    BZ2_bzLibVersion();
 176                    loaded = true;
 177                }
 078                catch (EntryPointNotFoundException)
 79                {
 080                    logger.warning($"found {libNames} but entry point BZ2_bzlibVersion is missing.");
 081                }
 082                catch (TypeLoadException)
 83                {
 84                    // Expected -- bzip2 lib not installed or not in PATH.
 085                }
 086                catch (BadImageFormatException)
 87                {
 088                    var msg = new StringBuilder();
 089                    msg.AppendLine(
 090                        CultureInfo.InvariantCulture,
 091                        $"{libNames} could not be loaded (likely due to 32/64-bit mismatch).");
 092                    if (IntPtr.Size == 8)
 93                    {
 094                        msg.AppendLine(
 095                            CultureInfo.InvariantCulture,
 096                            $"Make sure the directory containing the 64-bit {libNames} is in your PATH.");
 97                    }
 098                    logger.warning(msg.ToString());
 099                }
 1100                _isLoaded = loaded;
 101            }
 1102            return _isLoaded.Value;
 103        }
 1104    }
 105
 106    internal static Buffer? compress(Buffer buf, int headerSize, int compressionLevel)
 107    {
 108        Debug.Assert(_isLoaded is true);
 109        // In the worst case, the compressed buffer will be 1% larger than the decompressed buffer plus 600 bytes
 110        // for the bzip2 header, plus 4 bytes for the decompressed size added by Ice protocol.
 1111        int compressedLenMax = (int)((buf.size() * 1.01) + 600 + 4);
 112
 1113        byte[] compressed = new byte[compressedLenMax];
 114
 115        // Prevent GC from moving the byte array, this allow to take the object address and pass it to bzip2 calls.
 1116        var compressedHandle = GCHandle.Alloc(compressed, GCHandleType.Pinned);
 1117        var bzStream = new BZStream(
 1118            compressedHandle.AddrOfPinnedObject() + headerSize + 4,
 1119            (uint)(compressed.Length - headerSize - 4));
 120
 121        BzStatus rc;
 122        try
 123        {
 1124            rc = (BzStatus)BZ2_bzCompressInit(ref bzStream, compressionLevel, 0, 0);
 1125            if (rc != BzStatus.Ok)
 126            {
 0127                throw new ProtocolException($"Bzip2 compress init failed {rc}");
 128            }
 129
 130            // Compress the message body, but not the header.
 1131            int decompressedLen = buf.size() - headerSize;
 1132            byte[] decompressed = buf.b.rawBytes();
 1133            var payload = new ArraySegment<byte>(decompressed, headerSize, decompressedLen);
 134
 1135            var payloadHandle = GCHandle.Alloc(payload.Array, GCHandleType.Pinned);
 1136            bzStream.NextIn = payloadHandle.AddrOfPinnedObject() + payload.Offset;
 1137            bzStream.AvailIn = (uint)payload.Count;
 138            Debug.Assert(bzStream.AvailIn > 0);
 139
 140            do
 141            {
 1142                rc = (BzStatus)BZ2_bzCompress(ref bzStream, (int)BzAction.Run);
 143            }
 1144            while (rc == BzStatus.RunOk && bzStream.AvailIn > 0);
 1145            payloadHandle.Free();
 146
 1147            if (rc != BzStatus.RunOk)
 148            {
 0149                throw new ProtocolException($"Bzip2 compress run failed {rc}");
 150            }
 151
 152            do
 153            {
 1154                rc = (BzStatus)BZ2_bzCompress(ref bzStream, (int)BzAction.Finish);
 155            }
 1156            while (rc == BzStatus.FinishOk);
 157
 1158            if (rc != BzStatus.StreamEnd)
 159            {
 0160                throw new ProtocolException($"Bzip2 compress finish failed {rc}");
 161            }
 162
 1163            int compressedLen = compressed.Length - (int)bzStream.AvailOut;
 164
 165            // Don't bother if the compressed data is larger than the decompressed data.
 1166            if (compressedLen >= decompressedLen)
 167            {
 1168                return null;
 169            }
 170
 1171            var r = new Buffer(compressed);
 1172            r.resize(compressedLen, false);
 1173            r.b.position(0);
 174
 175            // Copy the header from the decompressed buffer to the compressed one.
 1176            r.b.put(decompressed, 0, headerSize);
 177
 178            // Add the size of the decompressed stream before the message body
 1179            r.b.putInt(buf.size());
 180
 1181            r.b.position(compressedLen);
 1182            return r;
 183        }
 184        finally
 185        {
 1186            rc = (BzStatus)BZ2_bzCompressEnd(ref bzStream);
 187            Debug.Assert(rc == BzStatus.Ok);
 1188            compressedHandle.Free();
 1189        }
 1190    }
 191
 192    internal static Buffer decompress(Buffer buf, int headerSize, int messageSizeMax)
 193    {
 194        Debug.Assert(_isLoaded is true);
 195
 1196        buf.b.position(headerSize);
 1197        int decompressedSize = buf.b.getInt();
 1198        if (decompressedSize <= headerSize)
 199        {
 0200            throw new MarshalException("decompressed size <= header size");
 201        }
 1202        if (decompressedSize > messageSizeMax)
 203        {
 1204            Ex.throwMemoryLimitException(decompressedSize, messageSizeMax);
 205        }
 206
 1207        byte[] compressed = buf.b.rawBytes();
 208
 1209        byte[] decompressed = new byte[decompressedSize];
 210        // Prevent GC from moving the byte array, this allow to take the object address and pass it to bzip2 calls.
 1211        var decompressedHandle = GCHandle.Alloc(decompressed, GCHandleType.Pinned);
 1212        var compressedHandle = GCHandle.Alloc(compressed, GCHandleType.Pinned);
 1213        var bzStream = new BZStream(
 1214            decompressedHandle.AddrOfPinnedObject() + headerSize,
 1215            (uint)(decompressedSize - headerSize));
 216
 217        BzStatus rc;
 218        try
 219        {
 1220            rc = (BzStatus)BZ2_bzDecompressInit(ref bzStream, 0, 0);
 1221            if (rc != BzStatus.Ok)
 222            {
 0223                throw new ProtocolException($"bzip2 decompression failed: {rc}");
 224            }
 225
 1226            bzStream.NextIn = compressedHandle.AddrOfPinnedObject() + headerSize + 4;
 227            // Size the input from the buffer's logical limit, not the backing array capacity (which can be larger),
 228            // so bzip2 doesn't consume padding past the message. getInt above guarantees limit >= headerSize + 4.
 1229            bzStream.AvailIn = (uint)(buf.b.limit() - headerSize - 4);
 1230            rc = (BzStatus)BZ2_bzDecompress(ref bzStream);
 1231            if (rc != BzStatus.StreamEnd)
 232            {
 0233                throw new ProtocolException($"bzip2 decompression failed: {rc}");
 234            }
 1235        }
 236        finally
 237        {
 1238            rc = (BzStatus)BZ2_bzDecompressEnd(ref bzStream);
 239            Debug.Assert(rc == BzStatus.Ok);
 1240            decompressedHandle.Free();
 1241            compressedHandle.Free();
 1242        }
 1243        compressed.AsSpan(0, headerSize).CopyTo(decompressed);
 1244        return new Buffer(decompressed);
 245    }
 246
 247    [DllImport("bzip2", EntryPoint = "BZ2_bzlibVersion", ExactSpelling = true)]
 248    private static extern IntPtr BZ2_bzLibVersion();
 249
 250    [DllImport("bzip2", EntryPoint = "BZ2_bzCompressInit", ExactSpelling = true)]
 251    private static extern int BZ2_bzCompressInit(
 252        ref BZStream stream,
 253        int blockSize100k,
 254        int verbosity,
 255        int workFactor);
 256
 257    [DllImport("bzip2", EntryPoint = "BZ2_bzCompress", ExactSpelling = true)]
 258    private static extern int BZ2_bzCompress(ref BZStream stream, int action);
 259
 260    [DllImport("bzip2", EntryPoint = "BZ2_bzCompressEnd", ExactSpelling = true)]
 261    private static extern int BZ2_bzCompressEnd(ref BZStream stream);
 262
 263    [DllImport("bzip2", EntryPoint = "BZ2_bzDecompressInit", ExactSpelling = true)]
 264    private static extern int BZ2_bzDecompressInit(ref BZStream stream, int verbosity, int small);
 265
 266    [DllImport("bzip2", EntryPoint = "BZ2_bzDecompress", ExactSpelling = true)]
 267    private static extern int BZ2_bzDecompress(ref BZStream stream);
 268
 269    [DllImport("bzip2", EntryPoint = "BZ2_bzDecompressEnd", ExactSpelling = true)]
 270    private static extern int BZ2_bzDecompressEnd(ref BZStream stream);
 271
 272    private static IntPtr dllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
 273    {
 1274        DllNotFoundException? failure = null;
 275        Debug.Assert(libraryName == "bzip2");
 1276        foreach (string name in getPlatformNativeLibraryNames())
 277        {
 278            try
 279            {
 1280                return NativeLibrary.Load(name, assembly, searchPath);
 281            }
 0282            catch (DllNotFoundException ex)
 283            {
 0284                failure = ex;
 0285            }
 286        }
 287        Debug.Assert(failure is not null);
 0288        ExceptionDispatchInfo.Throw(failure);
 289        Debug.Assert(false);
 0290        return IntPtr.Zero;
 1291    }
 292
 293    private static string[] getPlatformNativeLibraryNames()
 294    {
 1295        if (AssemblyUtil.isWindows)
 296        {
 0297            return ["bzip2.dll", "bzip2d.dll"];
 298        }
 1299        else if (AssemblyUtil.isMacOS)
 300        {
 0301            return ["libbz2.dylib"];
 302        }
 303        else
 304        {
 1305            return ["libbz2.so.1.0", "libbz2.so.1", "libbz2.so"];
 306        }
 307    }
 308}