internal static ArraySegment <byte> Decompress(ArraySegment <byte> compressed, int headerSize, int messageSizeMax) { Debug.Assert(IsLoaded); int decompressedSize = InputStream.ReadInt(compressed.AsSpan(headerSize, 4)); if (decompressedSize <= headerSize) { throw new InvalidDataException( $"received compressed ice1 frame with a decompressed size of only {decompressedSize} bytes"); } if (decompressedSize > messageSizeMax) { throw new InvalidDataException( $"decompressed size of {decompressedSize} bytes is greater than Ice.MessageSizeMax value"); } byte[] decompressed = new byte[decompressedSize]; // Prevent GC from moving the byte array, this allow to take the object address // and pass it to bzip2 calls. var decompressedHandle = GCHandle.Alloc(decompressed, GCHandleType.Pinned); var compressedHandle = GCHandle.Alloc(compressed.Array, GCHandleType.Pinned); var bzStream = new BZStream(decompressedHandle.AddrOfPinnedObject() + headerSize, (uint)(decompressedSize - headerSize)); BzStatus rc; try { rc = (BzStatus)BZ2_bzDecompressInit(ref bzStream, 0, 0); if (rc != BzStatus.Ok) { throw new TransportException($"bzip2 decompression failed: {rc}"); } bzStream.NextIn = compressedHandle.AddrOfPinnedObject() + compressed.Offset + headerSize + 4; bzStream.AvailIn = (uint)(compressed.Count - headerSize - 4); rc = (BzStatus)BZ2_bzDecompress(ref bzStream); if (rc != BzStatus.StreamEnd) { throw new TransportException($"bzip2 decompression failed: {rc}"); } } finally { rc = (BzStatus)BZ2_bzDecompressEnd(ref bzStream); Debug.Assert(rc == BzStatus.Ok); decompressedHandle.Free(); compressedHandle.Free(); } compressed.AsSpan(0, headerSize).CopyTo(decompressed); return(decompressed); }
private static extern int BZ2_bzDecompressEnd(ref BZStream stream);
private static extern int BZ2_bzDecompressInit(ref BZStream stream, int verbosity, int small);
private static extern int BZ2_bzCompress(ref BZStream stream, int action);
private static extern int BZ2_bzCompressInit(ref BZStream stream, int blockSize100k, int verbosity, int workFactor);
internal static List <ArraySegment <byte> >?Compress( List <ArraySegment <byte> > data, int size, int headerSize, int compressionLevel) { Debug.Assert(IsLoaded); // Compress the frame body, but not the header. int decompressedLen = size - headerSize; // Compress the frame body, but not the header. byte[] compressed = new byte[(int)((decompressedLen * 1.01) + 600)]; // Prevent GC from moving the byte array, this allow to take the object address and pass it to bzip2 calls. var compressedHandle = GCHandle.Alloc(compressed, GCHandleType.Pinned); var bzStream = new BZStream(compressedHandle.AddrOfPinnedObject(), (uint)compressed.Length); ArraySegment <byte> headerSegment = data[0]; BzStatus rc; try { rc = (BzStatus)BZ2_bzCompressInit(ref bzStream, compressionLevel, 0, 0); if (rc != BzStatus.Ok) { throw new TransportException($"bzip2 compression failed: {rc}"); } // Slice the first segment to skip the header, the header is never compressed Debug.Assert(headerSegment.Offset == 0); data[0] = headerSegment.Slice(headerSize); rc = BzStatus.RunOk; for (int i = 0; rc == BzStatus.RunOk && i < data.Count; i++) { ArraySegment <byte> segment = data[i]; var segmentHandle = GCHandle.Alloc(segment.Array, GCHandleType.Pinned); bzStream.NextIn = segmentHandle.AddrOfPinnedObject() + segment.Offset; bzStream.AvailIn = (uint)segment.Count; Debug.Assert(bzStream.AvailIn > 0); do { rc = (BzStatus)BZ2_bzCompress(ref bzStream, (int)BzAction.Run); }while (rc == BzStatus.RunOk && bzStream.AvailIn > 0); segmentHandle.Free(); } if (rc != BzStatus.RunOk) { throw new TransportException($"bzip2 compression failed: {rc}"); } do { rc = (BzStatus)BZ2_bzCompress(ref bzStream, (int)BzAction.Finish); }while (rc == BzStatus.FinishOk); if (rc != BzStatus.StreamEnd) { throw new TransportException($"bzip2 compression failed: {rc}"); } int compressedLen = compressed.Length - (int)bzStream.AvailOut; // Don't bother if the compressed data is larger than the decompressed data. if (compressedLen >= decompressedLen) { return(null); } // Copy the header from the decompressed stream to the compressed one, we use headerSize + 4 to ensure // there is room for the size of the decompressed stream in the first segment. ArraySegment <byte> compressedHeader = new byte[headerSize + 4]; headerSegment.AsSpan(0, headerSize).CopyTo(compressedHeader); int compressedSize = compressedLen + compressedHeader.Count; // Write the compression status and the size of the compressed stream into the header. compressedHeader[9] = 2; OutputStream.WriteInt(compressedSize, compressedHeader.AsSpan(10, 4)); // Write the compression status and size of the compressed stream into the header of the decompressed // stream -- we need this to trace requests correctly. headerSegment[9] = 2; OutputStream.WriteInt(compressedSize, headerSegment.AsSpan(10, 4)); // Add the size of the decompressed stream before the frame body. OutputStream.WriteInt(size, compressedHeader.AsSpan(headerSize, 4)); return(new List <ArraySegment <byte> >(2) { compressedHeader, new ArraySegment <byte>(compressed, 0, compressedLen) }); } finally { // Restore the first segment that was Sliced above to skip the header data[0] = headerSegment; rc = (BzStatus)BZ2_bzCompressEnd(ref bzStream); Debug.Assert(rc == BzStatus.Ok); compressedHandle.Free(); } }