< Summary

Information
Class: System.IO.Compression.ZstandardDictionary
Assembly: System.IO.Compression
File(s): D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\Zstandard\ZstandardDictionary.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 106
Coverable lines: 106
Total lines: 230
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 32
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
Create(...)100%110%
Create(...)0%10100%
Train(...)0%20200%
Dispose()0%220%
ThrowIfDisposed()100%110%

File(s)

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\Zstandard\ZstandardDictionary.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.Runtime.CompilerServices;
 7using System.Runtime.InteropServices;
 8using Microsoft.Win32.SafeHandles;
 9
 10namespace System.IO.Compression
 11{
 12    /// <summary>Represents a Zstandard compression dictionary.</summary>
 13    [System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
 14    public sealed class ZstandardDictionary : IDisposable
 15    {
 16        private readonly SafeZstdCDictHandle _compressionDictionary;
 17        private readonly SafeZstdDDictHandle _decompressionDictionary;
 18        private readonly byte[] _dictionaryData;
 19        private bool _disposed;
 20
 021        private ZstandardDictionary(SafeZstdCDictHandle compressionDict, SafeZstdDDictHandle decompressionDict, byte[] d
 022        {
 023            _compressionDictionary = compressionDict;
 024            _decompressionDictionary = decompressionDict;
 025            _dictionaryData = data;
 026        }
 27
 28        /// <summary>Creates a Zstandard dictionary from the specified buffer.</summary>
 29        /// <param name="buffer">The buffer containing the dictionary data.</param>
 30        /// <returns>A new <see cref="ZstandardDictionary"/> instance.</returns>
 31        /// <exception cref="ArgumentException">The buffer is empty.</exception>
 032        public static ZstandardDictionary Create(ReadOnlySpan<byte> buffer) => Create(buffer, ZstandardUtils.Quality_Def
 33
 34        /// <summary>Creates a Zstandard dictionary from the specified buffer with the specified quality level and dicti
 35        /// <param name="buffer">The buffer containing the dictionary data.</param>
 36        /// <param name="quality">The quality level for dictionary creation.</param>
 37        /// <returns>A new <see cref="ZstandardDictionary"/> instance.</returns>
 38        /// <exception cref="ArgumentException">The buffer is empty.</exception>
 39        /// <exception cref="IOException">Failed to create the <see cref="ZstandardDictionary"/> instance.</exception>
 40        public static ZstandardDictionary Create(ReadOnlySpan<byte> buffer, int quality)
 041        {
 042            if (buffer.IsEmpty)
 043            {
 044                throw new ArgumentException(SR.ZstandardDictionary_EmptyBuffer, nameof(buffer));
 45            }
 46
 047            byte[] data = buffer.ToArray();
 48
 49
 50            unsafe
 051            {
 052                fixed (byte* dictPtr = data)
 053                {
 054                    SafeZstdCDictHandle compressionDict = Interop.Zstd.ZSTD_createCDict_byReference(dictPtr, (nuint)data
 55
 056                    if (compressionDict.IsInvalid)
 057                    {
 058                        throw new IOException(SR.ZstandardDictionary_CreateCompressionFailed);
 59                    }
 060                    compressionDict._pinnedData = new PinnedGCHandle<byte[]>(data);
 61
 062                    SafeZstdDDictHandle decompressionDict = Interop.Zstd.ZSTD_createDDict_byReference(dictPtr, (nuint)da
 63
 064                    if (decompressionDict.IsInvalid)
 065                    {
 066                        compressionDict.Dispose();
 067                        throw new IOException(SR.ZstandardDictionary_CreateDecompressionFailed);
 68                    }
 069                    decompressionDict._pinnedData = new PinnedGCHandle<byte[]>(data);
 70
 071                    return new ZstandardDictionary(compressionDict, decompressionDict, data);
 72                }
 73            }
 074        }
 75
 76        /// <summary>Trains Zstandard dictionary data using the provided samples.</summary>
 77        /// <param name="samples">All training samples concatenated in one large buffer.</param>
 78        /// <param name="sampleLengths">The lengths of the individual samples. The sum of these lengths must equal the l
 79        /// <param name="maxDictionarySize">The maximum size of the dictionary to create.</param>
 80        /// <returns>A byte array containing the trained dictionary data.</returns>
 81        /// <exception cref="ArgumentException">The sample data or lengths are invalid.</exception>
 82        /// <exception cref="ArgumentOutOfRangeException"><paramref name="maxDictionarySize"/> is less than 256.</except
 83        /// <exception cref="IOException">Failed to train the dictionary.</exception>
 84        /// <remarks>
 85        /// The recommended maximum dictionary size is 100 KB, and the size of the training data
 86        /// should be approximately 100 times the size of the resulting dictionary.
 87        /// The returned data can be stored for later use. Call <see cref="Create(ReadOnlySpan{byte})"/>
 88        /// or <see cref="Create(ReadOnlySpan{byte}, int)"/> to prepare a dictionary for compression or decompression.
 89        /// Specify compression quality when creating the dictionary; its quality takes precedence over
 90        /// <see cref="ZstandardCompressionOptions.Quality"/>.
 91        /// </remarks>
 92        /// <example>
 93        /// <code language="csharp">
 94        /// byte[] dictionaryData = ZstandardDictionary.Train(samples, sampleLengths, maxDictionarySize);
 95        /// using ZstandardDictionary dictionary = ZstandardDictionary.Create(dictionaryData, quality);
 96        /// </code>
 97        /// </example>
 98        public static byte[] Train(ReadOnlySpan<byte> samples, ReadOnlySpan<int> sampleLengths, int maxDictionarySize)
 099        {
 0100            if (samples.IsEmpty)
 0101            {
 0102                throw new ArgumentException(SR.ZstandardDictionary_EmptyBuffer, nameof(samples));
 103            }
 104
 105            // this requirement is enforced by zstd native library, probably due to the underlying algorithm design
 0106            if (sampleLengths.Length < 5)
 0107            {
 0108                throw new ArgumentException(SR.Format(SR.ZstandardDictionary_Train_MinimumSampleCount, 5), nameof(sample
 109            }
 110
 111            // the lengths need to be converted to nuint for the native call. Rent appropriately sized array from pool
 112            // This incidentally also protects against concurrent modifications of the sampleLengths that could cause
 113            // access violations later in native code.
 0114            byte[] lengthsArray = ArrayPool<byte>.Shared.Rent(sampleLengths.Length * sizeof(nuint));
 0115            byte[]? dictionaryBuffer = null;
 116            try
 0117            {
 0118                Span<nuint> lengthsAsNuint = MemoryMarshal.Cast<byte, nuint>(lengthsArray.AsSpan(0, sampleLengths.Length
 0119                Debug.Assert(lengthsAsNuint.Length == sampleLengths.Length);
 120
 121                const double TrainingSplitPoint = 0.75;
 122                const int MinimumTrainingBytes = 8;
 0123                int trainingSampleCount = (int)(sampleLengths.Length * TrainingSplitPoint);
 0124                long trainingLength = 0;
 0125                long totalLength = 0;
 0126                for (int i = 0; i < sampleLengths.Length; i++)
 0127                {
 0128                    int length = sampleLengths[i];
 0129                    if (length <= 0)
 0130                    {
 0131                        throw new ArgumentException(SR.ZstandardDictionary_Train_InvalidSampleLength, nameof(sampleLengt
 132                    }
 0133                    totalLength += length;
 0134                    if (i < trainingSampleCount)
 0135                    {
 0136                        trainingLength += length;
 0137                    }
 0138                    lengthsAsNuint[i] = (nuint)length;
 0139                }
 140
 0141                if (totalLength != samples.Length)
 0142                {
 0143                    throw new ArgumentException(SR.ZstandardDictionary_SampleLengthsMismatch, nameof(sampleLengths));
 144                }
 145
 0146                ArgumentOutOfRangeException.ThrowIfLessThan(maxDictionarySize, 256, nameof(maxDictionarySize));
 147
 148                // zstd checks the total size, but its 75% training subset must contain at least 8 bytes to avoid underf
 149                // Remove this workaround once https://github.com/facebook/zstd/issues/4827 is fixed in the bundled vers
 0150                if (trainingLength < MinimumTrainingBytes)
 0151                {
 0152                    ZstandardUtils.Throw(Interop.Zstd.ZSTD_error.srcSize_wrong);
 153                }
 154
 0155                dictionaryBuffer = ArrayPool<byte>.Shared.Rent(maxDictionarySize);
 156                nuint dictSize;
 157
 158                unsafe
 0159                {
 0160                    fixed (byte* samplesPtr = &MemoryMarshal.GetReference(samples))
 0161                    fixed (byte* dictPtr = dictionaryBuffer)
 0162                    fixed (nuint* lengthsAsNuintPtr = &MemoryMarshal.GetReference(lengthsAsNuint))
 0163                    {
 0164                        dictSize = Interop.Zstd.ZDICT_trainFromBuffer(
 0165                                dictPtr, (nuint)maxDictionarySize,
 0166                                samplesPtr, lengthsAsNuintPtr, (uint)sampleLengths.Length);
 0167                    }
 168
 0169                    ZstandardUtils.ThrowIfError(dictSize);
 0170                    return dictionaryBuffer.AsSpan(0, (int)dictSize).ToArray();
 171                }
 172            }
 173            finally
 0174            {
 0175                if (dictionaryBuffer is not null)
 0176                {
 0177                    ArrayPool<byte>.Shared.Return(dictionaryBuffer);
 0178                }
 0179                ArrayPool<byte>.Shared.Return(lengthsArray);
 0180            }
 0181        }
 182
 183        /// <summary>Gets the compression dictionary handle.</summary>
 184        internal SafeZstdCDictHandle CompressionDictionary
 185        {
 186            get
 0187            {
 0188                ThrowIfDisposed();
 0189                return _compressionDictionary;
 0190            }
 191        }
 192
 193        /// <summary>Gets the decompression dictionary handle.</summary>
 194        internal SafeZstdDDictHandle DecompressionDictionary
 195        {
 196            get
 0197            {
 0198                ThrowIfDisposed();
 0199                return _decompressionDictionary;
 0200            }
 201        }
 202
 203        /// <summary>Gets the dictionary data.</summary>
 204        /// <value>The raw dictionary bytes.</value>
 205        public ReadOnlyMemory<byte> Data
 206        {
 207            get
 0208            {
 0209                ThrowIfDisposed();
 0210                return _dictionaryData;
 0211            }
 212        }
 213
 214        /// <summary>Releases all resources used by the <see cref="ZstandardDictionary"/>.</summary>
 215        public void Dispose()
 0216        {
 0217            if (!_disposed)
 0218            {
 0219                _compressionDictionary.Dispose();
 0220                _decompressionDictionary.Dispose();
 0221                _disposed = true;
 0222            }
 0223        }
 224
 225        private void ThrowIfDisposed()
 0226        {
 0227            ObjectDisposedException.ThrowIf(_disposed, this);
 0228        }
 229    }
 230}