< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 106
Coverable lines: 106
Total lines: 222
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)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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    [System.Runtime.Versioning.UnsupportedOSPlatform("wasi")]
 15    public sealed class ZstandardDictionary : IDisposable
 16    {
 17        private readonly SafeZstdCDictHandle _compressionDictionary;
 18        private readonly SafeZstdDDictHandle _decompressionDictionary;
 19        private readonly byte[] _dictionaryData;
 20        private bool _disposed;
 21
 022        private ZstandardDictionary(SafeZstdCDictHandle compressionDict, SafeZstdDDictHandle decompressionDict, byte[] d
 023        {
 024            _compressionDictionary = compressionDict;
 025            _decompressionDictionary = decompressionDict;
 026            _dictionaryData = data;
 027        }
 28
 29        /// <summary>Creates a Zstandard dictionary from the specified buffer.</summary>
 30        /// <param name="buffer">The buffer containing the dictionary data.</param>
 31        /// <returns>A new <see cref="ZstandardDictionary"/> instance.</returns>
 32        /// <exception cref="ArgumentException">The buffer is empty.</exception>
 033        public static ZstandardDictionary Create(ReadOnlySpan<byte> buffer) => Create(buffer, ZstandardUtils.Quality_Def
 34
 35        /// <summary>Creates a Zstandard dictionary from the specified buffer with the specified quality level and dicti
 36        /// <param name="buffer">The buffer containing the dictionary data.</param>
 37        /// <param name="quality">The quality level for dictionary creation.</param>
 38        /// <returns>A new <see cref="ZstandardDictionary"/> instance.</returns>
 39        /// <exception cref="ArgumentException">The buffer is empty.</exception>
 40        /// <exception cref="IOException">Failed to create the <see cref="ZstandardDictionary"/> instance.</exception>
 41        public static ZstandardDictionary Create(ReadOnlySpan<byte> buffer, int quality)
 042        {
 043            if (buffer.IsEmpty)
 044            {
 045                throw new ArgumentException(SR.ZstandardDictionary_EmptyBuffer, nameof(buffer));
 46            }
 47
 048            byte[] data = buffer.ToArray();
 49
 50
 51            unsafe
 052            {
 053                fixed (byte* dictPtr = data)
 054                {
 055                    SafeZstdCDictHandle compressionDict = Interop.Zstd.ZSTD_createCDict_byReference(dictPtr, (nuint)data
 56
 057                    if (compressionDict.IsInvalid)
 058                    {
 059                        throw new IOException(SR.ZstandardDictionary_CreateCompressionFailed);
 60                    }
 061                    compressionDict._pinnedData = new PinnedGCHandle<byte[]>(data);
 62
 063                    SafeZstdDDictHandle decompressionDict = Interop.Zstd.ZSTD_createDDict_byReference(dictPtr, (nuint)da
 64
 065                    if (decompressionDict.IsInvalid)
 066                    {
 067                        compressionDict.Dispose();
 068                        throw new IOException(SR.ZstandardDictionary_CreateDecompressionFailed);
 69                    }
 070                    decompressionDict._pinnedData = new PinnedGCHandle<byte[]>(data);
 71
 072                    return new ZstandardDictionary(compressionDict, decompressionDict, data);
 73                }
 74            }
 075        }
 76
 77        /// <summary>Creates a dictionary by training on the provided samples.</summary>
 78        /// <param name="samples">All training samples concatenated in one large buffer.</param>
 79        /// <param name="sampleLengths">The lengths of the individual samples. The sum of these lengths must equal the l
 80        /// <param name="maxDictionarySize">The maximum size of the dictionary to create.</param>
 81        /// <returns>A new <see cref="ZstandardDictionary"/> instance.</returns>
 82        /// <exception cref="ArgumentException">The sample data or lengths are invalid.</exception>
 83        /// <exception cref="ArgumentOutOfRangeException"><paramref name="maxDictionarySize"/> is not between the minimu
 84        /// <exception cref="IOException">Failed to train the dictionary.</exception>
 85        /// <remarks>
 86        /// The recommended maximum dictionary size is 100 KB, and the size of the training data
 87        /// should be approximately 100 times the size of the resulting dictionary.
 88        /// </remarks>
 89        public static ZstandardDictionary Train(ReadOnlySpan<byte> samples, ReadOnlySpan<int> sampleLengths, int maxDict
 090        {
 091            if (samples.IsEmpty)
 092            {
 093                throw new ArgumentException(SR.ZstandardDictionary_EmptyBuffer, nameof(samples));
 94            }
 95
 96            // this requirement is enforced by zstd native library, probably due to the underlying algorithm design
 097            if (sampleLengths.Length < 5)
 098            {
 099                throw new ArgumentException(SR.Format(SR.ZstandardDictionary_Train_MinimumSampleCount, 5), nameof(sample
 100            }
 101
 102            // the lengths need to be converted to nuint for the native call. Rent appropriately sized array from pool
 103            // This incidentally also protects against concurrent modifications of the sampleLengths that could cause
 104            // access violations later in native code.
 0105            byte[] lengthsArray = ArrayPool<byte>.Shared.Rent(sampleLengths.Length * sizeof(nuint));
 0106            byte[]? dictionaryBuffer = null;
 107            try
 0108            {
 0109                Span<nuint> lengthsAsNuint = MemoryMarshal.Cast<byte, nuint>(lengthsArray.AsSpan(0, sampleLengths.Length
 0110                Debug.Assert(lengthsAsNuint.Length == sampleLengths.Length);
 111
 112                const double TrainingSplitPoint = 0.75;
 113                const int MinimumTrainingBytes = 8;
 0114                int trainingSampleCount = (int)(sampleLengths.Length * TrainingSplitPoint);
 0115                long trainingLength = 0;
 0116                long totalLength = 0;
 0117                for (int i = 0; i < sampleLengths.Length; i++)
 0118                {
 0119                    int length = sampleLengths[i];
 0120                    if (length <= 0)
 0121                    {
 0122                        throw new ArgumentException(SR.ZstandardDictionary_Train_InvalidSampleLength, nameof(sampleLengt
 123                    }
 0124                    totalLength += length;
 0125                    if (i < trainingSampleCount)
 0126                    {
 0127                        trainingLength += length;
 0128                    }
 0129                    lengthsAsNuint[i] = (nuint)length;
 0130                }
 131
 0132                if (totalLength != samples.Length)
 0133                {
 0134                    throw new ArgumentException(SR.ZstandardDictionary_SampleLengthsMismatch, nameof(sampleLengths));
 135                }
 136
 0137                ArgumentOutOfRangeException.ThrowIfLessThan(maxDictionarySize, 256, nameof(maxDictionarySize));
 138
 139                // zstd checks the total size, but its 75% training subset must contain at least 8 bytes to avoid underf
 140                // Remove this workaround once https://github.com/facebook/zstd/issues/4827 is fixed in the bundled vers
 0141                if (trainingLength < MinimumTrainingBytes)
 0142                {
 0143                    ZstandardUtils.Throw(Interop.Zstd.ZSTD_error.srcSize_wrong);
 144                }
 145
 0146                dictionaryBuffer = ArrayPool<byte>.Shared.Rent(maxDictionarySize);
 147                nuint dictSize;
 148
 149                unsafe
 0150                {
 0151                    fixed (byte* samplesPtr = &MemoryMarshal.GetReference(samples))
 0152                    fixed (byte* dictPtr = dictionaryBuffer)
 0153                    fixed (nuint* lengthsAsNuintPtr = &MemoryMarshal.GetReference(lengthsAsNuint))
 0154                    {
 0155                        dictSize = Interop.Zstd.ZDICT_trainFromBuffer(
 0156                                dictPtr, (nuint)maxDictionarySize,
 0157                                samplesPtr, lengthsAsNuintPtr, (uint)sampleLengths.Length);
 0158                    }
 159
 0160                    ZstandardUtils.ThrowIfError(dictSize);
 0161                    return Create(dictionaryBuffer.AsSpan(0, (int)dictSize));
 162                }
 163            }
 164            finally
 0165            {
 0166                if (dictionaryBuffer is not null)
 0167                {
 0168                    ArrayPool<byte>.Shared.Return(dictionaryBuffer);
 0169                }
 0170                ArrayPool<byte>.Shared.Return(lengthsArray);
 0171            }
 0172        }
 173
 174        /// <summary>Gets the compression dictionary handle.</summary>
 175        internal SafeZstdCDictHandle CompressionDictionary
 176        {
 177            get
 0178            {
 0179                ThrowIfDisposed();
 0180                return _compressionDictionary;
 0181            }
 182        }
 183
 184        /// <summary>Gets the decompression dictionary handle.</summary>
 185        internal SafeZstdDDictHandle DecompressionDictionary
 186        {
 187            get
 0188            {
 0189                ThrowIfDisposed();
 0190                return _decompressionDictionary;
 0191            }
 192        }
 193
 194        /// <summary>Gets the dictionary data.</summary>
 195        /// <value>The raw dictionary bytes.</value>
 196        public ReadOnlyMemory<byte> Data
 197        {
 198            get
 0199            {
 0200                ThrowIfDisposed();
 0201                return _dictionaryData;
 0202            }
 203        }
 204
 205        /// <summary>Releases all resources used by the <see cref="ZstandardDictionary"/>.</summary>
 206        public void Dispose()
 0207        {
 0208            if (!_disposed)
 0209            {
 0210                _compressionDictionary.Dispose();
 0211                _decompressionDictionary.Dispose();
 0212                _disposed = true;
 0213            }
 0214        }
 215
 216        private void ThrowIfDisposed()
 0217        {
 0218            ObjectDisposedException.ThrowIf(_disposed, this);
 0219        }
 220    }
 221}
 222