From 3b83219146ba430cfc159d996791abd571d97f9c Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Fri, 2 Dec 2022 15:33:35 +0000 Subject: [PATCH] Code refactor and cleanup. --- Aaru6.Checksums/Aaru6.Checksums.csproj | 44 +- Aaru6.Checksums/Adler32/neon.cs | 287 ++-- Aaru6.Checksums/Adler32/ssse3.cs | 217 ++- Aaru6.Checksums/Adler32Context.cs | 614 ++++----- Aaru6.Checksums/CRC16CCITTContext.cs | 449 +++--- Aaru6.Checksums/CRC16Context.cs | 1034 +++++++------- Aaru6.Checksums/CRC16IBMContext.cs | 437 +++--- Aaru6.Checksums/CRC32/arm_simd.cs | 159 ++- Aaru6.Checksums/CRC32/clmul.cs | 349 +++-- Aaru6.Checksums/CRC32/vmull.cs | 652 +++++---- Aaru6.Checksums/CRC32Context.cs | 1157 ++++++++-------- Aaru6.Checksums/CRC64/clmul.cs | 149 +- Aaru6.Checksums/CRC64Context.cs | 1049 +++++++------- Aaru6.Checksums/FletcherContext.cs | 1215 ++++++++--------- Aaru6.Checksums/Native.cs | 71 +- Aaru6.Checksums/SpamSumContext.cs | 797 ++++++----- Aaru6.Compression/ADC.cs | 210 ++- Aaru6.Compression/AppleRle.cs | 141 +- Aaru6.Compression/TeleDiskLzh.cs | 680 +++++---- AaruBenchmark/AaruBenchmark.csproj | 40 +- AaruBenchmark/Benchs.cs | 697 +++++----- AaruBenchmark/Checksums/Aaru.cs | 669 +++++---- AaruBenchmark/Checksums/Aaru6.cs | 359 +++-- AaruBenchmark/Checksums/AaruNative.cs | 433 +++--- AaruBenchmark/Checksums/OpenSsl.cs | 337 +++-- AaruBenchmark/Checksums/RHash.cs | 363 +++-- AaruBenchmark/Compression/Aaru.cs | 213 ++- .../Compression/Aaru6Compressions.cs | 145 +- AaruBenchmark/Compression/DotNetZip.cs | 243 ++-- AaruBenchmark/Compression/NetRuntime.cs | 123 +- AaruBenchmark/Compression/SharpCompress.cs | 539 ++++---- AaruBenchmark/Program.cs | 2 +- Benchmarks.ods | Bin 238309 -> 132552 bytes 33 files changed, 6879 insertions(+), 6995 deletions(-) diff --git a/Aaru6.Checksums/Aaru6.Checksums.csproj b/Aaru6.Checksums/Aaru6.Checksums.csproj index 71601bb..2b46892 100644 --- a/Aaru6.Checksums/Aaru6.Checksums.csproj +++ b/Aaru6.Checksums/Aaru6.Checksums.csproj @@ -50,24 +50,24 @@ true - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + @@ -75,17 +75,17 @@ - - - + + + - - - + + + diff --git a/Aaru6.Checksums/Adler32/neon.cs b/Aaru6.Checksums/Adler32/neon.cs index a5575ff..1ec8838 100644 --- a/Aaru6.Checksums/Adler32/neon.cs +++ b/Aaru6.Checksums/Adler32/neon.cs @@ -48,177 +48,176 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.Arm; -namespace Aaru6.Checksums.Adler32 +namespace Aaru6.Checksums.Adler32; + +internal static class neon { - internal static class neon + internal static void Step(ref ushort preSum1, ref ushort preSum2, byte[] buf, uint len) { - internal static void Step(ref ushort preSum1, ref ushort preSum2, byte[] buf, uint len) + /* + * Split Adler-32 into component sums. + */ + uint s1 = preSum1; + uint s2 = preSum2; + + int bufPos = 0; + + /* + * Process the data in blocks. + */ + uint BLOCK_SIZE = 1 << 5; + uint blocks = len / BLOCK_SIZE; + len -= blocks * BLOCK_SIZE; + + while(blocks != 0) { + uint n = Adler32Context.NMAX / BLOCK_SIZE; /* The NMAX constraint. */ + + if(n > blocks) + n = blocks; + + blocks -= n; /* - * Split Adler-32 into component sums. + * Process n blocks of data. At most NMAX data bytes can be + * processed before s2 must be reduced modulo ADLER_MODULE. */ - uint s1 = preSum1; - uint s2 = preSum2; + Vector128 v_s2 = Vector128.Create(s1 * n, 0, 0, 0); + Vector128 v_s1 = Vector128.Create(0u, 0, 0, 0); + Vector128 v_column_sum_1 = AdvSimd.DuplicateToVector128((ushort)0); + Vector128 v_column_sum_2 = AdvSimd.DuplicateToVector128((ushort)0); + Vector128 v_column_sum_3 = AdvSimd.DuplicateToVector128((ushort)0); + Vector128 v_column_sum_4 = AdvSimd.DuplicateToVector128((ushort)0); - int bufPos = 0; - - /* - * Process the data in blocks. - */ - uint BLOCK_SIZE = 1 << 5; - uint blocks = len / BLOCK_SIZE; - len -= blocks * BLOCK_SIZE; - - while(blocks != 0) + do { - uint n = Adler32Context.NMAX / BLOCK_SIZE; /* The NMAX constraint. */ - - if(n > blocks) - n = blocks; - - blocks -= n; /* - * Process n blocks of data. At most NMAX data bytes can be - * processed before s2 must be reduced modulo ADLER_MODULE. + * Load 32 input bytes. */ - Vector128 v_s2 = Vector128.Create(s1 * n, 0, 0, 0); - Vector128 v_s1 = Vector128.Create(0u, 0, 0, 0); - Vector128 v_column_sum_1 = AdvSimd.DuplicateToVector128((ushort)0); - Vector128 v_column_sum_2 = AdvSimd.DuplicateToVector128((ushort)0); - Vector128 v_column_sum_3 = AdvSimd.DuplicateToVector128((ushort)0); - Vector128 v_column_sum_4 = AdvSimd.DuplicateToVector128((ushort)0); + Vector128 bytes1 = Vector128.Create(buf[bufPos], buf[bufPos + 1], buf[bufPos + 2], + buf[bufPos + 3], buf[bufPos + 4], buf[bufPos + 5], + buf[bufPos + 6], buf[bufPos + 7], buf[bufPos + 8], + buf[bufPos + 9], buf[bufPos + 10], buf[bufPos + 11], + buf[bufPos + 12], buf[bufPos + 13], buf[bufPos + 14], + buf[bufPos + 15]); - do - { - /* - * Load 32 input bytes. - */ - Vector128 bytes1 = Vector128.Create(buf[bufPos], buf[bufPos + 1], buf[bufPos + 2], - buf[bufPos + 3], buf[bufPos + 4], buf[bufPos + 5], - buf[bufPos + 6], buf[bufPos + 7], buf[bufPos + 8], - buf[bufPos + 9], buf[bufPos + 10], buf[bufPos + 11], - buf[bufPos + 12], buf[bufPos + 13], buf[bufPos + 14], - buf[bufPos + 15]); + bufPos += 16; - bufPos += 16; + Vector128 bytes2 = Vector128.Create(buf[bufPos], buf[bufPos + 1], buf[bufPos + 2], + buf[bufPos + 3], buf[bufPos + 4], buf[bufPos + 5], + buf[bufPos + 6], buf[bufPos + 7], buf[bufPos + 8], + buf[bufPos + 9], buf[bufPos + 10], buf[bufPos + 11], + buf[bufPos + 12], buf[bufPos + 13], buf[bufPos + 14], + buf[bufPos + 15]); - Vector128 bytes2 = Vector128.Create(buf[bufPos], buf[bufPos + 1], buf[bufPos + 2], - buf[bufPos + 3], buf[bufPos + 4], buf[bufPos + 5], - buf[bufPos + 6], buf[bufPos + 7], buf[bufPos + 8], - buf[bufPos + 9], buf[bufPos + 10], buf[bufPos + 11], - buf[bufPos + 12], buf[bufPos + 13], buf[bufPos + 14], - buf[bufPos + 15]); - - bufPos += 16; - /* - * Add previous block byte sum to v_s2. - */ - v_s2 = AdvSimd.Add(v_s2, v_s1); - - /* - * Horizontally add the bytes for s1. - */ - v_s1 = - AdvSimd.AddPairwiseWideningAndAdd(v_s1, - AdvSimd. - AddPairwiseWideningAndAdd(AdvSimd.AddPairwiseWidening(bytes1), - bytes2)); - - /* - * Vertically add the bytes for s2. - */ - v_column_sum_1 = AdvSimd.AddWideningLower(v_column_sum_1, bytes1.GetLower()); - v_column_sum_2 = AdvSimd.AddWideningLower(v_column_sum_2, bytes1.GetUpper()); - v_column_sum_3 = AdvSimd.AddWideningLower(v_column_sum_3, bytes2.GetLower()); - v_column_sum_4 = AdvSimd.AddWideningLower(v_column_sum_4, bytes2.GetUpper()); - } while(--n != 0); - - v_s2 = AdvSimd.ShiftLeftLogical(v_s2, 5); + bufPos += 16; + /* + * Add previous block byte sum to v_s2. + */ + v_s2 = AdvSimd.Add(v_s2, v_s1); /* - * Multiply-add bytes by [ 32, 31, 30, ... ] for s2. + * Horizontally add the bytes for s1. */ - v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_1.GetLower(), - Vector64.Create((ushort)32, 31, 30, 29)); - - v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_1.GetUpper(), - Vector64.Create((ushort)28, 27, 26, 25)); - - v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_2.GetLower(), - Vector64.Create((ushort)24, 23, 22, 21)); - - v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_2.GetUpper(), - Vector64.Create((ushort)20, 19, 18, 17)); - - v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_3.GetLower(), - Vector64.Create((ushort)16, 15, 14, 13)); - - v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_3.GetUpper(), - Vector64.Create((ushort)12, 11, 10, 9)); - - v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_4.GetLower(), - Vector64.Create((ushort)8, 7, 6, 5)); - - v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_4.GetUpper(), - Vector64.Create((ushort)4, 3, 2, 1)); + v_s1 = + AdvSimd.AddPairwiseWideningAndAdd(v_s1, + AdvSimd. + AddPairwiseWideningAndAdd(AdvSimd.AddPairwiseWidening(bytes1), + bytes2)); /* - * Sum epi32 ints v_s1(s2) and accumulate in s1(s2). + * Vertically add the bytes for s2. */ - Vector64 sum1 = AdvSimd.AddPairwise(v_s1.GetLower(), v_s1.GetUpper()); - Vector64 sum2 = AdvSimd.AddPairwise(v_s2.GetLower(), v_s2.GetUpper()); - Vector64 s1s2 = AdvSimd.AddPairwise(sum1, sum2); - s1 += AdvSimd.Extract(s1s2, 0); - s2 += AdvSimd.Extract(s1s2, 1); - /* - * Reduce. - */ - s1 %= Adler32Context.ADLER_MODULE; - s2 %= Adler32Context.ADLER_MODULE; - } + v_column_sum_1 = AdvSimd.AddWideningLower(v_column_sum_1, bytes1.GetLower()); + v_column_sum_2 = AdvSimd.AddWideningLower(v_column_sum_2, bytes1.GetUpper()); + v_column_sum_3 = AdvSimd.AddWideningLower(v_column_sum_3, bytes2.GetLower()); + v_column_sum_4 = AdvSimd.AddWideningLower(v_column_sum_4, bytes2.GetUpper()); + } while(--n != 0); + + v_s2 = AdvSimd.ShiftLeftLogical(v_s2, 5); /* - * Handle leftover data. + * Multiply-add bytes by [ 32, 31, 30, ... ] for s2. */ - if(len != 0) - { - if(len >= 16) - { - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - len -= 16; - } + v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_1.GetLower(), + Vector64.Create((ushort)32, 31, 30, 29)); - while(len-- != 0) - { - s2 += s1 += buf[bufPos++]; - } + v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_1.GetUpper(), + Vector64.Create((ushort)28, 27, 26, 25)); - if(s1 >= Adler32Context.ADLER_MODULE) - s1 -= Adler32Context.ADLER_MODULE; + v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_2.GetLower(), + Vector64.Create((ushort)24, 23, 22, 21)); - s2 %= Adler32Context.ADLER_MODULE; - } + v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_2.GetUpper(), + Vector64.Create((ushort)20, 19, 18, 17)); + + v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_3.GetLower(), + Vector64.Create((ushort)16, 15, 14, 13)); + + v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_3.GetUpper(), + Vector64.Create((ushort)12, 11, 10, 9)); + + v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_4.GetLower(), + Vector64.Create((ushort)8, 7, 6, 5)); + + v_s2 = AdvSimd.MultiplyWideningLowerAndAdd(v_s2, v_column_sum_4.GetUpper(), + Vector64.Create((ushort)4, 3, 2, 1)); /* - * Return the recombined sums. + * Sum epi32 ints v_s1(s2) and accumulate in s1(s2). */ - preSum1 = (ushort)(s1 & 0xFFFF); - preSum2 = (ushort)(s2 & 0xFFFF); + Vector64 sum1 = AdvSimd.AddPairwise(v_s1.GetLower(), v_s1.GetUpper()); + Vector64 sum2 = AdvSimd.AddPairwise(v_s2.GetLower(), v_s2.GetUpper()); + Vector64 s1s2 = AdvSimd.AddPairwise(sum1, sum2); + s1 += AdvSimd.Extract(s1s2, 0); + s2 += AdvSimd.Extract(s1s2, 1); + /* + * Reduce. + */ + s1 %= Adler32Context.ADLER_MODULE; + s2 %= Adler32Context.ADLER_MODULE; } + + /* + * Handle leftover data. + */ + if(len != 0) + { + if(len >= 16) + { + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + len -= 16; + } + + while(len-- != 0) + { + s2 += s1 += buf[bufPos++]; + } + + if(s1 >= Adler32Context.ADLER_MODULE) + s1 -= Adler32Context.ADLER_MODULE; + + s2 %= Adler32Context.ADLER_MODULE; + } + + /* + * Return the recombined sums. + */ + preSum1 = (ushort)(s1 & 0xFFFF); + preSum2 = (ushort)(s2 & 0xFFFF); } } \ No newline at end of file diff --git a/Aaru6.Checksums/Adler32/ssse3.cs b/Aaru6.Checksums/Adler32/ssse3.cs index a0888d9..0dcc40e 100644 --- a/Aaru6.Checksums/Adler32/ssse3.cs +++ b/Aaru6.Checksums/Adler32/ssse3.cs @@ -49,138 +49,137 @@ using System; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace Aaru6.Checksums.Adler32 +namespace Aaru6.Checksums.Adler32; + +internal static class ssse3 { - internal static class ssse3 + internal static void Step(ref ushort sum1, ref ushort sum2, byte[] buf, uint len) { - internal static void Step(ref ushort sum1, ref ushort sum2, byte[] buf, uint len) + uint s1 = sum1; + uint s2 = sum2; + int bufPos = 0; + + /* + * Process the data in blocks. + */ + uint BLOCK_SIZE = 1 << 5; + uint blocks = len / BLOCK_SIZE; + len -= blocks * BLOCK_SIZE; + + while(blocks != 0) { - uint s1 = sum1; - uint s2 = sum2; - int bufPos = 0; + uint n = Adler32Context.NMAX / BLOCK_SIZE; /* The NMAX constraint. */ + if(n > blocks) + n = blocks; + + blocks -= n; + + Vector128 tap1 = Vector128.Create(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17). + AsByte(); + + Vector128 tap2 = Vector128.Create(16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1).AsByte(); + Vector128 zero = Vector128.Create(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).AsByte(); + Vector128 ones = Vector128.Create(1, 1, 1, 1, 1, 1, 1, 1); /* - * Process the data in blocks. + * Process n blocks of data. At most NMAX data bytes can be + * processed before s2 must be reduced modulo BASE. */ - uint BLOCK_SIZE = 1 << 5; - uint blocks = len / BLOCK_SIZE; - len -= blocks * BLOCK_SIZE; + Vector128 v_ps = Vector128.Create(s1 * n, 0, 0, 0); + Vector128 v_s2 = Vector128.Create(s2, 0, 0, 0); + Vector128 v_s1 = Vector128.Create(0u, 0, 0, 0); - while(blocks != 0) + do { - uint n = Adler32Context.NMAX / BLOCK_SIZE; /* The NMAX constraint. */ - - if(n > blocks) - n = blocks; - - blocks -= n; - - Vector128 tap1 = Vector128.Create(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17). - AsByte(); - - Vector128 tap2 = Vector128.Create(16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1).AsByte(); - Vector128 zero = Vector128.Create(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).AsByte(); - Vector128 ones = Vector128.Create(1, 1, 1, 1, 1, 1, 1, 1); /* - * Process n blocks of data. At most NMAX data bytes can be - * processed before s2 must be reduced modulo BASE. + * Load 32 input bytes. */ - Vector128 v_ps = Vector128.Create(s1 * n, 0, 0, 0); - Vector128 v_s2 = Vector128.Create(s2, 0, 0, 0); - Vector128 v_s1 = Vector128.Create(0u, 0, 0, 0); + Vector128 bytes1 = Vector128.Create(BitConverter.ToUInt32(buf, bufPos), + BitConverter.ToUInt32(buf, bufPos + 4), + BitConverter.ToUInt32(buf, bufPos + 8), + BitConverter.ToUInt32(buf, bufPos + 12)); - do - { - /* - * Load 32 input bytes. - */ - Vector128 bytes1 = Vector128.Create(BitConverter.ToUInt32(buf, bufPos), - BitConverter.ToUInt32(buf, bufPos + 4), - BitConverter.ToUInt32(buf, bufPos + 8), - BitConverter.ToUInt32(buf, bufPos + 12)); + bufPos += 16; - bufPos += 16; + Vector128 bytes2 = Vector128.Create(BitConverter.ToUInt32(buf, bufPos), + BitConverter.ToUInt32(buf, bufPos + 4), + BitConverter.ToUInt32(buf, bufPos + 8), + BitConverter.ToUInt32(buf, bufPos + 12)); - Vector128 bytes2 = Vector128.Create(BitConverter.ToUInt32(buf, bufPos), - BitConverter.ToUInt32(buf, bufPos + 4), - BitConverter.ToUInt32(buf, bufPos + 8), - BitConverter.ToUInt32(buf, bufPos + 12)); + bufPos += 16; - bufPos += 16; - - /* - * Add previous block byte sum to v_ps. - */ - v_ps = Sse2.Add(v_ps, v_s1); - /* - * Horizontally add the bytes for s1, multiply-adds the - * bytes by [ 32, 31, 30, ... ] for s2. - */ - v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1.AsByte(), zero).AsUInt32()); - Vector128 mad1 = Ssse3.MultiplyAddAdjacent(bytes1.AsByte(), tap1.AsSByte()); - v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1.AsInt16(), ones.AsInt16()).AsUInt32()); - v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2.AsByte(), zero).AsUInt32()); - Vector128 mad2 = Ssse3.MultiplyAddAdjacent(bytes2.AsByte(), tap2.AsSByte()); - v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2.AsInt16(), ones.AsInt16()).AsUInt32()); - } while(--n != 0); - - v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5)); /* - * Sum epi32 ints v_s1(s2) and accumulate in s1(s2). + * Add previous block byte sum to v_ps. */ - v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, 177)); - v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, 78)); - s1 += (uint)Sse2.ConvertToInt32(v_s1.AsInt32()); - v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, 177)); - v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, 78)); - s2 = (uint)Sse2.ConvertToInt32(v_s2.AsInt32()); + v_ps = Sse2.Add(v_ps, v_s1); /* - * Reduce. + * Horizontally add the bytes for s1, multiply-adds the + * bytes by [ 32, 31, 30, ... ] for s2. */ - s1 %= Adler32Context.ADLER_MODULE; - s2 %= Adler32Context.ADLER_MODULE; - } + v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1.AsByte(), zero).AsUInt32()); + Vector128 mad1 = Ssse3.MultiplyAddAdjacent(bytes1.AsByte(), tap1.AsSByte()); + v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1.AsInt16(), ones.AsInt16()).AsUInt32()); + v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2.AsByte(), zero).AsUInt32()); + Vector128 mad2 = Ssse3.MultiplyAddAdjacent(bytes2.AsByte(), tap2.AsSByte()); + v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2.AsInt16(), ones.AsInt16()).AsUInt32()); + } while(--n != 0); + v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5)); /* - * Handle leftover data. + * Sum epi32 ints v_s1(s2) and accumulate in s1(s2). */ - if(len != 0) - { - if(len >= 16) - { - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - s2 += s1 += buf[bufPos++]; - len -= 16; - } - - while(len-- != 0) - s2 += s1 += buf[bufPos++]; - - if(s1 >= Adler32Context.ADLER_MODULE) - s1 -= Adler32Context.ADLER_MODULE; - - s2 %= Adler32Context.ADLER_MODULE; - } - + v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, 177)); + v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, 78)); + s1 += (uint)Sse2.ConvertToInt32(v_s1.AsInt32()); + v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, 177)); + v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, 78)); + s2 = (uint)Sse2.ConvertToInt32(v_s2.AsInt32()); /* - * Return the recombined sums. + * Reduce. */ - sum1 = (ushort)(s1 & 0xFFFF); - sum2 = (ushort)(s2 & 0xFFFF); + s1 %= Adler32Context.ADLER_MODULE; + s2 %= Adler32Context.ADLER_MODULE; } + + /* + * Handle leftover data. + */ + if(len != 0) + { + if(len >= 16) + { + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + s2 += s1 += buf[bufPos++]; + len -= 16; + } + + while(len-- != 0) + s2 += s1 += buf[bufPos++]; + + if(s1 >= Adler32Context.ADLER_MODULE) + s1 -= Adler32Context.ADLER_MODULE; + + s2 %= Adler32Context.ADLER_MODULE; + } + + /* + * Return the recombined sums. + */ + sum1 = (ushort)(s1 & 0xFFFF); + sum2 = (ushort)(s2 & 0xFFFF); } } \ No newline at end of file diff --git a/Aaru6.Checksums/Adler32Context.cs b/Aaru6.Checksums/Adler32Context.cs index 507ad37..9215d16 100644 --- a/Aaru6.Checksums/Adler32Context.cs +++ b/Aaru6.Checksums/Adler32Context.cs @@ -46,363 +46,363 @@ using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; using Aaru6.Checksums.Adler32; -namespace Aaru6.Checksums +namespace Aaru6.Checksums; + +/// +/// Implements the Adler-32 algorithm +public sealed class Adler32Context : IChecksum { - /// - /// Implements the Adler-32 algorithm - public sealed class Adler32Context : IChecksum + internal const ushort ADLER_MODULE = 65521; + internal const uint NMAX = 5552; + readonly IntPtr _nativeContext; + readonly bool _useNative; + ushort _sum1, _sum2; + + /// Initializes the Adler-32 sums + public Adler32Context() { - internal const ushort ADLER_MODULE = 65521; - internal const uint NMAX = 5552; - ushort _sum1, _sum2; - readonly bool _useNative; - readonly IntPtr _nativeContext; + _sum1 = 1; + _sum2 = 0; - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern IntPtr adler32_init(); + if(!Native.IsSupported) + return; - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int adler32_update(IntPtr ctx, byte[] data, uint len); + _nativeContext = adler32_init(); + _useNative = _nativeContext != IntPtr.Zero; + } - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int adler32_final(IntPtr ctx, ref uint checksum); + /// + /// Updates the hash with data. + /// Data buffer. + /// Length of buffer to hash. + public void Update(byte[] data, uint len) => Step(ref _sum1, ref _sum2, data, len, _useNative, _nativeContext); - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern void adler32_free(IntPtr ctx); + /// + /// Updates the hash with data. + /// Data buffer. + public void Update(byte[] data) => Update(data, (uint)data.Length); - /// Initializes the Adler-32 sums - public Adler32Context() + /// + /// Returns a byte array of the hash value. + public byte[] Final() + { + uint finalSum = (uint)((_sum2 << 16) | _sum1); + + if(!_useNative) + return BigEndianBitConverter.GetBytes(finalSum); + + adler32_final(_nativeContext, ref finalSum); + adler32_free(_nativeContext); + + return BigEndianBitConverter.GetBytes(finalSum); + } + + /// + /// Returns a hexadecimal representation of the hash value. + public string End() + { + uint finalSum = (uint)((_sum2 << 16) | _sum1); + + if(_useNative) { - _sum1 = 1; - _sum2 = 0; - - if(!Native.IsSupported) - return; - - _nativeContext = adler32_init(); - _useNative = _nativeContext != IntPtr.Zero; - } - - /// - /// Updates the hash with data. - /// Data buffer. - /// Length of buffer to hash. - public void Update(byte[] data, uint len) => Step(ref _sum1, ref _sum2, data, len, _useNative, _nativeContext); - - /// - /// Updates the hash with data. - /// Data buffer. - public void Update(byte[] data) => Update(data, (uint)data.Length); - - /// - /// Returns a byte array of the hash value. - public byte[] Final() - { - uint finalSum = (uint)((_sum2 << 16) | _sum1); - - if(!_useNative) - return BigEndianBitConverter.GetBytes(finalSum); - adler32_final(_nativeContext, ref finalSum); adler32_free(_nativeContext); - - return BigEndianBitConverter.GetBytes(finalSum); } - /// - /// Returns a hexadecimal representation of the hash value. - public string End() + var adlerOutput = new StringBuilder(); + + for(int i = 0; i < BigEndianBitConverter.GetBytes(finalSum).Length; i++) + adlerOutput.Append(BigEndianBitConverter.GetBytes(finalSum)[i].ToString("x2")); + + return adlerOutput.ToString(); + } + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern IntPtr adler32_init(); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int adler32_update(IntPtr ctx, byte[] data, uint len); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int adler32_final(IntPtr ctx, ref uint checksum); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern void adler32_free(IntPtr ctx); + + static void Step(ref ushort preSum1, ref ushort preSum2, byte[] data, uint len, bool useNative, + IntPtr nativeContext) + { + if(useNative) { - uint finalSum = (uint)((_sum2 << 16) | _sum1); + adler32_update(nativeContext, data, len); - if(_useNative) - { - adler32_final(_nativeContext, ref finalSum); - adler32_free(_nativeContext); - } - - var adlerOutput = new StringBuilder(); - - for(int i = 0; i < BigEndianBitConverter.GetBytes(finalSum).Length; i++) - adlerOutput.Append(BigEndianBitConverter.GetBytes(finalSum)[i].ToString("x2")); - - return adlerOutput.ToString(); + return; } - static void Step(ref ushort preSum1, ref ushort preSum2, byte[] data, uint len, bool useNative, IntPtr nativeContext) + if(Ssse3.IsSupported) { - if(useNative) - { - adler32_update(nativeContext, data, len); + ssse3.Step(ref preSum1, ref preSum2, data, len); - return; - } + return; + } - if(Ssse3.IsSupported) - { - ssse3.Step(ref preSum1, ref preSum2, data, len); + if(AdvSimd.IsSupported) + { + neon.Step(ref preSum1, ref preSum2, data, len); - return; - } + return; + } - if(AdvSimd.IsSupported) - { - neon.Step(ref preSum1, ref preSum2, data, len); + uint sum1 = preSum1; + uint sum2 = preSum2; + uint n; + int dataOff = 0; - return; - } + /* in case user likes doing a byte at a time, keep it fast */ + if(len == 1) + { + sum1 += data[dataOff]; - uint sum1 = preSum1; - uint sum2 = preSum2; - uint n; - int dataOff = 0; + if(sum1 >= ADLER_MODULE) + sum1 -= ADLER_MODULE; - /* in case user likes doing a byte at a time, keep it fast */ - if(len == 1) - { - sum1 += data[dataOff]; + sum2 += sum1; - if(sum1 >= ADLER_MODULE) - sum1 -= ADLER_MODULE; - - sum2 += sum1; - - if(sum2 >= ADLER_MODULE) - sum2 -= ADLER_MODULE; - - preSum1 = (ushort)(sum1 & 0xFFFF); - preSum2 = (ushort)(sum2 & 0xFFFF); - - return; - } - - /* in case short lengths are provided, keep it somewhat fast */ - if(len < 16) - { - while(len-- > 0) - { - sum1 += data[dataOff++]; - sum2 += sum1; - } - - if(sum1 >= ADLER_MODULE) - sum1 -= ADLER_MODULE; - - sum2 %= ADLER_MODULE; /* only added so many ADLER_MODULE's */ - preSum1 = (ushort)(sum1 & 0xFFFF); - preSum2 = (ushort)(sum2 & 0xFFFF); - - return; - } - - /* do length NMAX blocks -- requires just one modulo operation */ - while(len >= NMAX) - { - len -= NMAX; - n = NMAX / 16; /* NMAX is divisible by 16 */ - - do - { - sum1 += data[dataOff]; - sum2 += sum1; - sum1 += data[dataOff + 1]; - sum2 += sum1; - sum1 += data[dataOff + 2]; - sum2 += sum1; - sum1 += data[dataOff + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 4]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4 + 2 + 1]; - sum2 += sum1; - - /* 16 sums unrolled */ - dataOff += 16; - } while(--n != 0); - - sum1 %= ADLER_MODULE; - sum2 %= ADLER_MODULE; - } - - /* do remaining bytes (less than NMAX, still just one modulo) */ - if(len != 0) - { - /* avoid modulos if none remaining */ - while(len >= 16) - { - len -= 16; - sum1 += data[dataOff]; - sum2 += sum1; - sum1 += data[dataOff + 1]; - sum2 += sum1; - sum1 += data[dataOff + 2]; - sum2 += sum1; - sum1 += data[dataOff + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 4]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4 + 2 + 1]; - sum2 += sum1; - - dataOff += 16; - } - - while(len-- != 0) - { - sum1 += data[dataOff++]; - sum2 += sum1; - } - - sum1 %= ADLER_MODULE; - sum2 %= ADLER_MODULE; - } + if(sum2 >= ADLER_MODULE) + sum2 -= ADLER_MODULE; preSum1 = (ushort)(sum1 & 0xFFFF); preSum2 = (ushort)(sum2 & 0xFFFF); + + return; } - /// Gets the hash of a file - /// File path. - public static byte[] File(string filename) + /* in case short lengths are provided, keep it somewhat fast */ + if(len < 16) { - File(filename, out byte[] hash); + while(len-- > 0) + { + sum1 += data[dataOff++]; + sum2 += sum1; + } - return hash; + if(sum1 >= ADLER_MODULE) + sum1 -= ADLER_MODULE; + + sum2 %= ADLER_MODULE; /* only added so many ADLER_MODULE's */ + preSum1 = (ushort)(sum1 & 0xFFFF); + preSum2 = (ushort)(sum2 & 0xFFFF); + + return; } - /// Gets the hash of a file in hexadecimal and as a byte array. - /// File path. - /// Byte array of the hash value. - public static string File(string filename, out byte[] hash) + /* do length NMAX blocks -- requires just one modulo operation */ + while(len >= NMAX) { - bool useNative = Native.IsSupported; - IntPtr nativeContext = IntPtr.Zero; + len -= NMAX; + n = NMAX / 16; /* NMAX is divisible by 16 */ - if(useNative) + do { - nativeContext = adler32_init(); + sum1 += data[dataOff]; + sum2 += sum1; + sum1 += data[dataOff + 1]; + sum2 += sum1; + sum1 += data[dataOff + 2]; + sum2 += sum1; + sum1 += data[dataOff + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 4]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4 + 2 + 1]; + sum2 += sum1; - if(nativeContext == IntPtr.Zero) - useNative = false; - } + /* 16 sums unrolled */ + dataOff += 16; + } while(--n != 0); - var fileStream = new FileStream(filename, FileMode.Open); - - ushort localSum1 = 1; - ushort localSum2 = 0; - - byte[] buffer = new byte[65536]; - int read = fileStream.Read(buffer, 0, 65536); - - while(read > 0) - { - Step(ref localSum1, ref localSum2, buffer, (uint)read, useNative, nativeContext); - read = fileStream.Read(buffer, 0, 65536); - } - - uint finalSum = (uint)((localSum2 << 16) | localSum1); - - if(useNative) - { - adler32_final(nativeContext, ref finalSum); - adler32_free(nativeContext); - } - - hash = BigEndianBitConverter.GetBytes(finalSum); - - var adlerOutput = new StringBuilder(); - - foreach(byte h in hash) - adlerOutput.Append(h.ToString("x2")); - - fileStream.Close(); - - return adlerOutput.ToString(); + sum1 %= ADLER_MODULE; + sum2 %= ADLER_MODULE; } - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Length of the data buffer to hash. - /// Byte array of the hash value. - public static string Data(byte[] data, uint len, out byte[] hash) + /* do remaining bytes (less than NMAX, still just one modulo) */ + if(len != 0) { - bool useNative = Native.IsSupported; - IntPtr nativeContext = IntPtr.Zero; - - if(useNative) + /* avoid modulos if none remaining */ + while(len >= 16) { - nativeContext = adler32_init(); + len -= 16; + sum1 += data[dataOff]; + sum2 += sum1; + sum1 += data[dataOff + 1]; + sum2 += sum1; + sum1 += data[dataOff + 2]; + sum2 += sum1; + sum1 += data[dataOff + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 4]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4 + 2 + 1]; + sum2 += sum1; - if(nativeContext == IntPtr.Zero) - useNative = false; + dataOff += 16; } - ushort localSum1 = 1; - ushort localSum2 = 0; - - Step(ref localSum1, ref localSum2, data, len, useNative, nativeContext); - - uint finalSum = (uint)((localSum2 << 16) | localSum1); - - if(useNative) + while(len-- != 0) { - adler32_final(nativeContext, ref finalSum); - adler32_free(nativeContext); + sum1 += data[dataOff++]; + sum2 += sum1; } - hash = BigEndianBitConverter.GetBytes(finalSum); - - var adlerOutput = new StringBuilder(); - - foreach(byte h in hash) - adlerOutput.Append(h.ToString("x2")); - - return adlerOutput.ToString(); + sum1 %= ADLER_MODULE; + sum2 %= ADLER_MODULE; } - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Byte array of the hash value. - public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); + preSum1 = (ushort)(sum1 & 0xFFFF); + preSum2 = (ushort)(sum2 & 0xFFFF); } + + /// Gets the hash of a file + /// File path. + public static byte[] File(string filename) + { + File(filename, out byte[] hash); + + return hash; + } + + /// Gets the hash of a file in hexadecimal and as a byte array. + /// File path. + /// Byte array of the hash value. + public static string File(string filename, out byte[] hash) + { + bool useNative = Native.IsSupported; + IntPtr nativeContext = IntPtr.Zero; + + if(useNative) + { + nativeContext = adler32_init(); + + if(nativeContext == IntPtr.Zero) + useNative = false; + } + + var fileStream = new FileStream(filename, FileMode.Open); + + ushort localSum1 = 1; + ushort localSum2 = 0; + + byte[] buffer = new byte[65536]; + int read = fileStream.Read(buffer, 0, 65536); + + while(read > 0) + { + Step(ref localSum1, ref localSum2, buffer, (uint)read, useNative, nativeContext); + read = fileStream.Read(buffer, 0, 65536); + } + + uint finalSum = (uint)((localSum2 << 16) | localSum1); + + if(useNative) + { + adler32_final(nativeContext, ref finalSum); + adler32_free(nativeContext); + } + + hash = BigEndianBitConverter.GetBytes(finalSum); + + var adlerOutput = new StringBuilder(); + + foreach(byte h in hash) + adlerOutput.Append(h.ToString("x2")); + + fileStream.Close(); + + return adlerOutput.ToString(); + } + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Length of the data buffer to hash. + /// Byte array of the hash value. + public static string Data(byte[] data, uint len, out byte[] hash) + { + bool useNative = Native.IsSupported; + IntPtr nativeContext = IntPtr.Zero; + + if(useNative) + { + nativeContext = adler32_init(); + + if(nativeContext == IntPtr.Zero) + useNative = false; + } + + ushort localSum1 = 1; + ushort localSum2 = 0; + + Step(ref localSum1, ref localSum2, data, len, useNative, nativeContext); + + uint finalSum = (uint)((localSum2 << 16) | localSum1); + + if(useNative) + { + adler32_final(nativeContext, ref finalSum); + adler32_free(nativeContext); + } + + hash = BigEndianBitConverter.GetBytes(finalSum); + + var adlerOutput = new StringBuilder(); + + foreach(byte h in hash) + adlerOutput.Append(h.ToString("x2")); + + return adlerOutput.ToString(); + } + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Byte array of the hash value. + public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); } \ No newline at end of file diff --git a/Aaru6.Checksums/CRC16CCITTContext.cs b/Aaru6.Checksums/CRC16CCITTContext.cs index 6fb05e9..bc30b81 100644 --- a/Aaru6.Checksums/CRC16CCITTContext.cs +++ b/Aaru6.Checksums/CRC16CCITTContext.cs @@ -30,238 +30,237 @@ // Copyright © 2011-2023 Natalia Portillo // ****************************************************************************/ -namespace Aaru6.Checksums +namespace Aaru6.Checksums; + +/// +/// Implements the CRC16 algorithm with CCITT polynomial and seed +public sealed class CRC16CCITTContext : Crc16Context { - /// - /// Implements the CRC16 algorithm with CCITT polynomial and seed - public sealed class CRC16CCITTContext : Crc16Context + /// CCITT CRC16 polynomial + public const ushort CRC16_CCITT_POLY = 0x8408; + /// CCITT CRC16 seed + public const ushort CRC16_CCITT_SEED = 0x0000; + static readonly ushort[][] _ccittCrc16Table = { - /// CCITT CRC16 polynomial - public const ushort CRC16_CCITT_POLY = 0x8408; - /// CCITT CRC16 seed - public const ushort CRC16_CCITT_SEED = 0x0000; - static readonly ushort[][] _ccittCrc16Table = + new ushort[] { - new ushort[] - { - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, - 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, - 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, - 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, - 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, - 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, - 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, - 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, - 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, - 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, - 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, - 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, - 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, - 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, - 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, - 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, - 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, - 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, - 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, - 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 - }, - new ushort[] - { - 0x0000, 0x3331, 0x6662, 0x5553, 0xCCC4, 0xFFF5, 0xAAA6, 0x9997, 0x89A9, 0xBA98, 0xEFCB, 0xDCFA, 0x456D, - 0x765C, 0x230F, 0x103E, 0x0373, 0x3042, 0x6511, 0x5620, 0xCFB7, 0xFC86, 0xA9D5, 0x9AE4, 0x8ADA, 0xB9EB, - 0xECB8, 0xDF89, 0x461E, 0x752F, 0x207C, 0x134D, 0x06E6, 0x35D7, 0x6084, 0x53B5, 0xCA22, 0xF913, 0xAC40, - 0x9F71, 0x8F4F, 0xBC7E, 0xE92D, 0xDA1C, 0x438B, 0x70BA, 0x25E9, 0x16D8, 0x0595, 0x36A4, 0x63F7, 0x50C6, - 0xC951, 0xFA60, 0xAF33, 0x9C02, 0x8C3C, 0xBF0D, 0xEA5E, 0xD96F, 0x40F8, 0x73C9, 0x269A, 0x15AB, 0x0DCC, - 0x3EFD, 0x6BAE, 0x589F, 0xC108, 0xF239, 0xA76A, 0x945B, 0x8465, 0xB754, 0xE207, 0xD136, 0x48A1, 0x7B90, - 0x2EC3, 0x1DF2, 0x0EBF, 0x3D8E, 0x68DD, 0x5BEC, 0xC27B, 0xF14A, 0xA419, 0x9728, 0x8716, 0xB427, 0xE174, - 0xD245, 0x4BD2, 0x78E3, 0x2DB0, 0x1E81, 0x0B2A, 0x381B, 0x6D48, 0x5E79, 0xC7EE, 0xF4DF, 0xA18C, 0x92BD, - 0x8283, 0xB1B2, 0xE4E1, 0xD7D0, 0x4E47, 0x7D76, 0x2825, 0x1B14, 0x0859, 0x3B68, 0x6E3B, 0x5D0A, 0xC49D, - 0xF7AC, 0xA2FF, 0x91CE, 0x81F0, 0xB2C1, 0xE792, 0xD4A3, 0x4D34, 0x7E05, 0x2B56, 0x1867, 0x1B98, 0x28A9, - 0x7DFA, 0x4ECB, 0xD75C, 0xE46D, 0xB13E, 0x820F, 0x9231, 0xA100, 0xF453, 0xC762, 0x5EF5, 0x6DC4, 0x3897, - 0x0BA6, 0x18EB, 0x2BDA, 0x7E89, 0x4DB8, 0xD42F, 0xE71E, 0xB24D, 0x817C, 0x9142, 0xA273, 0xF720, 0xC411, - 0x5D86, 0x6EB7, 0x3BE4, 0x08D5, 0x1D7E, 0x2E4F, 0x7B1C, 0x482D, 0xD1BA, 0xE28B, 0xB7D8, 0x84E9, 0x94D7, - 0xA7E6, 0xF2B5, 0xC184, 0x5813, 0x6B22, 0x3E71, 0x0D40, 0x1E0D, 0x2D3C, 0x786F, 0x4B5E, 0xD2C9, 0xE1F8, - 0xB4AB, 0x879A, 0x97A4, 0xA495, 0xF1C6, 0xC2F7, 0x5B60, 0x6851, 0x3D02, 0x0E33, 0x1654, 0x2565, 0x7036, - 0x4307, 0xDA90, 0xE9A1, 0xBCF2, 0x8FC3, 0x9FFD, 0xACCC, 0xF99F, 0xCAAE, 0x5339, 0x6008, 0x355B, 0x066A, - 0x1527, 0x2616, 0x7345, 0x4074, 0xD9E3, 0xEAD2, 0xBF81, 0x8CB0, 0x9C8E, 0xAFBF, 0xFAEC, 0xC9DD, 0x504A, - 0x637B, 0x3628, 0x0519, 0x10B2, 0x2383, 0x76D0, 0x45E1, 0xDC76, 0xEF47, 0xBA14, 0x8925, 0x991B, 0xAA2A, - 0xFF79, 0xCC48, 0x55DF, 0x66EE, 0x33BD, 0x008C, 0x13C1, 0x20F0, 0x75A3, 0x4692, 0xDF05, 0xEC34, 0xB967, - 0x8A56, 0x9A68, 0xA959, 0xFC0A, 0xCF3B, 0x56AC, 0x659D, 0x30CE, 0x03FF - }, - new ushort[] - { - 0x0000, 0x3730, 0x6E60, 0x5950, 0xDCC0, 0xEBF0, 0xB2A0, 0x8590, 0xA9A1, 0x9E91, 0xC7C1, 0xF0F1, 0x7561, - 0x4251, 0x1B01, 0x2C31, 0x4363, 0x7453, 0x2D03, 0x1A33, 0x9FA3, 0xA893, 0xF1C3, 0xC6F3, 0xEAC2, 0xDDF2, - 0x84A2, 0xB392, 0x3602, 0x0132, 0x5862, 0x6F52, 0x86C6, 0xB1F6, 0xE8A6, 0xDF96, 0x5A06, 0x6D36, 0x3466, - 0x0356, 0x2F67, 0x1857, 0x4107, 0x7637, 0xF3A7, 0xC497, 0x9DC7, 0xAAF7, 0xC5A5, 0xF295, 0xABC5, 0x9CF5, - 0x1965, 0x2E55, 0x7705, 0x4035, 0x6C04, 0x5B34, 0x0264, 0x3554, 0xB0C4, 0x87F4, 0xDEA4, 0xE994, 0x1DAD, - 0x2A9D, 0x73CD, 0x44FD, 0xC16D, 0xF65D, 0xAF0D, 0x983D, 0xB40C, 0x833C, 0xDA6C, 0xED5C, 0x68CC, 0x5FFC, - 0x06AC, 0x319C, 0x5ECE, 0x69FE, 0x30AE, 0x079E, 0x820E, 0xB53E, 0xEC6E, 0xDB5E, 0xF76F, 0xC05F, 0x990F, - 0xAE3F, 0x2BAF, 0x1C9F, 0x45CF, 0x72FF, 0x9B6B, 0xAC5B, 0xF50B, 0xC23B, 0x47AB, 0x709B, 0x29CB, 0x1EFB, - 0x32CA, 0x05FA, 0x5CAA, 0x6B9A, 0xEE0A, 0xD93A, 0x806A, 0xB75A, 0xD808, 0xEF38, 0xB668, 0x8158, 0x04C8, - 0x33F8, 0x6AA8, 0x5D98, 0x71A9, 0x4699, 0x1FC9, 0x28F9, 0xAD69, 0x9A59, 0xC309, 0xF439, 0x3B5A, 0x0C6A, - 0x553A, 0x620A, 0xE79A, 0xD0AA, 0x89FA, 0xBECA, 0x92FB, 0xA5CB, 0xFC9B, 0xCBAB, 0x4E3B, 0x790B, 0x205B, - 0x176B, 0x7839, 0x4F09, 0x1659, 0x2169, 0xA4F9, 0x93C9, 0xCA99, 0xFDA9, 0xD198, 0xE6A8, 0xBFF8, 0x88C8, - 0x0D58, 0x3A68, 0x6338, 0x5408, 0xBD9C, 0x8AAC, 0xD3FC, 0xE4CC, 0x615C, 0x566C, 0x0F3C, 0x380C, 0x143D, - 0x230D, 0x7A5D, 0x4D6D, 0xC8FD, 0xFFCD, 0xA69D, 0x91AD, 0xFEFF, 0xC9CF, 0x909F, 0xA7AF, 0x223F, 0x150F, - 0x4C5F, 0x7B6F, 0x575E, 0x606E, 0x393E, 0x0E0E, 0x8B9E, 0xBCAE, 0xE5FE, 0xD2CE, 0x26F7, 0x11C7, 0x4897, - 0x7FA7, 0xFA37, 0xCD07, 0x9457, 0xA367, 0x8F56, 0xB866, 0xE136, 0xD606, 0x5396, 0x64A6, 0x3DF6, 0x0AC6, - 0x6594, 0x52A4, 0x0BF4, 0x3CC4, 0xB954, 0x8E64, 0xD734, 0xE004, 0xCC35, 0xFB05, 0xA255, 0x9565, 0x10F5, - 0x27C5, 0x7E95, 0x49A5, 0xA031, 0x9701, 0xCE51, 0xF961, 0x7CF1, 0x4BC1, 0x1291, 0x25A1, 0x0990, 0x3EA0, - 0x67F0, 0x50C0, 0xD550, 0xE260, 0xBB30, 0x8C00, 0xE352, 0xD462, 0x8D32, 0xBA02, 0x3F92, 0x08A2, 0x51F2, - 0x66C2, 0x4AF3, 0x7DC3, 0x2493, 0x13A3, 0x9633, 0xA103, 0xF853, 0xCF63 - }, - new ushort[] - { - 0x0000, 0x76B4, 0xED68, 0x9BDC, 0xCAF1, 0xBC45, 0x2799, 0x512D, 0x85C3, 0xF377, 0x68AB, 0x1E1F, 0x4F32, - 0x3986, 0xA25A, 0xD4EE, 0x1BA7, 0x6D13, 0xF6CF, 0x807B, 0xD156, 0xA7E2, 0x3C3E, 0x4A8A, 0x9E64, 0xE8D0, - 0x730C, 0x05B8, 0x5495, 0x2221, 0xB9FD, 0xCF49, 0x374E, 0x41FA, 0xDA26, 0xAC92, 0xFDBF, 0x8B0B, 0x10D7, - 0x6663, 0xB28D, 0xC439, 0x5FE5, 0x2951, 0x787C, 0x0EC8, 0x9514, 0xE3A0, 0x2CE9, 0x5A5D, 0xC181, 0xB735, - 0xE618, 0x90AC, 0x0B70, 0x7DC4, 0xA92A, 0xDF9E, 0x4442, 0x32F6, 0x63DB, 0x156F, 0x8EB3, 0xF807, 0x6E9C, - 0x1828, 0x83F4, 0xF540, 0xA46D, 0xD2D9, 0x4905, 0x3FB1, 0xEB5F, 0x9DEB, 0x0637, 0x7083, 0x21AE, 0x571A, - 0xCCC6, 0xBA72, 0x753B, 0x038F, 0x9853, 0xEEE7, 0xBFCA, 0xC97E, 0x52A2, 0x2416, 0xF0F8, 0x864C, 0x1D90, - 0x6B24, 0x3A09, 0x4CBD, 0xD761, 0xA1D5, 0x59D2, 0x2F66, 0xB4BA, 0xC20E, 0x9323, 0xE597, 0x7E4B, 0x08FF, - 0xDC11, 0xAAA5, 0x3179, 0x47CD, 0x16E0, 0x6054, 0xFB88, 0x8D3C, 0x4275, 0x34C1, 0xAF1D, 0xD9A9, 0x8884, - 0xFE30, 0x65EC, 0x1358, 0xC7B6, 0xB102, 0x2ADE, 0x5C6A, 0x0D47, 0x7BF3, 0xE02F, 0x969B, 0xDD38, 0xAB8C, - 0x3050, 0x46E4, 0x17C9, 0x617D, 0xFAA1, 0x8C15, 0x58FB, 0x2E4F, 0xB593, 0xC327, 0x920A, 0xE4BE, 0x7F62, - 0x09D6, 0xC69F, 0xB02B, 0x2BF7, 0x5D43, 0x0C6E, 0x7ADA, 0xE106, 0x97B2, 0x435C, 0x35E8, 0xAE34, 0xD880, - 0x89AD, 0xFF19, 0x64C5, 0x1271, 0xEA76, 0x9CC2, 0x071E, 0x71AA, 0x2087, 0x5633, 0xCDEF, 0xBB5B, 0x6FB5, - 0x1901, 0x82DD, 0xF469, 0xA544, 0xD3F0, 0x482C, 0x3E98, 0xF1D1, 0x8765, 0x1CB9, 0x6A0D, 0x3B20, 0x4D94, - 0xD648, 0xA0FC, 0x7412, 0x02A6, 0x997A, 0xEFCE, 0xBEE3, 0xC857, 0x538B, 0x253F, 0xB3A4, 0xC510, 0x5ECC, - 0x2878, 0x7955, 0x0FE1, 0x943D, 0xE289, 0x3667, 0x40D3, 0xDB0F, 0xADBB, 0xFC96, 0x8A22, 0x11FE, 0x674A, - 0xA803, 0xDEB7, 0x456B, 0x33DF, 0x62F2, 0x1446, 0x8F9A, 0xF92E, 0x2DC0, 0x5B74, 0xC0A8, 0xB61C, 0xE731, - 0x9185, 0x0A59, 0x7CED, 0x84EA, 0xF25E, 0x6982, 0x1F36, 0x4E1B, 0x38AF, 0xA373, 0xD5C7, 0x0129, 0x779D, - 0xEC41, 0x9AF5, 0xCBD8, 0xBD6C, 0x26B0, 0x5004, 0x9F4D, 0xE9F9, 0x7225, 0x0491, 0x55BC, 0x2308, 0xB8D4, - 0xCE60, 0x1A8E, 0x6C3A, 0xF7E6, 0x8152, 0xD07F, 0xA6CB, 0x3D17, 0x4BA3 - }, - new ushort[] - { - 0x0000, 0xAA51, 0x4483, 0xEED2, 0x8906, 0x2357, 0xCD85, 0x67D4, 0x022D, 0xA87C, 0x46AE, 0xECFF, 0x8B2B, - 0x217A, 0xCFA8, 0x65F9, 0x045A, 0xAE0B, 0x40D9, 0xEA88, 0x8D5C, 0x270D, 0xC9DF, 0x638E, 0x0677, 0xAC26, - 0x42F4, 0xE8A5, 0x8F71, 0x2520, 0xCBF2, 0x61A3, 0x08B4, 0xA2E5, 0x4C37, 0xE666, 0x81B2, 0x2BE3, 0xC531, - 0x6F60, 0x0A99, 0xA0C8, 0x4E1A, 0xE44B, 0x839F, 0x29CE, 0xC71C, 0x6D4D, 0x0CEE, 0xA6BF, 0x486D, 0xE23C, - 0x85E8, 0x2FB9, 0xC16B, 0x6B3A, 0x0EC3, 0xA492, 0x4A40, 0xE011, 0x87C5, 0x2D94, 0xC346, 0x6917, 0x1168, - 0xBB39, 0x55EB, 0xFFBA, 0x986E, 0x323F, 0xDCED, 0x76BC, 0x1345, 0xB914, 0x57C6, 0xFD97, 0x9A43, 0x3012, - 0xDEC0, 0x7491, 0x1532, 0xBF63, 0x51B1, 0xFBE0, 0x9C34, 0x3665, 0xD8B7, 0x72E6, 0x171F, 0xBD4E, 0x539C, - 0xF9CD, 0x9E19, 0x3448, 0xDA9A, 0x70CB, 0x19DC, 0xB38D, 0x5D5F, 0xF70E, 0x90DA, 0x3A8B, 0xD459, 0x7E08, - 0x1BF1, 0xB1A0, 0x5F72, 0xF523, 0x92F7, 0x38A6, 0xD674, 0x7C25, 0x1D86, 0xB7D7, 0x5905, 0xF354, 0x9480, - 0x3ED1, 0xD003, 0x7A52, 0x1FAB, 0xB5FA, 0x5B28, 0xF179, 0x96AD, 0x3CFC, 0xD22E, 0x787F, 0x22D0, 0x8881, - 0x6653, 0xCC02, 0xABD6, 0x0187, 0xEF55, 0x4504, 0x20FD, 0x8AAC, 0x647E, 0xCE2F, 0xA9FB, 0x03AA, 0xED78, - 0x4729, 0x268A, 0x8CDB, 0x6209, 0xC858, 0xAF8C, 0x05DD, 0xEB0F, 0x415E, 0x24A7, 0x8EF6, 0x6024, 0xCA75, - 0xADA1, 0x07F0, 0xE922, 0x4373, 0x2A64, 0x8035, 0x6EE7, 0xC4B6, 0xA362, 0x0933, 0xE7E1, 0x4DB0, 0x2849, - 0x8218, 0x6CCA, 0xC69B, 0xA14F, 0x0B1E, 0xE5CC, 0x4F9D, 0x2E3E, 0x846F, 0x6ABD, 0xC0EC, 0xA738, 0x0D69, - 0xE3BB, 0x49EA, 0x2C13, 0x8642, 0x6890, 0xC2C1, 0xA515, 0x0F44, 0xE196, 0x4BC7, 0x33B8, 0x99E9, 0x773B, - 0xDD6A, 0xBABE, 0x10EF, 0xFE3D, 0x546C, 0x3195, 0x9BC4, 0x7516, 0xDF47, 0xB893, 0x12C2, 0xFC10, 0x5641, - 0x37E2, 0x9DB3, 0x7361, 0xD930, 0xBEE4, 0x14B5, 0xFA67, 0x5036, 0x35CF, 0x9F9E, 0x714C, 0xDB1D, 0xBCC9, - 0x1698, 0xF84A, 0x521B, 0x3B0C, 0x915D, 0x7F8F, 0xD5DE, 0xB20A, 0x185B, 0xF689, 0x5CD8, 0x3921, 0x9370, - 0x7DA2, 0xD7F3, 0xB027, 0x1A76, 0xF4A4, 0x5EF5, 0x3F56, 0x9507, 0x7BD5, 0xD184, 0xB650, 0x1C01, 0xF2D3, - 0x5882, 0x3D7B, 0x972A, 0x79F8, 0xD3A9, 0xB47D, 0x1E2C, 0xF0FE, 0x5AAF - }, - new ushort[] - { - 0x0000, 0x45A0, 0x8B40, 0xCEE0, 0x06A1, 0x4301, 0x8DE1, 0xC841, 0x0D42, 0x48E2, 0x8602, 0xC3A2, 0x0BE3, - 0x4E43, 0x80A3, 0xC503, 0x1A84, 0x5F24, 0x91C4, 0xD464, 0x1C25, 0x5985, 0x9765, 0xD2C5, 0x17C6, 0x5266, - 0x9C86, 0xD926, 0x1167, 0x54C7, 0x9A27, 0xDF87, 0x3508, 0x70A8, 0xBE48, 0xFBE8, 0x33A9, 0x7609, 0xB8E9, - 0xFD49, 0x384A, 0x7DEA, 0xB30A, 0xF6AA, 0x3EEB, 0x7B4B, 0xB5AB, 0xF00B, 0x2F8C, 0x6A2C, 0xA4CC, 0xE16C, - 0x292D, 0x6C8D, 0xA26D, 0xE7CD, 0x22CE, 0x676E, 0xA98E, 0xEC2E, 0x246F, 0x61CF, 0xAF2F, 0xEA8F, 0x6A10, - 0x2FB0, 0xE150, 0xA4F0, 0x6CB1, 0x2911, 0xE7F1, 0xA251, 0x6752, 0x22F2, 0xEC12, 0xA9B2, 0x61F3, 0x2453, - 0xEAB3, 0xAF13, 0x7094, 0x3534, 0xFBD4, 0xBE74, 0x7635, 0x3395, 0xFD75, 0xB8D5, 0x7DD6, 0x3876, 0xF696, - 0xB336, 0x7B77, 0x3ED7, 0xF037, 0xB597, 0x5F18, 0x1AB8, 0xD458, 0x91F8, 0x59B9, 0x1C19, 0xD2F9, 0x9759, - 0x525A, 0x17FA, 0xD91A, 0x9CBA, 0x54FB, 0x115B, 0xDFBB, 0x9A1B, 0x459C, 0x003C, 0xCEDC, 0x8B7C, 0x433D, - 0x069D, 0xC87D, 0x8DDD, 0x48DE, 0x0D7E, 0xC39E, 0x863E, 0x4E7F, 0x0BDF, 0xC53F, 0x809F, 0xD420, 0x9180, - 0x5F60, 0x1AC0, 0xD281, 0x9721, 0x59C1, 0x1C61, 0xD962, 0x9CC2, 0x5222, 0x1782, 0xDFC3, 0x9A63, 0x5483, - 0x1123, 0xCEA4, 0x8B04, 0x45E4, 0x0044, 0xC805, 0x8DA5, 0x4345, 0x06E5, 0xC3E6, 0x8646, 0x48A6, 0x0D06, - 0xC547, 0x80E7, 0x4E07, 0x0BA7, 0xE128, 0xA488, 0x6A68, 0x2FC8, 0xE789, 0xA229, 0x6CC9, 0x2969, 0xEC6A, - 0xA9CA, 0x672A, 0x228A, 0xEACB, 0xAF6B, 0x618B, 0x242B, 0xFBAC, 0xBE0C, 0x70EC, 0x354C, 0xFD0D, 0xB8AD, - 0x764D, 0x33ED, 0xF6EE, 0xB34E, 0x7DAE, 0x380E, 0xF04F, 0xB5EF, 0x7B0F, 0x3EAF, 0xBE30, 0xFB90, 0x3570, - 0x70D0, 0xB891, 0xFD31, 0x33D1, 0x7671, 0xB372, 0xF6D2, 0x3832, 0x7D92, 0xB5D3, 0xF073, 0x3E93, 0x7B33, - 0xA4B4, 0xE114, 0x2FF4, 0x6A54, 0xA215, 0xE7B5, 0x2955, 0x6CF5, 0xA9F6, 0xEC56, 0x22B6, 0x6716, 0xAF57, - 0xEAF7, 0x2417, 0x61B7, 0x8B38, 0xCE98, 0x0078, 0x45D8, 0x8D99, 0xC839, 0x06D9, 0x4379, 0x867A, 0xC3DA, - 0x0D3A, 0x489A, 0x80DB, 0xC57B, 0x0B9B, 0x4E3B, 0x91BC, 0xD41C, 0x1AFC, 0x5F5C, 0x971D, 0xD2BD, 0x1C5D, - 0x59FD, 0x9CFE, 0xD95E, 0x17BE, 0x521E, 0x9A5F, 0xDFFF, 0x111F, 0x54BF - }, - new ushort[] - { - 0x0000, 0xB861, 0x60E3, 0xD882, 0xC1C6, 0x79A7, 0xA125, 0x1944, 0x93AD, 0x2BCC, 0xF34E, 0x4B2F, 0x526B, - 0xEA0A, 0x3288, 0x8AE9, 0x377B, 0x8F1A, 0x5798, 0xEFF9, 0xF6BD, 0x4EDC, 0x965E, 0x2E3F, 0xA4D6, 0x1CB7, - 0xC435, 0x7C54, 0x6510, 0xDD71, 0x05F3, 0xBD92, 0x6EF6, 0xD697, 0x0E15, 0xB674, 0xAF30, 0x1751, 0xCFD3, - 0x77B2, 0xFD5B, 0x453A, 0x9DB8, 0x25D9, 0x3C9D, 0x84FC, 0x5C7E, 0xE41F, 0x598D, 0xE1EC, 0x396E, 0x810F, - 0x984B, 0x202A, 0xF8A8, 0x40C9, 0xCA20, 0x7241, 0xAAC3, 0x12A2, 0x0BE6, 0xB387, 0x6B05, 0xD364, 0xDDEC, - 0x658D, 0xBD0F, 0x056E, 0x1C2A, 0xA44B, 0x7CC9, 0xC4A8, 0x4E41, 0xF620, 0x2EA2, 0x96C3, 0x8F87, 0x37E6, - 0xEF64, 0x5705, 0xEA97, 0x52F6, 0x8A74, 0x3215, 0x2B51, 0x9330, 0x4BB2, 0xF3D3, 0x793A, 0xC15B, 0x19D9, - 0xA1B8, 0xB8FC, 0x009D, 0xD81F, 0x607E, 0xB31A, 0x0B7B, 0xD3F9, 0x6B98, 0x72DC, 0xCABD, 0x123F, 0xAA5E, - 0x20B7, 0x98D6, 0x4054, 0xF835, 0xE171, 0x5910, 0x8192, 0x39F3, 0x8461, 0x3C00, 0xE482, 0x5CE3, 0x45A7, - 0xFDC6, 0x2544, 0x9D25, 0x17CC, 0xAFAD, 0x772F, 0xCF4E, 0xD60A, 0x6E6B, 0xB6E9, 0x0E88, 0xABF9, 0x1398, - 0xCB1A, 0x737B, 0x6A3F, 0xD25E, 0x0ADC, 0xB2BD, 0x3854, 0x8035, 0x58B7, 0xE0D6, 0xF992, 0x41F3, 0x9971, - 0x2110, 0x9C82, 0x24E3, 0xFC61, 0x4400, 0x5D44, 0xE525, 0x3DA7, 0x85C6, 0x0F2F, 0xB74E, 0x6FCC, 0xD7AD, - 0xCEE9, 0x7688, 0xAE0A, 0x166B, 0xC50F, 0x7D6E, 0xA5EC, 0x1D8D, 0x04C9, 0xBCA8, 0x642A, 0xDC4B, 0x56A2, - 0xEEC3, 0x3641, 0x8E20, 0x9764, 0x2F05, 0xF787, 0x4FE6, 0xF274, 0x4A15, 0x9297, 0x2AF6, 0x33B2, 0x8BD3, - 0x5351, 0xEB30, 0x61D9, 0xD9B8, 0x013A, 0xB95B, 0xA01F, 0x187E, 0xC0FC, 0x789D, 0x7615, 0xCE74, 0x16F6, - 0xAE97, 0xB7D3, 0x0FB2, 0xD730, 0x6F51, 0xE5B8, 0x5DD9, 0x855B, 0x3D3A, 0x247E, 0x9C1F, 0x449D, 0xFCFC, - 0x416E, 0xF90F, 0x218D, 0x99EC, 0x80A8, 0x38C9, 0xE04B, 0x582A, 0xD2C3, 0x6AA2, 0xB220, 0x0A41, 0x1305, - 0xAB64, 0x73E6, 0xCB87, 0x18E3, 0xA082, 0x7800, 0xC061, 0xD925, 0x6144, 0xB9C6, 0x01A7, 0x8B4E, 0x332F, - 0xEBAD, 0x53CC, 0x4A88, 0xF2E9, 0x2A6B, 0x920A, 0x2F98, 0x97F9, 0x4F7B, 0xF71A, 0xEE5E, 0x563F, 0x8EBD, - 0x36DC, 0xBC35, 0x0454, 0xDCD6, 0x64B7, 0x7DF3, 0xC592, 0x1D10, 0xA571 - }, - new ushort[] - { - 0x0000, 0x47D3, 0x8FA6, 0xC875, 0x0F6D, 0x48BE, 0x80CB, 0xC718, 0x1EDA, 0x5909, 0x917C, 0xD6AF, 0x11B7, - 0x5664, 0x9E11, 0xD9C2, 0x3DB4, 0x7A67, 0xB212, 0xF5C1, 0x32D9, 0x750A, 0xBD7F, 0xFAAC, 0x236E, 0x64BD, - 0xACC8, 0xEB1B, 0x2C03, 0x6BD0, 0xA3A5, 0xE476, 0x7B68, 0x3CBB, 0xF4CE, 0xB31D, 0x7405, 0x33D6, 0xFBA3, - 0xBC70, 0x65B2, 0x2261, 0xEA14, 0xADC7, 0x6ADF, 0x2D0C, 0xE579, 0xA2AA, 0x46DC, 0x010F, 0xC97A, 0x8EA9, - 0x49B1, 0x0E62, 0xC617, 0x81C4, 0x5806, 0x1FD5, 0xD7A0, 0x9073, 0x576B, 0x10B8, 0xD8CD, 0x9F1E, 0xF6D0, - 0xB103, 0x7976, 0x3EA5, 0xF9BD, 0xBE6E, 0x761B, 0x31C8, 0xE80A, 0xAFD9, 0x67AC, 0x207F, 0xE767, 0xA0B4, - 0x68C1, 0x2F12, 0xCB64, 0x8CB7, 0x44C2, 0x0311, 0xC409, 0x83DA, 0x4BAF, 0x0C7C, 0xD5BE, 0x926D, 0x5A18, - 0x1DCB, 0xDAD3, 0x9D00, 0x5575, 0x12A6, 0x8DB8, 0xCA6B, 0x021E, 0x45CD, 0x82D5, 0xC506, 0x0D73, 0x4AA0, - 0x9362, 0xD4B1, 0x1CC4, 0x5B17, 0x9C0F, 0xDBDC, 0x13A9, 0x547A, 0xB00C, 0xF7DF, 0x3FAA, 0x7879, 0xBF61, - 0xF8B2, 0x30C7, 0x7714, 0xAED6, 0xE905, 0x2170, 0x66A3, 0xA1BB, 0xE668, 0x2E1D, 0x69CE, 0xFD81, 0xBA52, - 0x7227, 0x35F4, 0xF2EC, 0xB53F, 0x7D4A, 0x3A99, 0xE35B, 0xA488, 0x6CFD, 0x2B2E, 0xEC36, 0xABE5, 0x6390, - 0x2443, 0xC035, 0x87E6, 0x4F93, 0x0840, 0xCF58, 0x888B, 0x40FE, 0x072D, 0xDEEF, 0x993C, 0x5149, 0x169A, - 0xD182, 0x9651, 0x5E24, 0x19F7, 0x86E9, 0xC13A, 0x094F, 0x4E9C, 0x8984, 0xCE57, 0x0622, 0x41F1, 0x9833, - 0xDFE0, 0x1795, 0x5046, 0x975E, 0xD08D, 0x18F8, 0x5F2B, 0xBB5D, 0xFC8E, 0x34FB, 0x7328, 0xB430, 0xF3E3, - 0x3B96, 0x7C45, 0xA587, 0xE254, 0x2A21, 0x6DF2, 0xAAEA, 0xED39, 0x254C, 0x629F, 0x0B51, 0x4C82, 0x84F7, - 0xC324, 0x043C, 0x43EF, 0x8B9A, 0xCC49, 0x158B, 0x5258, 0x9A2D, 0xDDFE, 0x1AE6, 0x5D35, 0x9540, 0xD293, - 0x36E5, 0x7136, 0xB943, 0xFE90, 0x3988, 0x7E5B, 0xB62E, 0xF1FD, 0x283F, 0x6FEC, 0xA799, 0xE04A, 0x2752, - 0x6081, 0xA8F4, 0xEF27, 0x7039, 0x37EA, 0xFF9F, 0xB84C, 0x7F54, 0x3887, 0xF0F2, 0xB721, 0x6EE3, 0x2930, - 0xE145, 0xA696, 0x618E, 0x265D, 0xEE28, 0xA9FB, 0x4D8D, 0x0A5E, 0xC22B, 0x85F8, 0x42E0, 0x0533, 0xCD46, - 0x8A95, 0x5357, 0x1484, 0xDCF1, 0x9B22, 0x5C3A, 0x1BE9, 0xD39C, 0x944F - } - }; - - /// Initializes an instance of the CRC16 with CCITT polynomial and seed. - /// - public CRC16CCITTContext() : base(CRC16_CCITT_POLY, CRC16_CCITT_SEED, _ccittCrc16Table, true) {} - - /// Gets the hash of a file - /// File path. - public static byte[] File(string filename) + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, + 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, + 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, + 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, + 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, + 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, + 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, + 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, + 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, + 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, + 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, + 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, + 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, + 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, + 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, + 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, + 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, + 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 + }, + new ushort[] { - File(filename, out byte[] hash); - - return hash; + 0x0000, 0x3331, 0x6662, 0x5553, 0xCCC4, 0xFFF5, 0xAAA6, 0x9997, 0x89A9, 0xBA98, 0xEFCB, 0xDCFA, 0x456D, + 0x765C, 0x230F, 0x103E, 0x0373, 0x3042, 0x6511, 0x5620, 0xCFB7, 0xFC86, 0xA9D5, 0x9AE4, 0x8ADA, 0xB9EB, + 0xECB8, 0xDF89, 0x461E, 0x752F, 0x207C, 0x134D, 0x06E6, 0x35D7, 0x6084, 0x53B5, 0xCA22, 0xF913, 0xAC40, + 0x9F71, 0x8F4F, 0xBC7E, 0xE92D, 0xDA1C, 0x438B, 0x70BA, 0x25E9, 0x16D8, 0x0595, 0x36A4, 0x63F7, 0x50C6, + 0xC951, 0xFA60, 0xAF33, 0x9C02, 0x8C3C, 0xBF0D, 0xEA5E, 0xD96F, 0x40F8, 0x73C9, 0x269A, 0x15AB, 0x0DCC, + 0x3EFD, 0x6BAE, 0x589F, 0xC108, 0xF239, 0xA76A, 0x945B, 0x8465, 0xB754, 0xE207, 0xD136, 0x48A1, 0x7B90, + 0x2EC3, 0x1DF2, 0x0EBF, 0x3D8E, 0x68DD, 0x5BEC, 0xC27B, 0xF14A, 0xA419, 0x9728, 0x8716, 0xB427, 0xE174, + 0xD245, 0x4BD2, 0x78E3, 0x2DB0, 0x1E81, 0x0B2A, 0x381B, 0x6D48, 0x5E79, 0xC7EE, 0xF4DF, 0xA18C, 0x92BD, + 0x8283, 0xB1B2, 0xE4E1, 0xD7D0, 0x4E47, 0x7D76, 0x2825, 0x1B14, 0x0859, 0x3B68, 0x6E3B, 0x5D0A, 0xC49D, + 0xF7AC, 0xA2FF, 0x91CE, 0x81F0, 0xB2C1, 0xE792, 0xD4A3, 0x4D34, 0x7E05, 0x2B56, 0x1867, 0x1B98, 0x28A9, + 0x7DFA, 0x4ECB, 0xD75C, 0xE46D, 0xB13E, 0x820F, 0x9231, 0xA100, 0xF453, 0xC762, 0x5EF5, 0x6DC4, 0x3897, + 0x0BA6, 0x18EB, 0x2BDA, 0x7E89, 0x4DB8, 0xD42F, 0xE71E, 0xB24D, 0x817C, 0x9142, 0xA273, 0xF720, 0xC411, + 0x5D86, 0x6EB7, 0x3BE4, 0x08D5, 0x1D7E, 0x2E4F, 0x7B1C, 0x482D, 0xD1BA, 0xE28B, 0xB7D8, 0x84E9, 0x94D7, + 0xA7E6, 0xF2B5, 0xC184, 0x5813, 0x6B22, 0x3E71, 0x0D40, 0x1E0D, 0x2D3C, 0x786F, 0x4B5E, 0xD2C9, 0xE1F8, + 0xB4AB, 0x879A, 0x97A4, 0xA495, 0xF1C6, 0xC2F7, 0x5B60, 0x6851, 0x3D02, 0x0E33, 0x1654, 0x2565, 0x7036, + 0x4307, 0xDA90, 0xE9A1, 0xBCF2, 0x8FC3, 0x9FFD, 0xACCC, 0xF99F, 0xCAAE, 0x5339, 0x6008, 0x355B, 0x066A, + 0x1527, 0x2616, 0x7345, 0x4074, 0xD9E3, 0xEAD2, 0xBF81, 0x8CB0, 0x9C8E, 0xAFBF, 0xFAEC, 0xC9DD, 0x504A, + 0x637B, 0x3628, 0x0519, 0x10B2, 0x2383, 0x76D0, 0x45E1, 0xDC76, 0xEF47, 0xBA14, 0x8925, 0x991B, 0xAA2A, + 0xFF79, 0xCC48, 0x55DF, 0x66EE, 0x33BD, 0x008C, 0x13C1, 0x20F0, 0x75A3, 0x4692, 0xDF05, 0xEC34, 0xB967, + 0x8A56, 0x9A68, 0xA959, 0xFC0A, 0xCF3B, 0x56AC, 0x659D, 0x30CE, 0x03FF + }, + new ushort[] + { + 0x0000, 0x3730, 0x6E60, 0x5950, 0xDCC0, 0xEBF0, 0xB2A0, 0x8590, 0xA9A1, 0x9E91, 0xC7C1, 0xF0F1, 0x7561, + 0x4251, 0x1B01, 0x2C31, 0x4363, 0x7453, 0x2D03, 0x1A33, 0x9FA3, 0xA893, 0xF1C3, 0xC6F3, 0xEAC2, 0xDDF2, + 0x84A2, 0xB392, 0x3602, 0x0132, 0x5862, 0x6F52, 0x86C6, 0xB1F6, 0xE8A6, 0xDF96, 0x5A06, 0x6D36, 0x3466, + 0x0356, 0x2F67, 0x1857, 0x4107, 0x7637, 0xF3A7, 0xC497, 0x9DC7, 0xAAF7, 0xC5A5, 0xF295, 0xABC5, 0x9CF5, + 0x1965, 0x2E55, 0x7705, 0x4035, 0x6C04, 0x5B34, 0x0264, 0x3554, 0xB0C4, 0x87F4, 0xDEA4, 0xE994, 0x1DAD, + 0x2A9D, 0x73CD, 0x44FD, 0xC16D, 0xF65D, 0xAF0D, 0x983D, 0xB40C, 0x833C, 0xDA6C, 0xED5C, 0x68CC, 0x5FFC, + 0x06AC, 0x319C, 0x5ECE, 0x69FE, 0x30AE, 0x079E, 0x820E, 0xB53E, 0xEC6E, 0xDB5E, 0xF76F, 0xC05F, 0x990F, + 0xAE3F, 0x2BAF, 0x1C9F, 0x45CF, 0x72FF, 0x9B6B, 0xAC5B, 0xF50B, 0xC23B, 0x47AB, 0x709B, 0x29CB, 0x1EFB, + 0x32CA, 0x05FA, 0x5CAA, 0x6B9A, 0xEE0A, 0xD93A, 0x806A, 0xB75A, 0xD808, 0xEF38, 0xB668, 0x8158, 0x04C8, + 0x33F8, 0x6AA8, 0x5D98, 0x71A9, 0x4699, 0x1FC9, 0x28F9, 0xAD69, 0x9A59, 0xC309, 0xF439, 0x3B5A, 0x0C6A, + 0x553A, 0x620A, 0xE79A, 0xD0AA, 0x89FA, 0xBECA, 0x92FB, 0xA5CB, 0xFC9B, 0xCBAB, 0x4E3B, 0x790B, 0x205B, + 0x176B, 0x7839, 0x4F09, 0x1659, 0x2169, 0xA4F9, 0x93C9, 0xCA99, 0xFDA9, 0xD198, 0xE6A8, 0xBFF8, 0x88C8, + 0x0D58, 0x3A68, 0x6338, 0x5408, 0xBD9C, 0x8AAC, 0xD3FC, 0xE4CC, 0x615C, 0x566C, 0x0F3C, 0x380C, 0x143D, + 0x230D, 0x7A5D, 0x4D6D, 0xC8FD, 0xFFCD, 0xA69D, 0x91AD, 0xFEFF, 0xC9CF, 0x909F, 0xA7AF, 0x223F, 0x150F, + 0x4C5F, 0x7B6F, 0x575E, 0x606E, 0x393E, 0x0E0E, 0x8B9E, 0xBCAE, 0xE5FE, 0xD2CE, 0x26F7, 0x11C7, 0x4897, + 0x7FA7, 0xFA37, 0xCD07, 0x9457, 0xA367, 0x8F56, 0xB866, 0xE136, 0xD606, 0x5396, 0x64A6, 0x3DF6, 0x0AC6, + 0x6594, 0x52A4, 0x0BF4, 0x3CC4, 0xB954, 0x8E64, 0xD734, 0xE004, 0xCC35, 0xFB05, 0xA255, 0x9565, 0x10F5, + 0x27C5, 0x7E95, 0x49A5, 0xA031, 0x9701, 0xCE51, 0xF961, 0x7CF1, 0x4BC1, 0x1291, 0x25A1, 0x0990, 0x3EA0, + 0x67F0, 0x50C0, 0xD550, 0xE260, 0xBB30, 0x8C00, 0xE352, 0xD462, 0x8D32, 0xBA02, 0x3F92, 0x08A2, 0x51F2, + 0x66C2, 0x4AF3, 0x7DC3, 0x2493, 0x13A3, 0x9633, 0xA103, 0xF853, 0xCF63 + }, + new ushort[] + { + 0x0000, 0x76B4, 0xED68, 0x9BDC, 0xCAF1, 0xBC45, 0x2799, 0x512D, 0x85C3, 0xF377, 0x68AB, 0x1E1F, 0x4F32, + 0x3986, 0xA25A, 0xD4EE, 0x1BA7, 0x6D13, 0xF6CF, 0x807B, 0xD156, 0xA7E2, 0x3C3E, 0x4A8A, 0x9E64, 0xE8D0, + 0x730C, 0x05B8, 0x5495, 0x2221, 0xB9FD, 0xCF49, 0x374E, 0x41FA, 0xDA26, 0xAC92, 0xFDBF, 0x8B0B, 0x10D7, + 0x6663, 0xB28D, 0xC439, 0x5FE5, 0x2951, 0x787C, 0x0EC8, 0x9514, 0xE3A0, 0x2CE9, 0x5A5D, 0xC181, 0xB735, + 0xE618, 0x90AC, 0x0B70, 0x7DC4, 0xA92A, 0xDF9E, 0x4442, 0x32F6, 0x63DB, 0x156F, 0x8EB3, 0xF807, 0x6E9C, + 0x1828, 0x83F4, 0xF540, 0xA46D, 0xD2D9, 0x4905, 0x3FB1, 0xEB5F, 0x9DEB, 0x0637, 0x7083, 0x21AE, 0x571A, + 0xCCC6, 0xBA72, 0x753B, 0x038F, 0x9853, 0xEEE7, 0xBFCA, 0xC97E, 0x52A2, 0x2416, 0xF0F8, 0x864C, 0x1D90, + 0x6B24, 0x3A09, 0x4CBD, 0xD761, 0xA1D5, 0x59D2, 0x2F66, 0xB4BA, 0xC20E, 0x9323, 0xE597, 0x7E4B, 0x08FF, + 0xDC11, 0xAAA5, 0x3179, 0x47CD, 0x16E0, 0x6054, 0xFB88, 0x8D3C, 0x4275, 0x34C1, 0xAF1D, 0xD9A9, 0x8884, + 0xFE30, 0x65EC, 0x1358, 0xC7B6, 0xB102, 0x2ADE, 0x5C6A, 0x0D47, 0x7BF3, 0xE02F, 0x969B, 0xDD38, 0xAB8C, + 0x3050, 0x46E4, 0x17C9, 0x617D, 0xFAA1, 0x8C15, 0x58FB, 0x2E4F, 0xB593, 0xC327, 0x920A, 0xE4BE, 0x7F62, + 0x09D6, 0xC69F, 0xB02B, 0x2BF7, 0x5D43, 0x0C6E, 0x7ADA, 0xE106, 0x97B2, 0x435C, 0x35E8, 0xAE34, 0xD880, + 0x89AD, 0xFF19, 0x64C5, 0x1271, 0xEA76, 0x9CC2, 0x071E, 0x71AA, 0x2087, 0x5633, 0xCDEF, 0xBB5B, 0x6FB5, + 0x1901, 0x82DD, 0xF469, 0xA544, 0xD3F0, 0x482C, 0x3E98, 0xF1D1, 0x8765, 0x1CB9, 0x6A0D, 0x3B20, 0x4D94, + 0xD648, 0xA0FC, 0x7412, 0x02A6, 0x997A, 0xEFCE, 0xBEE3, 0xC857, 0x538B, 0x253F, 0xB3A4, 0xC510, 0x5ECC, + 0x2878, 0x7955, 0x0FE1, 0x943D, 0xE289, 0x3667, 0x40D3, 0xDB0F, 0xADBB, 0xFC96, 0x8A22, 0x11FE, 0x674A, + 0xA803, 0xDEB7, 0x456B, 0x33DF, 0x62F2, 0x1446, 0x8F9A, 0xF92E, 0x2DC0, 0x5B74, 0xC0A8, 0xB61C, 0xE731, + 0x9185, 0x0A59, 0x7CED, 0x84EA, 0xF25E, 0x6982, 0x1F36, 0x4E1B, 0x38AF, 0xA373, 0xD5C7, 0x0129, 0x779D, + 0xEC41, 0x9AF5, 0xCBD8, 0xBD6C, 0x26B0, 0x5004, 0x9F4D, 0xE9F9, 0x7225, 0x0491, 0x55BC, 0x2308, 0xB8D4, + 0xCE60, 0x1A8E, 0x6C3A, 0xF7E6, 0x8152, 0xD07F, 0xA6CB, 0x3D17, 0x4BA3 + }, + new ushort[] + { + 0x0000, 0xAA51, 0x4483, 0xEED2, 0x8906, 0x2357, 0xCD85, 0x67D4, 0x022D, 0xA87C, 0x46AE, 0xECFF, 0x8B2B, + 0x217A, 0xCFA8, 0x65F9, 0x045A, 0xAE0B, 0x40D9, 0xEA88, 0x8D5C, 0x270D, 0xC9DF, 0x638E, 0x0677, 0xAC26, + 0x42F4, 0xE8A5, 0x8F71, 0x2520, 0xCBF2, 0x61A3, 0x08B4, 0xA2E5, 0x4C37, 0xE666, 0x81B2, 0x2BE3, 0xC531, + 0x6F60, 0x0A99, 0xA0C8, 0x4E1A, 0xE44B, 0x839F, 0x29CE, 0xC71C, 0x6D4D, 0x0CEE, 0xA6BF, 0x486D, 0xE23C, + 0x85E8, 0x2FB9, 0xC16B, 0x6B3A, 0x0EC3, 0xA492, 0x4A40, 0xE011, 0x87C5, 0x2D94, 0xC346, 0x6917, 0x1168, + 0xBB39, 0x55EB, 0xFFBA, 0x986E, 0x323F, 0xDCED, 0x76BC, 0x1345, 0xB914, 0x57C6, 0xFD97, 0x9A43, 0x3012, + 0xDEC0, 0x7491, 0x1532, 0xBF63, 0x51B1, 0xFBE0, 0x9C34, 0x3665, 0xD8B7, 0x72E6, 0x171F, 0xBD4E, 0x539C, + 0xF9CD, 0x9E19, 0x3448, 0xDA9A, 0x70CB, 0x19DC, 0xB38D, 0x5D5F, 0xF70E, 0x90DA, 0x3A8B, 0xD459, 0x7E08, + 0x1BF1, 0xB1A0, 0x5F72, 0xF523, 0x92F7, 0x38A6, 0xD674, 0x7C25, 0x1D86, 0xB7D7, 0x5905, 0xF354, 0x9480, + 0x3ED1, 0xD003, 0x7A52, 0x1FAB, 0xB5FA, 0x5B28, 0xF179, 0x96AD, 0x3CFC, 0xD22E, 0x787F, 0x22D0, 0x8881, + 0x6653, 0xCC02, 0xABD6, 0x0187, 0xEF55, 0x4504, 0x20FD, 0x8AAC, 0x647E, 0xCE2F, 0xA9FB, 0x03AA, 0xED78, + 0x4729, 0x268A, 0x8CDB, 0x6209, 0xC858, 0xAF8C, 0x05DD, 0xEB0F, 0x415E, 0x24A7, 0x8EF6, 0x6024, 0xCA75, + 0xADA1, 0x07F0, 0xE922, 0x4373, 0x2A64, 0x8035, 0x6EE7, 0xC4B6, 0xA362, 0x0933, 0xE7E1, 0x4DB0, 0x2849, + 0x8218, 0x6CCA, 0xC69B, 0xA14F, 0x0B1E, 0xE5CC, 0x4F9D, 0x2E3E, 0x846F, 0x6ABD, 0xC0EC, 0xA738, 0x0D69, + 0xE3BB, 0x49EA, 0x2C13, 0x8642, 0x6890, 0xC2C1, 0xA515, 0x0F44, 0xE196, 0x4BC7, 0x33B8, 0x99E9, 0x773B, + 0xDD6A, 0xBABE, 0x10EF, 0xFE3D, 0x546C, 0x3195, 0x9BC4, 0x7516, 0xDF47, 0xB893, 0x12C2, 0xFC10, 0x5641, + 0x37E2, 0x9DB3, 0x7361, 0xD930, 0xBEE4, 0x14B5, 0xFA67, 0x5036, 0x35CF, 0x9F9E, 0x714C, 0xDB1D, 0xBCC9, + 0x1698, 0xF84A, 0x521B, 0x3B0C, 0x915D, 0x7F8F, 0xD5DE, 0xB20A, 0x185B, 0xF689, 0x5CD8, 0x3921, 0x9370, + 0x7DA2, 0xD7F3, 0xB027, 0x1A76, 0xF4A4, 0x5EF5, 0x3F56, 0x9507, 0x7BD5, 0xD184, 0xB650, 0x1C01, 0xF2D3, + 0x5882, 0x3D7B, 0x972A, 0x79F8, 0xD3A9, 0xB47D, 0x1E2C, 0xF0FE, 0x5AAF + }, + new ushort[] + { + 0x0000, 0x45A0, 0x8B40, 0xCEE0, 0x06A1, 0x4301, 0x8DE1, 0xC841, 0x0D42, 0x48E2, 0x8602, 0xC3A2, 0x0BE3, + 0x4E43, 0x80A3, 0xC503, 0x1A84, 0x5F24, 0x91C4, 0xD464, 0x1C25, 0x5985, 0x9765, 0xD2C5, 0x17C6, 0x5266, + 0x9C86, 0xD926, 0x1167, 0x54C7, 0x9A27, 0xDF87, 0x3508, 0x70A8, 0xBE48, 0xFBE8, 0x33A9, 0x7609, 0xB8E9, + 0xFD49, 0x384A, 0x7DEA, 0xB30A, 0xF6AA, 0x3EEB, 0x7B4B, 0xB5AB, 0xF00B, 0x2F8C, 0x6A2C, 0xA4CC, 0xE16C, + 0x292D, 0x6C8D, 0xA26D, 0xE7CD, 0x22CE, 0x676E, 0xA98E, 0xEC2E, 0x246F, 0x61CF, 0xAF2F, 0xEA8F, 0x6A10, + 0x2FB0, 0xE150, 0xA4F0, 0x6CB1, 0x2911, 0xE7F1, 0xA251, 0x6752, 0x22F2, 0xEC12, 0xA9B2, 0x61F3, 0x2453, + 0xEAB3, 0xAF13, 0x7094, 0x3534, 0xFBD4, 0xBE74, 0x7635, 0x3395, 0xFD75, 0xB8D5, 0x7DD6, 0x3876, 0xF696, + 0xB336, 0x7B77, 0x3ED7, 0xF037, 0xB597, 0x5F18, 0x1AB8, 0xD458, 0x91F8, 0x59B9, 0x1C19, 0xD2F9, 0x9759, + 0x525A, 0x17FA, 0xD91A, 0x9CBA, 0x54FB, 0x115B, 0xDFBB, 0x9A1B, 0x459C, 0x003C, 0xCEDC, 0x8B7C, 0x433D, + 0x069D, 0xC87D, 0x8DDD, 0x48DE, 0x0D7E, 0xC39E, 0x863E, 0x4E7F, 0x0BDF, 0xC53F, 0x809F, 0xD420, 0x9180, + 0x5F60, 0x1AC0, 0xD281, 0x9721, 0x59C1, 0x1C61, 0xD962, 0x9CC2, 0x5222, 0x1782, 0xDFC3, 0x9A63, 0x5483, + 0x1123, 0xCEA4, 0x8B04, 0x45E4, 0x0044, 0xC805, 0x8DA5, 0x4345, 0x06E5, 0xC3E6, 0x8646, 0x48A6, 0x0D06, + 0xC547, 0x80E7, 0x4E07, 0x0BA7, 0xE128, 0xA488, 0x6A68, 0x2FC8, 0xE789, 0xA229, 0x6CC9, 0x2969, 0xEC6A, + 0xA9CA, 0x672A, 0x228A, 0xEACB, 0xAF6B, 0x618B, 0x242B, 0xFBAC, 0xBE0C, 0x70EC, 0x354C, 0xFD0D, 0xB8AD, + 0x764D, 0x33ED, 0xF6EE, 0xB34E, 0x7DAE, 0x380E, 0xF04F, 0xB5EF, 0x7B0F, 0x3EAF, 0xBE30, 0xFB90, 0x3570, + 0x70D0, 0xB891, 0xFD31, 0x33D1, 0x7671, 0xB372, 0xF6D2, 0x3832, 0x7D92, 0xB5D3, 0xF073, 0x3E93, 0x7B33, + 0xA4B4, 0xE114, 0x2FF4, 0x6A54, 0xA215, 0xE7B5, 0x2955, 0x6CF5, 0xA9F6, 0xEC56, 0x22B6, 0x6716, 0xAF57, + 0xEAF7, 0x2417, 0x61B7, 0x8B38, 0xCE98, 0x0078, 0x45D8, 0x8D99, 0xC839, 0x06D9, 0x4379, 0x867A, 0xC3DA, + 0x0D3A, 0x489A, 0x80DB, 0xC57B, 0x0B9B, 0x4E3B, 0x91BC, 0xD41C, 0x1AFC, 0x5F5C, 0x971D, 0xD2BD, 0x1C5D, + 0x59FD, 0x9CFE, 0xD95E, 0x17BE, 0x521E, 0x9A5F, 0xDFFF, 0x111F, 0x54BF + }, + new ushort[] + { + 0x0000, 0xB861, 0x60E3, 0xD882, 0xC1C6, 0x79A7, 0xA125, 0x1944, 0x93AD, 0x2BCC, 0xF34E, 0x4B2F, 0x526B, + 0xEA0A, 0x3288, 0x8AE9, 0x377B, 0x8F1A, 0x5798, 0xEFF9, 0xF6BD, 0x4EDC, 0x965E, 0x2E3F, 0xA4D6, 0x1CB7, + 0xC435, 0x7C54, 0x6510, 0xDD71, 0x05F3, 0xBD92, 0x6EF6, 0xD697, 0x0E15, 0xB674, 0xAF30, 0x1751, 0xCFD3, + 0x77B2, 0xFD5B, 0x453A, 0x9DB8, 0x25D9, 0x3C9D, 0x84FC, 0x5C7E, 0xE41F, 0x598D, 0xE1EC, 0x396E, 0x810F, + 0x984B, 0x202A, 0xF8A8, 0x40C9, 0xCA20, 0x7241, 0xAAC3, 0x12A2, 0x0BE6, 0xB387, 0x6B05, 0xD364, 0xDDEC, + 0x658D, 0xBD0F, 0x056E, 0x1C2A, 0xA44B, 0x7CC9, 0xC4A8, 0x4E41, 0xF620, 0x2EA2, 0x96C3, 0x8F87, 0x37E6, + 0xEF64, 0x5705, 0xEA97, 0x52F6, 0x8A74, 0x3215, 0x2B51, 0x9330, 0x4BB2, 0xF3D3, 0x793A, 0xC15B, 0x19D9, + 0xA1B8, 0xB8FC, 0x009D, 0xD81F, 0x607E, 0xB31A, 0x0B7B, 0xD3F9, 0x6B98, 0x72DC, 0xCABD, 0x123F, 0xAA5E, + 0x20B7, 0x98D6, 0x4054, 0xF835, 0xE171, 0x5910, 0x8192, 0x39F3, 0x8461, 0x3C00, 0xE482, 0x5CE3, 0x45A7, + 0xFDC6, 0x2544, 0x9D25, 0x17CC, 0xAFAD, 0x772F, 0xCF4E, 0xD60A, 0x6E6B, 0xB6E9, 0x0E88, 0xABF9, 0x1398, + 0xCB1A, 0x737B, 0x6A3F, 0xD25E, 0x0ADC, 0xB2BD, 0x3854, 0x8035, 0x58B7, 0xE0D6, 0xF992, 0x41F3, 0x9971, + 0x2110, 0x9C82, 0x24E3, 0xFC61, 0x4400, 0x5D44, 0xE525, 0x3DA7, 0x85C6, 0x0F2F, 0xB74E, 0x6FCC, 0xD7AD, + 0xCEE9, 0x7688, 0xAE0A, 0x166B, 0xC50F, 0x7D6E, 0xA5EC, 0x1D8D, 0x04C9, 0xBCA8, 0x642A, 0xDC4B, 0x56A2, + 0xEEC3, 0x3641, 0x8E20, 0x9764, 0x2F05, 0xF787, 0x4FE6, 0xF274, 0x4A15, 0x9297, 0x2AF6, 0x33B2, 0x8BD3, + 0x5351, 0xEB30, 0x61D9, 0xD9B8, 0x013A, 0xB95B, 0xA01F, 0x187E, 0xC0FC, 0x789D, 0x7615, 0xCE74, 0x16F6, + 0xAE97, 0xB7D3, 0x0FB2, 0xD730, 0x6F51, 0xE5B8, 0x5DD9, 0x855B, 0x3D3A, 0x247E, 0x9C1F, 0x449D, 0xFCFC, + 0x416E, 0xF90F, 0x218D, 0x99EC, 0x80A8, 0x38C9, 0xE04B, 0x582A, 0xD2C3, 0x6AA2, 0xB220, 0x0A41, 0x1305, + 0xAB64, 0x73E6, 0xCB87, 0x18E3, 0xA082, 0x7800, 0xC061, 0xD925, 0x6144, 0xB9C6, 0x01A7, 0x8B4E, 0x332F, + 0xEBAD, 0x53CC, 0x4A88, 0xF2E9, 0x2A6B, 0x920A, 0x2F98, 0x97F9, 0x4F7B, 0xF71A, 0xEE5E, 0x563F, 0x8EBD, + 0x36DC, 0xBC35, 0x0454, 0xDCD6, 0x64B7, 0x7DF3, 0xC592, 0x1D10, 0xA571 + }, + new ushort[] + { + 0x0000, 0x47D3, 0x8FA6, 0xC875, 0x0F6D, 0x48BE, 0x80CB, 0xC718, 0x1EDA, 0x5909, 0x917C, 0xD6AF, 0x11B7, + 0x5664, 0x9E11, 0xD9C2, 0x3DB4, 0x7A67, 0xB212, 0xF5C1, 0x32D9, 0x750A, 0xBD7F, 0xFAAC, 0x236E, 0x64BD, + 0xACC8, 0xEB1B, 0x2C03, 0x6BD0, 0xA3A5, 0xE476, 0x7B68, 0x3CBB, 0xF4CE, 0xB31D, 0x7405, 0x33D6, 0xFBA3, + 0xBC70, 0x65B2, 0x2261, 0xEA14, 0xADC7, 0x6ADF, 0x2D0C, 0xE579, 0xA2AA, 0x46DC, 0x010F, 0xC97A, 0x8EA9, + 0x49B1, 0x0E62, 0xC617, 0x81C4, 0x5806, 0x1FD5, 0xD7A0, 0x9073, 0x576B, 0x10B8, 0xD8CD, 0x9F1E, 0xF6D0, + 0xB103, 0x7976, 0x3EA5, 0xF9BD, 0xBE6E, 0x761B, 0x31C8, 0xE80A, 0xAFD9, 0x67AC, 0x207F, 0xE767, 0xA0B4, + 0x68C1, 0x2F12, 0xCB64, 0x8CB7, 0x44C2, 0x0311, 0xC409, 0x83DA, 0x4BAF, 0x0C7C, 0xD5BE, 0x926D, 0x5A18, + 0x1DCB, 0xDAD3, 0x9D00, 0x5575, 0x12A6, 0x8DB8, 0xCA6B, 0x021E, 0x45CD, 0x82D5, 0xC506, 0x0D73, 0x4AA0, + 0x9362, 0xD4B1, 0x1CC4, 0x5B17, 0x9C0F, 0xDBDC, 0x13A9, 0x547A, 0xB00C, 0xF7DF, 0x3FAA, 0x7879, 0xBF61, + 0xF8B2, 0x30C7, 0x7714, 0xAED6, 0xE905, 0x2170, 0x66A3, 0xA1BB, 0xE668, 0x2E1D, 0x69CE, 0xFD81, 0xBA52, + 0x7227, 0x35F4, 0xF2EC, 0xB53F, 0x7D4A, 0x3A99, 0xE35B, 0xA488, 0x6CFD, 0x2B2E, 0xEC36, 0xABE5, 0x6390, + 0x2443, 0xC035, 0x87E6, 0x4F93, 0x0840, 0xCF58, 0x888B, 0x40FE, 0x072D, 0xDEEF, 0x993C, 0x5149, 0x169A, + 0xD182, 0x9651, 0x5E24, 0x19F7, 0x86E9, 0xC13A, 0x094F, 0x4E9C, 0x8984, 0xCE57, 0x0622, 0x41F1, 0x9833, + 0xDFE0, 0x1795, 0x5046, 0x975E, 0xD08D, 0x18F8, 0x5F2B, 0xBB5D, 0xFC8E, 0x34FB, 0x7328, 0xB430, 0xF3E3, + 0x3B96, 0x7C45, 0xA587, 0xE254, 0x2A21, 0x6DF2, 0xAAEA, 0xED39, 0x254C, 0x629F, 0x0B51, 0x4C82, 0x84F7, + 0xC324, 0x043C, 0x43EF, 0x8B9A, 0xCC49, 0x158B, 0x5258, 0x9A2D, 0xDDFE, 0x1AE6, 0x5D35, 0x9540, 0xD293, + 0x36E5, 0x7136, 0xB943, 0xFE90, 0x3988, 0x7E5B, 0xB62E, 0xF1FD, 0x283F, 0x6FEC, 0xA799, 0xE04A, 0x2752, + 0x6081, 0xA8F4, 0xEF27, 0x7039, 0x37EA, 0xFF9F, 0xB84C, 0x7F54, 0x3887, 0xF0F2, 0xB721, 0x6EE3, 0x2930, + 0xE145, 0xA696, 0x618E, 0x265D, 0xEE28, 0xA9FB, 0x4D8D, 0x0A5E, 0xC22B, 0x85F8, 0x42E0, 0x0533, 0xCD46, + 0x8A95, 0x5357, 0x1484, 0xDCF1, 0x9B22, 0x5C3A, 0x1BE9, 0xD39C, 0x944F } + }; - /// Gets the hash of a file in hexadecimal and as a byte array. - /// File path. - /// Byte array of the hash value. - public static string File(string filename, out byte[] hash) => - File(filename, out hash, CRC16_CCITT_POLY, CRC16_CCITT_SEED, _ccittCrc16Table, true); + /// Initializes an instance of the CRC16 with CCITT polynomial and seed. + /// + public CRC16CCITTContext() : base(CRC16_CCITT_POLY, CRC16_CCITT_SEED, _ccittCrc16Table, true) {} - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Length of the data buffer to hash. - /// Byte array of the hash value. - public static string Data(byte[] data, uint len, out byte[] hash) => - Data(data, len, out hash, CRC16_CCITT_POLY, CRC16_CCITT_SEED, _ccittCrc16Table, true); + /// Gets the hash of a file + /// File path. + public static byte[] File(string filename) + { + File(filename, out byte[] hash); - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Byte array of the hash value. - public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); - - /// Calculates the CCITT CRC16 of the specified buffer with the specified parameters - /// Buffer - public static ushort Calculate(byte[] buffer) => - Calculate(buffer, CRC16_CCITT_POLY, CRC16_CCITT_SEED, _ccittCrc16Table, true); + return hash; } + + /// Gets the hash of a file in hexadecimal and as a byte array. + /// File path. + /// Byte array of the hash value. + public static string File(string filename, out byte[] hash) => + File(filename, out hash, CRC16_CCITT_POLY, CRC16_CCITT_SEED, _ccittCrc16Table, true); + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Length of the data buffer to hash. + /// Byte array of the hash value. + public static string Data(byte[] data, uint len, out byte[] hash) => + Data(data, len, out hash, CRC16_CCITT_POLY, CRC16_CCITT_SEED, _ccittCrc16Table, true); + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Byte array of the hash value. + public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); + + /// Calculates the CCITT CRC16 of the specified buffer with the specified parameters + /// Buffer + public static ushort Calculate(byte[] buffer) => + Calculate(buffer, CRC16_CCITT_POLY, CRC16_CCITT_SEED, _ccittCrc16Table, true); } \ No newline at end of file diff --git a/Aaru6.Checksums/CRC16Context.cs b/Aaru6.Checksums/CRC16Context.cs index 1ea0033..f4191a6 100644 --- a/Aaru6.Checksums/CRC16Context.cs +++ b/Aaru6.Checksums/CRC16Context.cs @@ -37,585 +37,583 @@ using System.Text; using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; -namespace Aaru6.Checksums +namespace Aaru6.Checksums; + +/// +/// Implements a CRC16 algorithm +public class Crc16Context : IChecksum { - /// - /// Implements a CRC16 algorithm - public class Crc16Context : IChecksum + readonly ushort _finalSeed; + readonly bool _inverse; + readonly IntPtr _nativeContext; + readonly ushort[][] _table; + readonly bool _useCcitt; + readonly bool _useIbm; + readonly bool _useNative; + ushort _hashInt; + + /// Initializes the CRC16 table with a custom polynomial and seed + public Crc16Context(ushort polynomial, ushort seed, ushort[][] table, bool inverse) { - readonly ushort _finalSeed; - readonly bool _inverse; - readonly ushort[][] _table; - ushort _hashInt; - readonly IntPtr _nativeContext; - readonly bool _useCcitt; - readonly bool _useIbm; - readonly bool _useNative; + _hashInt = seed; + _finalSeed = seed; + _inverse = inverse; - /// Initializes the CRC16 table with a custom polynomial and seed - public Crc16Context(ushort polynomial, ushort seed, ushort[][] table, bool inverse) + _useNative = Native.IsSupported; + + _useCcitt = polynomial == CRC16CCITTContext.CRC16_CCITT_POLY && seed == CRC16CCITTContext.CRC16_CCITT_SEED && + inverse; + + _useIbm = polynomial == CRC16IBMContext.CRC16_IBM_POLY && seed == CRC16IBMContext.CRC16_IBM_SEED && !inverse; + + if(_useCcitt && _useNative) { - _hashInt = seed; - _finalSeed = seed; - _inverse = inverse; + _nativeContext = crc16_ccitt_init(); + _useNative = _nativeContext != IntPtr.Zero; + } + else if(_useIbm && _useNative) + { + _nativeContext = crc16_init(); + _useNative = _nativeContext != IntPtr.Zero; + } + else + _useNative = false; - _useNative = Native.IsSupported; + if(!_useNative) + _table = table ?? GenerateTable(polynomial, inverse); + } - _useCcitt = polynomial == CRC16CCITTContext.CRC16_CCITT_POLY && - seed == CRC16CCITTContext.CRC16_CCITT_SEED && inverse; + /// + /// Updates the hash with data. + /// Data buffer. + /// Length of buffer to hash. + public void Update(byte[] data, uint len) + { + switch(_useNative) + { + case true when _useCcitt: + crc16_ccitt_update(_nativeContext, data, len); - _useIbm = polynomial == CRC16IBMContext.CRC16_IBM_POLY && seed == CRC16IBMContext.CRC16_IBM_SEED && - !inverse; + break; + case true when _useIbm: + crc16_update(_nativeContext, data, len); - if(_useCcitt && _useNative) + break; + default: { - _nativeContext = crc16_ccitt_init(); - _useNative = _nativeContext != IntPtr.Zero; - } - else if(_useIbm && _useNative) - { - _nativeContext = crc16_init(); - _useNative = _nativeContext != IntPtr.Zero; - } - else - _useNative = false; + if(_inverse) + StepInverse(ref _hashInt, _table, data, len); + else + Step(ref _hashInt, _table, data, len); - if(!_useNative) - _table = table ?? GenerateTable(polynomial, inverse); + break; + } + } + } + + /// + /// Updates the hash with data. + /// Data buffer. + public void Update(byte[] data) => Update(data, (uint)data.Length); + + /// + /// Returns a byte array of the hash value. + public byte[] Final() + { + ushort crc = 0; + + switch(_useNative) + { + case true when _useCcitt: + crc16_ccitt_final(_nativeContext, ref crc); + crc16_ccitt_free(_nativeContext); + + break; + case true when _useIbm: + crc16_final(_nativeContext, ref crc); + crc16_free(_nativeContext); + + break; + default: + { + if(_inverse) + crc = (ushort)~(_hashInt ^ _finalSeed); + else + crc = (ushort)(_hashInt ^ _finalSeed); + + break; + } } - /// - /// Updates the hash with data. - /// Data buffer. - /// Length of buffer to hash. - public void Update(byte[] data, uint len) + return BigEndianBitConverter.GetBytes(crc); + } + + /// + /// Returns a hexadecimal representation of the hash value. + public string End() + { + var crc16Output = new StringBuilder(); + ushort final = 0; + + switch(_useNative) { - switch(_useNative) + case true when _useCcitt: + crc16_ccitt_final(_nativeContext, ref final); + crc16_ccitt_free(_nativeContext); + + break; + case true when _useIbm: + crc16_final(_nativeContext, ref final); + crc16_free(_nativeContext); + + break; + default: { - case true when _useCcitt: - crc16_ccitt_update(_nativeContext, data, len); + if(_inverse) + final = (ushort)~(_hashInt ^ _finalSeed); + else + final = (ushort)(_hashInt ^ _finalSeed); - break; - case true when _useIbm: - crc16_update(_nativeContext, data, len); + break; + } + } - break; - default: - { - if(_inverse) - StepInverse(ref _hashInt, _table, data, len); + byte[] finalBytes = BigEndianBitConverter.GetBytes(final); + + for(int i = 0; i < finalBytes.Length; i++) + crc16Output.Append(finalBytes[i].ToString("x2")); + + return crc16Output.ToString(); + } + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern IntPtr crc16_init(); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int crc16_update(IntPtr ctx, byte[] data, uint len); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int crc16_final(IntPtr ctx, ref ushort crc); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern void crc16_free(IntPtr ctx); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern IntPtr crc16_ccitt_init(); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int crc16_ccitt_update(IntPtr ctx, byte[] data, uint len); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int crc16_ccitt_final(IntPtr ctx, ref ushort crc); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern void crc16_ccitt_free(IntPtr ctx); + + static void Step(ref ushort previousCrc, ushort[][] table, byte[] data, uint len) + { + // Unroll according to Intel slicing by uint8_t + // http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf + // http://sourceforge.net/projects/slicing-by-8/ + + ushort crc; + int current_pos = 0; + const int unroll = 4; + const int bytes_at_once = 8 * unroll; + + crc = previousCrc; + + while(len >= bytes_at_once) + { + int unrolling; + + for(unrolling = 0; unrolling < unroll; unrolling++) + { + // TODO: What trick is Microsoft doing here that's faster than arithmetic conversion + uint one = BitConverter.ToUInt32(data, current_pos) ^ crc; + current_pos += 4; + uint two = BitConverter.ToUInt32(data, current_pos); + current_pos += 4; + + crc = (ushort)(table[0][(two >> 24) & 0xFF] ^ table[1][(two >> 16) & 0xFF] ^ + table[2][(two >> 8) & 0xFF] ^ table[3][two & 0xFF] ^ table[4][(one >> 24) & 0xFF] ^ + table[5][(one >> 16) & 0xFF] ^ table[6][(one >> 8) & 0xFF] ^ table[7][one & 0xFF]); + } + + len -= bytes_at_once; + } + + while(len-- != 0) + crc = (ushort)((crc >> 8) ^ table[0][(crc & 0xFF) ^ data[current_pos++]]); + + previousCrc = crc; + } + + static void StepInverse(ref ushort previousCrc, ushort[][] table, byte[] data, uint len) + { + // Unroll according to Intel slicing by uint8_t + // http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf + // http://sourceforge.net/projects/slicing-by-8/ + + ushort crc; + int current_pos = 0; + const int unroll = 4; + const int bytes_at_once = 8 * unroll; + + crc = previousCrc; + + while(len >= bytes_at_once) + { + int unrolling; + + for(unrolling = 0; unrolling < unroll; unrolling++) + { + crc = (ushort)(table[7][data[current_pos + 0] ^ (crc >> 8)] ^ + table[6][data[current_pos + 1] ^ (crc & 0xFF)] ^ table[5][data[current_pos + 2]] ^ + table[4][data[current_pos + 3]] ^ table[3][data[current_pos + 4]] ^ + table[2][data[current_pos + 5]] ^ table[1][data[current_pos + 6]] ^ + table[0][data[current_pos + 7]]); + + current_pos += 8; + } + + len -= bytes_at_once; + } + + while(len-- != 0) + crc = (ushort)((crc << 8) ^ table[0][(crc >> 8) ^ data[current_pos++]]); + + previousCrc = crc; + } + + static ushort[][] GenerateTable(ushort polynomial, bool inverseTable) + { + ushort[][] table = new ushort[8][]; + + for(int i = 0; i < 8; i++) + table[i] = new ushort[256]; + + if(!inverseTable) + for(uint i = 0; i < 256; i++) + { + uint entry = i; + + for(int j = 0; j < 8; j++) + if((entry & 1) == 1) + entry = (entry >> 1) ^ polynomial; else - Step(ref _hashInt, _table, data, len); + entry >>= 1; - break; - } + table[0][i] = (ushort)entry; } - } - - /// - /// Updates the hash with data. - /// Data buffer. - public void Update(byte[] data) => Update(data, (uint)data.Length); - - /// - /// Returns a byte array of the hash value. - public byte[] Final() + else { - ushort crc = 0; - - switch(_useNative) + for(uint i = 0; i < 256; i++) { - case true when _useCcitt: - crc16_ccitt_final(_nativeContext, ref crc); - crc16_ccitt_free(_nativeContext); + uint entry = i << 8; - break; - case true when _useIbm: - crc16_final(_nativeContext, ref crc); - crc16_free(_nativeContext); - - break; - default: + for(uint j = 0; j < 8; j++) { - if(_inverse) - crc = (ushort)~(_hashInt ^ _finalSeed); + if((entry & 0x8000) > 0) + entry = (entry << 1) ^ polynomial; else - crc = (ushort)(_hashInt ^ _finalSeed); - - break; - } - } - - return BigEndianBitConverter.GetBytes(crc); - } - - /// - /// Returns a hexadecimal representation of the hash value. - public string End() - { - var crc16Output = new StringBuilder(); - ushort final = 0; - - switch(_useNative) - { - case true when _useCcitt: - crc16_ccitt_final(_nativeContext, ref final); - crc16_ccitt_free(_nativeContext); - - break; - case true when _useIbm: - crc16_final(_nativeContext, ref final); - crc16_free(_nativeContext); - - break; - default: - { - if(_inverse) - final = (ushort)~(_hashInt ^ _finalSeed); - else - final = (ushort)(_hashInt ^ _finalSeed); - - break; - } - } - - byte[] finalBytes = BigEndianBitConverter.GetBytes(final); - - for(int i = 0; i < finalBytes.Length; i++) - crc16Output.Append(finalBytes[i].ToString("x2")); - - return crc16Output.ToString(); - } - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern IntPtr crc16_init(); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int crc16_update(IntPtr ctx, byte[] data, uint len); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int crc16_final(IntPtr ctx, ref ushort crc); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern void crc16_free(IntPtr ctx); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern IntPtr crc16_ccitt_init(); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int crc16_ccitt_update(IntPtr ctx, byte[] data, uint len); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int crc16_ccitt_final(IntPtr ctx, ref ushort crc); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern void crc16_ccitt_free(IntPtr ctx); - - static void Step(ref ushort previousCrc, ushort[][] table, byte[] data, uint len) - { - // Unroll according to Intel slicing by uint8_t - // http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf - // http://sourceforge.net/projects/slicing-by-8/ - - ushort crc; - int current_pos = 0; - const int unroll = 4; - const int bytes_at_once = 8 * unroll; - - crc = previousCrc; - - while(len >= bytes_at_once) - { - int unrolling; - - for(unrolling = 0; unrolling < unroll; unrolling++) - { - // TODO: What trick is Microsoft doing here that's faster than arithmetic conversion - uint one = BitConverter.ToUInt32(data, current_pos) ^ crc; - current_pos += 4; - uint two = BitConverter.ToUInt32(data, current_pos); - current_pos += 4; - - crc = (ushort)(table[0][(two >> 24) & 0xFF] ^ table[1][(two >> 16) & 0xFF] ^ - table[2][(two >> 8) & 0xFF] ^ table[3][two & 0xFF] ^ table[4][(one >> 24) & 0xFF] ^ - table[5][(one >> 16) & 0xFF] ^ table[6][(one >> 8) & 0xFF] ^ table[7][one & 0xFF]); - } - - len -= bytes_at_once; - } - - while(len-- != 0) - crc = (ushort)((crc >> 8) ^ table[0][(crc & 0xFF) ^ data[current_pos++]]); - - previousCrc = crc; - } - - static void StepInverse(ref ushort previousCrc, ushort[][] table, byte[] data, uint len) - { - // Unroll according to Intel slicing by uint8_t - // http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf - // http://sourceforge.net/projects/slicing-by-8/ - - ushort crc; - int current_pos = 0; - const int unroll = 4; - const int bytes_at_once = 8 * unroll; - - crc = previousCrc; - - while(len >= bytes_at_once) - { - int unrolling; - - for(unrolling = 0; unrolling < unroll; unrolling++) - { - crc = (ushort)(table[7][data[current_pos + 0] ^ (crc >> 8)] ^ - table[6][data[current_pos + 1] ^ (crc & 0xFF)] ^ table[5][data[current_pos + 2]] ^ - table[4][data[current_pos + 3]] ^ table[3][data[current_pos + 4]] ^ - table[2][data[current_pos + 5]] ^ table[1][data[current_pos + 6]] ^ - table[0][data[current_pos + 7]]); - - current_pos += 8; - } - - len -= bytes_at_once; - } - - while(len-- != 0) - crc = (ushort)((crc << 8) ^ table[0][(crc >> 8) ^ data[current_pos++]]); - - previousCrc = crc; - } - - static ushort[][] GenerateTable(ushort polynomial, bool inverseTable) - { - ushort[][] table = new ushort[8][]; - - for(int i = 0; i < 8; i++) - table[i] = new ushort[256]; - - if(!inverseTable) - for(uint i = 0; i < 256; i++) - { - uint entry = i; - - for(int j = 0; j < 8; j++) - if((entry & 1) == 1) - entry = (entry >> 1) ^ polynomial; - else - entry >>= 1; + entry <<= 1; table[0][i] = (ushort)entry; } - else - { - for(uint i = 0; i < 256; i++) - { - uint entry = i << 8; - - for(uint j = 0; j < 8; j++) - { - if((entry & 0x8000) > 0) - entry = (entry << 1) ^ polynomial; - else - entry <<= 1; - - table[0][i] = (ushort)entry; - } - } } - - for(int slice = 1; slice < 8; slice++) - for(int i = 0; i < 256; i++) - { - if(inverseTable) - table[slice][i] = (ushort)((table[slice - 1][i] << 8) ^ table[0][table[slice - 1][i] >> 8]); - else - table[slice][i] = (ushort)((table[slice - 1][i] >> 8) ^ table[0][table[slice - 1][i] & 0xFF]); - } - - return table; } - /// Gets the hash of a file in hexadecimal and as a byte array. - /// File path. - /// Byte array of the hash value. - /// CRC polynomial - /// CRC seed - /// CRC lookup table - /// Is CRC inverted? - public static string File(string filename, out byte[] hash, ushort polynomial, ushort seed, ushort[][] table, - bool inverse) + for(int slice = 1; slice < 8; slice++) + for(int i = 0; i < 256; i++) + { + if(inverseTable) + table[slice][i] = (ushort)((table[slice - 1][i] << 8) ^ table[0][table[slice - 1][i] >> 8]); + else + table[slice][i] = (ushort)((table[slice - 1][i] >> 8) ^ table[0][table[slice - 1][i] & 0xFF]); + } + + return table; + } + + /// Gets the hash of a file in hexadecimal and as a byte array. + /// File path. + /// Byte array of the hash value. + /// CRC polynomial + /// CRC seed + /// CRC lookup table + /// Is CRC inverted? + public static string File(string filename, out byte[] hash, ushort polynomial, ushort seed, ushort[][] table, + bool inverse) + { + bool useNative = Native.IsSupported; + + bool useCcitt = polynomial == CRC16CCITTContext.CRC16_CCITT_POLY && + seed == CRC16CCITTContext.CRC16_CCITT_SEED && inverse; + + bool useIbm = polynomial == CRC16IBMContext.CRC16_IBM_POLY && seed == CRC16IBMContext.CRC16_IBM_SEED && + !inverse; + + IntPtr nativeContext = IntPtr.Zero; + + var fileStream = new FileStream(filename, FileMode.Open); + + ushort localHashInt = seed; + + switch(useNative) { - bool useNative = Native.IsSupported; + case true when useCcitt: + nativeContext = crc16_ccitt_init(); + useNative = nativeContext != IntPtr.Zero; - bool useCcitt = polynomial == CRC16CCITTContext.CRC16_CCITT_POLY && - seed == CRC16CCITTContext.CRC16_CCITT_SEED && inverse; + break; + case true when useIbm: + nativeContext = crc16_init(); + useNative = nativeContext != IntPtr.Zero; - bool useIbm = polynomial == CRC16IBMContext.CRC16_IBM_POLY && seed == CRC16IBMContext.CRC16_IBM_SEED && - !inverse; - - IntPtr nativeContext = IntPtr.Zero; - - var fileStream = new FileStream(filename, FileMode.Open); - - ushort localHashInt = seed; - - switch(useNative) - { - case true when useCcitt: - nativeContext = crc16_ccitt_init(); - useNative = nativeContext != IntPtr.Zero; - - break; - case true when useIbm: - nativeContext = crc16_init(); - useNative = nativeContext != IntPtr.Zero; - - break; - } - - ushort[][] localTable = table ?? GenerateTable(polynomial, inverse); - - byte[] buffer = new byte[65536]; - int read = fileStream.Read(buffer, 0, 65536); - - while(read > 0) - { - switch(useNative) - { - case true when useCcitt: - crc16_ccitt_update(nativeContext, buffer, (uint)read); - - break; - case true when useIbm: - crc16_update(nativeContext, buffer, (uint)read); - - break; - default: - { - if(inverse) - StepInverse(ref localHashInt, localTable, buffer, (uint)read); - else - Step(ref localHashInt, localTable, buffer, (uint)read); - - break; - } - } - - read = fileStream.Read(buffer, 0, 65536); - } - - localHashInt ^= seed; - - switch(useNative) - { - case true when useCcitt: - crc16_ccitt_final(nativeContext, ref localHashInt); - crc16_ccitt_free(nativeContext); - - break; - case true when useIbm: - crc16_final(nativeContext, ref localHashInt); - crc16_free(nativeContext); - - break; - default: - { - if(inverse) - localHashInt = (ushort)~localHashInt; - - break; - } - } - - hash = BigEndianBitConverter.GetBytes(localHashInt); - - var crc16Output = new StringBuilder(); - - foreach(byte h in hash) - crc16Output.Append(h.ToString("x2")); - - fileStream.Close(); - - return crc16Output.ToString(); + break; } - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Length of the data buffer to hash. - /// Byte array of the hash value. - /// CRC polynomial - /// CRC seed - /// CRC lookup table - /// Is CRC inverted? - public static string Data(byte[] data, uint len, out byte[] hash, ushort polynomial, ushort seed, - ushort[][] table, bool inverse) + ushort[][] localTable = table ?? GenerateTable(polynomial, inverse); + + byte[] buffer = new byte[65536]; + int read = fileStream.Read(buffer, 0, 65536); + + while(read > 0) { - bool useNative = Native.IsSupported; - - bool useCcitt = polynomial == CRC16CCITTContext.CRC16_CCITT_POLY && - seed == CRC16CCITTContext.CRC16_CCITT_SEED && inverse; - - bool useIbm = polynomial == CRC16IBMContext.CRC16_IBM_POLY && seed == CRC16IBMContext.CRC16_IBM_SEED && - !inverse; - - IntPtr nativeContext = IntPtr.Zero; - - ushort localHashInt = seed; - switch(useNative) { case true when useCcitt: - nativeContext = crc16_ccitt_init(); - useNative = nativeContext != IntPtr.Zero; + crc16_ccitt_update(nativeContext, buffer, (uint)read); break; case true when useIbm: - nativeContext = crc16_init(); - useNative = nativeContext != IntPtr.Zero; - - break; - } - - ushort[][] localTable = table ?? GenerateTable(polynomial, inverse); - - switch(useNative) - { - case true when useCcitt: - crc16_ccitt_update(nativeContext, data, len); - - break; - case true when useIbm: - crc16_update(nativeContext, data, len); + crc16_update(nativeContext, buffer, (uint)read); break; default: { if(inverse) - StepInverse(ref localHashInt, localTable, data, len); + StepInverse(ref localHashInt, localTable, buffer, (uint)read); else - Step(ref localHashInt, localTable, data, len); + Step(ref localHashInt, localTable, buffer, (uint)read); break; } } - localHashInt ^= seed; - - switch(useNative) - { - case true when useCcitt: - crc16_ccitt_final(nativeContext, ref localHashInt); - crc16_ccitt_free(nativeContext); - - break; - case true when useIbm: - crc16_final(nativeContext, ref localHashInt); - crc16_free(nativeContext); - - break; - default: - { - if(inverse) - localHashInt = (ushort)~localHashInt; - - break; - } - } - - hash = BigEndianBitConverter.GetBytes(localHashInt); - - var crc16Output = new StringBuilder(); - - foreach(byte h in hash) - crc16Output.Append(h.ToString("x2")); - - return crc16Output.ToString(); + read = fileStream.Read(buffer, 0, 65536); } - /// Calculates the CRC16 of the specified buffer with the specified parameters - /// Buffer - /// Polynomial - /// Seed - /// Pre-generated lookup table - /// Inverse CRC - /// CRC16 - public static ushort Calculate(byte[] buffer, ushort polynomial, ushort seed, ushort[][] table, bool inverse) + localHashInt ^= seed; + + switch(useNative) { - bool useNative = Native.IsSupported; + case true when useCcitt: + crc16_ccitt_final(nativeContext, ref localHashInt); + crc16_ccitt_free(nativeContext); - bool useCcitt = polynomial == CRC16CCITTContext.CRC16_CCITT_POLY && - seed == CRC16CCITTContext.CRC16_CCITT_SEED && inverse; + break; + case true when useIbm: + crc16_final(nativeContext, ref localHashInt); + crc16_free(nativeContext); - bool useIbm = polynomial == CRC16IBMContext.CRC16_IBM_POLY && seed == CRC16IBMContext.CRC16_IBM_SEED && - !inverse; - - IntPtr nativeContext = IntPtr.Zero; - - ushort localHashInt = seed; - - switch(useNative) + break; + default: { - case true when useCcitt: - nativeContext = crc16_ccitt_init(); - useNative = nativeContext != IntPtr.Zero; + if(inverse) + localHashInt = (ushort)~localHashInt; - break; - case true when useIbm: - nativeContext = crc16_init(); - useNative = nativeContext != IntPtr.Zero; - - break; + break; } - - ushort[][] localTable = table ?? GenerateTable(polynomial, inverse); - - switch(useNative) - { - case true when useCcitt: - crc16_ccitt_update(nativeContext, buffer, (uint)buffer.Length); - - break; - case true when useIbm: - crc16_update(nativeContext, buffer, (uint)buffer.Length); - - break; - default: - { - if(inverse) - StepInverse(ref localHashInt, localTable, buffer, (uint)buffer.Length); - else - Step(ref localHashInt, localTable, buffer, (uint)buffer.Length); - - break; - } - } - - localHashInt ^= seed; - - switch(useNative) - { - case true when useCcitt: - crc16_ccitt_final(nativeContext, ref localHashInt); - crc16_ccitt_free(nativeContext); - - break; - case true when useIbm: - crc16_final(nativeContext, ref localHashInt); - crc16_free(nativeContext); - - break; - default: - { - if(inverse) - localHashInt = (ushort)~localHashInt; - - break; - } - } - - return localHashInt; } + + hash = BigEndianBitConverter.GetBytes(localHashInt); + + var crc16Output = new StringBuilder(); + + foreach(byte h in hash) + crc16Output.Append(h.ToString("x2")); + + fileStream.Close(); + + return crc16Output.ToString(); + } + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Length of the data buffer to hash. + /// Byte array of the hash value. + /// CRC polynomial + /// CRC seed + /// CRC lookup table + /// Is CRC inverted? + public static string Data(byte[] data, uint len, out byte[] hash, ushort polynomial, ushort seed, ushort[][] table, + bool inverse) + { + bool useNative = Native.IsSupported; + + bool useCcitt = polynomial == CRC16CCITTContext.CRC16_CCITT_POLY && + seed == CRC16CCITTContext.CRC16_CCITT_SEED && inverse; + + bool useIbm = polynomial == CRC16IBMContext.CRC16_IBM_POLY && seed == CRC16IBMContext.CRC16_IBM_SEED && + !inverse; + + IntPtr nativeContext = IntPtr.Zero; + + ushort localHashInt = seed; + + switch(useNative) + { + case true when useCcitt: + nativeContext = crc16_ccitt_init(); + useNative = nativeContext != IntPtr.Zero; + + break; + case true when useIbm: + nativeContext = crc16_init(); + useNative = nativeContext != IntPtr.Zero; + + break; + } + + ushort[][] localTable = table ?? GenerateTable(polynomial, inverse); + + switch(useNative) + { + case true when useCcitt: + crc16_ccitt_update(nativeContext, data, len); + + break; + case true when useIbm: + crc16_update(nativeContext, data, len); + + break; + default: + { + if(inverse) + StepInverse(ref localHashInt, localTable, data, len); + else + Step(ref localHashInt, localTable, data, len); + + break; + } + } + + localHashInt ^= seed; + + switch(useNative) + { + case true when useCcitt: + crc16_ccitt_final(nativeContext, ref localHashInt); + crc16_ccitt_free(nativeContext); + + break; + case true when useIbm: + crc16_final(nativeContext, ref localHashInt); + crc16_free(nativeContext); + + break; + default: + { + if(inverse) + localHashInt = (ushort)~localHashInt; + + break; + } + } + + hash = BigEndianBitConverter.GetBytes(localHashInt); + + var crc16Output = new StringBuilder(); + + foreach(byte h in hash) + crc16Output.Append(h.ToString("x2")); + + return crc16Output.ToString(); + } + + /// Calculates the CRC16 of the specified buffer with the specified parameters + /// Buffer + /// Polynomial + /// Seed + /// Pre-generated lookup table + /// Inverse CRC + /// CRC16 + public static ushort Calculate(byte[] buffer, ushort polynomial, ushort seed, ushort[][] table, bool inverse) + { + bool useNative = Native.IsSupported; + + bool useCcitt = polynomial == CRC16CCITTContext.CRC16_CCITT_POLY && + seed == CRC16CCITTContext.CRC16_CCITT_SEED && inverse; + + bool useIbm = polynomial == CRC16IBMContext.CRC16_IBM_POLY && seed == CRC16IBMContext.CRC16_IBM_SEED && + !inverse; + + IntPtr nativeContext = IntPtr.Zero; + + ushort localHashInt = seed; + + switch(useNative) + { + case true when useCcitt: + nativeContext = crc16_ccitt_init(); + useNative = nativeContext != IntPtr.Zero; + + break; + case true when useIbm: + nativeContext = crc16_init(); + useNative = nativeContext != IntPtr.Zero; + + break; + } + + ushort[][] localTable = table ?? GenerateTable(polynomial, inverse); + + switch(useNative) + { + case true when useCcitt: + crc16_ccitt_update(nativeContext, buffer, (uint)buffer.Length); + + break; + case true when useIbm: + crc16_update(nativeContext, buffer, (uint)buffer.Length); + + break; + default: + { + if(inverse) + StepInverse(ref localHashInt, localTable, buffer, (uint)buffer.Length); + else + Step(ref localHashInt, localTable, buffer, (uint)buffer.Length); + + break; + } + } + + localHashInt ^= seed; + + switch(useNative) + { + case true when useCcitt: + crc16_ccitt_final(nativeContext, ref localHashInt); + crc16_ccitt_free(nativeContext); + + break; + case true when useIbm: + crc16_final(nativeContext, ref localHashInt); + crc16_free(nativeContext); + + break; + default: + { + if(inverse) + localHashInt = (ushort)~localHashInt; + + break; + } + } + + return localHashInt; } } \ No newline at end of file diff --git a/Aaru6.Checksums/CRC16IBMContext.cs b/Aaru6.Checksums/CRC16IBMContext.cs index 6b47b33..bd80198 100644 --- a/Aaru6.Checksums/CRC16IBMContext.cs +++ b/Aaru6.Checksums/CRC16IBMContext.cs @@ -30,232 +30,231 @@ // Copyright © 2011-2023 Natalia Portillo // ****************************************************************************/ -namespace Aaru6.Checksums +namespace Aaru6.Checksums; + +/// +/// Implements the CRC16 algorithm with IBM polynomial and seed +public sealed class CRC16IBMContext : Crc16Context { - /// - /// Implements the CRC16 algorithm with IBM polynomial and seed - public sealed class CRC16IBMContext : Crc16Context + internal const ushort CRC16_IBM_POLY = 0xA001; + internal const ushort CRC16_IBM_SEED = 0x0000; + + static readonly ushort[][] _ibmCrc16Table = { - internal const ushort CRC16_IBM_POLY = 0xA001; - internal const ushort CRC16_IBM_SEED = 0x0000; - - static readonly ushort[][] _ibmCrc16Table = + new ushort[] { - new ushort[] - { - 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, - 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, - 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, - 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, - 0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, - 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, - 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, - 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, - 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, - 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0, - 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, - 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41, - 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, - 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, - 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, - 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, - 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, - 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, - 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, - 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 - }, - new ushort[] - { - 0x0000, 0x9001, 0x6001, 0xF000, 0xC002, 0x5003, 0xA003, 0x3002, 0xC007, 0x5006, 0xA006, 0x3007, 0x0005, - 0x9004, 0x6004, 0xF005, 0xC00D, 0x500C, 0xA00C, 0x300D, 0x000F, 0x900E, 0x600E, 0xF00F, 0x000A, 0x900B, - 0x600B, 0xF00A, 0xC008, 0x5009, 0xA009, 0x3008, 0xC019, 0x5018, 0xA018, 0x3019, 0x001B, 0x901A, 0x601A, - 0xF01B, 0x001E, 0x901F, 0x601F, 0xF01E, 0xC01C, 0x501D, 0xA01D, 0x301C, 0x0014, 0x9015, 0x6015, 0xF014, - 0xC016, 0x5017, 0xA017, 0x3016, 0xC013, 0x5012, 0xA012, 0x3013, 0x0011, 0x9010, 0x6010, 0xF011, 0xC031, - 0x5030, 0xA030, 0x3031, 0x0033, 0x9032, 0x6032, 0xF033, 0x0036, 0x9037, 0x6037, 0xF036, 0xC034, 0x5035, - 0xA035, 0x3034, 0x003C, 0x903D, 0x603D, 0xF03C, 0xC03E, 0x503F, 0xA03F, 0x303E, 0xC03B, 0x503A, 0xA03A, - 0x303B, 0x0039, 0x9038, 0x6038, 0xF039, 0x0028, 0x9029, 0x6029, 0xF028, 0xC02A, 0x502B, 0xA02B, 0x302A, - 0xC02F, 0x502E, 0xA02E, 0x302F, 0x002D, 0x902C, 0x602C, 0xF02D, 0xC025, 0x5024, 0xA024, 0x3025, 0x0027, - 0x9026, 0x6026, 0xF027, 0x0022, 0x9023, 0x6023, 0xF022, 0xC020, 0x5021, 0xA021, 0x3020, 0xC061, 0x5060, - 0xA060, 0x3061, 0x0063, 0x9062, 0x6062, 0xF063, 0x0066, 0x9067, 0x6067, 0xF066, 0xC064, 0x5065, 0xA065, - 0x3064, 0x006C, 0x906D, 0x606D, 0xF06C, 0xC06E, 0x506F, 0xA06F, 0x306E, 0xC06B, 0x506A, 0xA06A, 0x306B, - 0x0069, 0x9068, 0x6068, 0xF069, 0x0078, 0x9079, 0x6079, 0xF078, 0xC07A, 0x507B, 0xA07B, 0x307A, 0xC07F, - 0x507E, 0xA07E, 0x307F, 0x007D, 0x907C, 0x607C, 0xF07D, 0xC075, 0x5074, 0xA074, 0x3075, 0x0077, 0x9076, - 0x6076, 0xF077, 0x0072, 0x9073, 0x6073, 0xF072, 0xC070, 0x5071, 0xA071, 0x3070, 0x0050, 0x9051, 0x6051, - 0xF050, 0xC052, 0x5053, 0xA053, 0x3052, 0xC057, 0x5056, 0xA056, 0x3057, 0x0055, 0x9054, 0x6054, 0xF055, - 0xC05D, 0x505C, 0xA05C, 0x305D, 0x005F, 0x905E, 0x605E, 0xF05F, 0x005A, 0x905B, 0x605B, 0xF05A, 0xC058, - 0x5059, 0xA059, 0x3058, 0xC049, 0x5048, 0xA048, 0x3049, 0x004B, 0x904A, 0x604A, 0xF04B, 0x004E, 0x904F, - 0x604F, 0xF04E, 0xC04C, 0x504D, 0xA04D, 0x304C, 0x0044, 0x9045, 0x6045, 0xF044, 0xC046, 0x5047, 0xA047, - 0x3046, 0xC043, 0x5042, 0xA042, 0x3043, 0x0041, 0x9040, 0x6040, 0xF041 - }, - new ushort[] - { - 0x0000, 0xC051, 0xC0A1, 0x00F0, 0xC141, 0x0110, 0x01E0, 0xC1B1, 0xC281, 0x02D0, 0x0220, 0xC271, 0x03C0, - 0xC391, 0xC361, 0x0330, 0xC501, 0x0550, 0x05A0, 0xC5F1, 0x0440, 0xC411, 0xC4E1, 0x04B0, 0x0780, 0xC7D1, - 0xC721, 0x0770, 0xC6C1, 0x0690, 0x0660, 0xC631, 0xCA01, 0x0A50, 0x0AA0, 0xCAF1, 0x0B40, 0xCB11, 0xCBE1, - 0x0BB0, 0x0880, 0xC8D1, 0xC821, 0x0870, 0xC9C1, 0x0990, 0x0960, 0xC931, 0x0F00, 0xCF51, 0xCFA1, 0x0FF0, - 0xCE41, 0x0E10, 0x0EE0, 0xCEB1, 0xCD81, 0x0DD0, 0x0D20, 0xCD71, 0x0CC0, 0xCC91, 0xCC61, 0x0C30, 0xD401, - 0x1450, 0x14A0, 0xD4F1, 0x1540, 0xD511, 0xD5E1, 0x15B0, 0x1680, 0xD6D1, 0xD621, 0x1670, 0xD7C1, 0x1790, - 0x1760, 0xD731, 0x1100, 0xD151, 0xD1A1, 0x11F0, 0xD041, 0x1010, 0x10E0, 0xD0B1, 0xD381, 0x13D0, 0x1320, - 0xD371, 0x12C0, 0xD291, 0xD261, 0x1230, 0x1E00, 0xDE51, 0xDEA1, 0x1EF0, 0xDF41, 0x1F10, 0x1FE0, 0xDFB1, - 0xDC81, 0x1CD0, 0x1C20, 0xDC71, 0x1DC0, 0xDD91, 0xDD61, 0x1D30, 0xDB01, 0x1B50, 0x1BA0, 0xDBF1, 0x1A40, - 0xDA11, 0xDAE1, 0x1AB0, 0x1980, 0xD9D1, 0xD921, 0x1970, 0xD8C1, 0x1890, 0x1860, 0xD831, 0xE801, 0x2850, - 0x28A0, 0xE8F1, 0x2940, 0xE911, 0xE9E1, 0x29B0, 0x2A80, 0xEAD1, 0xEA21, 0x2A70, 0xEBC1, 0x2B90, 0x2B60, - 0xEB31, 0x2D00, 0xED51, 0xEDA1, 0x2DF0, 0xEC41, 0x2C10, 0x2CE0, 0xECB1, 0xEF81, 0x2FD0, 0x2F20, 0xEF71, - 0x2EC0, 0xEE91, 0xEE61, 0x2E30, 0x2200, 0xE251, 0xE2A1, 0x22F0, 0xE341, 0x2310, 0x23E0, 0xE3B1, 0xE081, - 0x20D0, 0x2020, 0xE071, 0x21C0, 0xE191, 0xE161, 0x2130, 0xE701, 0x2750, 0x27A0, 0xE7F1, 0x2640, 0xE611, - 0xE6E1, 0x26B0, 0x2580, 0xE5D1, 0xE521, 0x2570, 0xE4C1, 0x2490, 0x2460, 0xE431, 0x3C00, 0xFC51, 0xFCA1, - 0x3CF0, 0xFD41, 0x3D10, 0x3DE0, 0xFDB1, 0xFE81, 0x3ED0, 0x3E20, 0xFE71, 0x3FC0, 0xFF91, 0xFF61, 0x3F30, - 0xF901, 0x3950, 0x39A0, 0xF9F1, 0x3840, 0xF811, 0xF8E1, 0x38B0, 0x3B80, 0xFBD1, 0xFB21, 0x3B70, 0xFAC1, - 0x3A90, 0x3A60, 0xFA31, 0xF601, 0x3650, 0x36A0, 0xF6F1, 0x3740, 0xF711, 0xF7E1, 0x37B0, 0x3480, 0xF4D1, - 0xF421, 0x3470, 0xF5C1, 0x3590, 0x3560, 0xF531, 0x3300, 0xF351, 0xF3A1, 0x33F0, 0xF241, 0x3210, 0x32E0, - 0xF2B1, 0xF181, 0x31D0, 0x3120, 0xF171, 0x30C0, 0xF091, 0xF061, 0x3030 - }, - new ushort[] - { - 0x0000, 0xFC01, 0xB801, 0x4400, 0x3001, 0xCC00, 0x8800, 0x7401, 0x6002, 0x9C03, 0xD803, 0x2402, 0x5003, - 0xAC02, 0xE802, 0x1403, 0xC004, 0x3C05, 0x7805, 0x8404, 0xF005, 0x0C04, 0x4804, 0xB405, 0xA006, 0x5C07, - 0x1807, 0xE406, 0x9007, 0x6C06, 0x2806, 0xD407, 0xC00B, 0x3C0A, 0x780A, 0x840B, 0xF00A, 0x0C0B, 0x480B, - 0xB40A, 0xA009, 0x5C08, 0x1808, 0xE409, 0x9008, 0x6C09, 0x2809, 0xD408, 0x000F, 0xFC0E, 0xB80E, 0x440F, - 0x300E, 0xCC0F, 0x880F, 0x740E, 0x600D, 0x9C0C, 0xD80C, 0x240D, 0x500C, 0xAC0D, 0xE80D, 0x140C, 0xC015, - 0x3C14, 0x7814, 0x8415, 0xF014, 0x0C15, 0x4815, 0xB414, 0xA017, 0x5C16, 0x1816, 0xE417, 0x9016, 0x6C17, - 0x2817, 0xD416, 0x0011, 0xFC10, 0xB810, 0x4411, 0x3010, 0xCC11, 0x8811, 0x7410, 0x6013, 0x9C12, 0xD812, - 0x2413, 0x5012, 0xAC13, 0xE813, 0x1412, 0x001E, 0xFC1F, 0xB81F, 0x441E, 0x301F, 0xCC1E, 0x881E, 0x741F, - 0x601C, 0x9C1D, 0xD81D, 0x241C, 0x501D, 0xAC1C, 0xE81C, 0x141D, 0xC01A, 0x3C1B, 0x781B, 0x841A, 0xF01B, - 0x0C1A, 0x481A, 0xB41B, 0xA018, 0x5C19, 0x1819, 0xE418, 0x9019, 0x6C18, 0x2818, 0xD419, 0xC029, 0x3C28, - 0x7828, 0x8429, 0xF028, 0x0C29, 0x4829, 0xB428, 0xA02B, 0x5C2A, 0x182A, 0xE42B, 0x902A, 0x6C2B, 0x282B, - 0xD42A, 0x002D, 0xFC2C, 0xB82C, 0x442D, 0x302C, 0xCC2D, 0x882D, 0x742C, 0x602F, 0x9C2E, 0xD82E, 0x242F, - 0x502E, 0xAC2F, 0xE82F, 0x142E, 0x0022, 0xFC23, 0xB823, 0x4422, 0x3023, 0xCC22, 0x8822, 0x7423, 0x6020, - 0x9C21, 0xD821, 0x2420, 0x5021, 0xAC20, 0xE820, 0x1421, 0xC026, 0x3C27, 0x7827, 0x8426, 0xF027, 0x0C26, - 0x4826, 0xB427, 0xA024, 0x5C25, 0x1825, 0xE424, 0x9025, 0x6C24, 0x2824, 0xD425, 0x003C, 0xFC3D, 0xB83D, - 0x443C, 0x303D, 0xCC3C, 0x883C, 0x743D, 0x603E, 0x9C3F, 0xD83F, 0x243E, 0x503F, 0xAC3E, 0xE83E, 0x143F, - 0xC038, 0x3C39, 0x7839, 0x8438, 0xF039, 0x0C38, 0x4838, 0xB439, 0xA03A, 0x5C3B, 0x183B, 0xE43A, 0x903B, - 0x6C3A, 0x283A, 0xD43B, 0xC037, 0x3C36, 0x7836, 0x8437, 0xF036, 0x0C37, 0x4837, 0xB436, 0xA035, 0x5C34, - 0x1834, 0xE435, 0x9034, 0x6C35, 0x2835, 0xD434, 0x0033, 0xFC32, 0xB832, 0x4433, 0x3032, 0xCC33, 0x8833, - 0x7432, 0x6031, 0x9C30, 0xD830, 0x2431, 0x5030, 0xAC31, 0xE831, 0x1430 - }, - new ushort[] - { - 0x0000, 0xC03D, 0xC079, 0x0044, 0xC0F1, 0x00CC, 0x0088, 0xC0B5, 0xC1E1, 0x01DC, 0x0198, 0xC1A5, 0x0110, - 0xC12D, 0xC169, 0x0154, 0xC3C1, 0x03FC, 0x03B8, 0xC385, 0x0330, 0xC30D, 0xC349, 0x0374, 0x0220, 0xC21D, - 0xC259, 0x0264, 0xC2D1, 0x02EC, 0x02A8, 0xC295, 0xC781, 0x07BC, 0x07F8, 0xC7C5, 0x0770, 0xC74D, 0xC709, - 0x0734, 0x0660, 0xC65D, 0xC619, 0x0624, 0xC691, 0x06AC, 0x06E8, 0xC6D5, 0x0440, 0xC47D, 0xC439, 0x0404, - 0xC4B1, 0x048C, 0x04C8, 0xC4F5, 0xC5A1, 0x059C, 0x05D8, 0xC5E5, 0x0550, 0xC56D, 0xC529, 0x0514, 0xCF01, - 0x0F3C, 0x0F78, 0xCF45, 0x0FF0, 0xCFCD, 0xCF89, 0x0FB4, 0x0EE0, 0xCEDD, 0xCE99, 0x0EA4, 0xCE11, 0x0E2C, - 0x0E68, 0xCE55, 0x0CC0, 0xCCFD, 0xCCB9, 0x0C84, 0xCC31, 0x0C0C, 0x0C48, 0xCC75, 0xCD21, 0x0D1C, 0x0D58, - 0xCD65, 0x0DD0, 0xCDED, 0xCDA9, 0x0D94, 0x0880, 0xC8BD, 0xC8F9, 0x08C4, 0xC871, 0x084C, 0x0808, 0xC835, - 0xC961, 0x095C, 0x0918, 0xC925, 0x0990, 0xC9AD, 0xC9E9, 0x09D4, 0xCB41, 0x0B7C, 0x0B38, 0xCB05, 0x0BB0, - 0xCB8D, 0xCBC9, 0x0BF4, 0x0AA0, 0xCA9D, 0xCAD9, 0x0AE4, 0xCA51, 0x0A6C, 0x0A28, 0xCA15, 0xDE01, 0x1E3C, - 0x1E78, 0xDE45, 0x1EF0, 0xDECD, 0xDE89, 0x1EB4, 0x1FE0, 0xDFDD, 0xDF99, 0x1FA4, 0xDF11, 0x1F2C, 0x1F68, - 0xDF55, 0x1DC0, 0xDDFD, 0xDDB9, 0x1D84, 0xDD31, 0x1D0C, 0x1D48, 0xDD75, 0xDC21, 0x1C1C, 0x1C58, 0xDC65, - 0x1CD0, 0xDCED, 0xDCA9, 0x1C94, 0x1980, 0xD9BD, 0xD9F9, 0x19C4, 0xD971, 0x194C, 0x1908, 0xD935, 0xD861, - 0x185C, 0x1818, 0xD825, 0x1890, 0xD8AD, 0xD8E9, 0x18D4, 0xDA41, 0x1A7C, 0x1A38, 0xDA05, 0x1AB0, 0xDA8D, - 0xDAC9, 0x1AF4, 0x1BA0, 0xDB9D, 0xDBD9, 0x1BE4, 0xDB51, 0x1B6C, 0x1B28, 0xDB15, 0x1100, 0xD13D, 0xD179, - 0x1144, 0xD1F1, 0x11CC, 0x1188, 0xD1B5, 0xD0E1, 0x10DC, 0x1098, 0xD0A5, 0x1010, 0xD02D, 0xD069, 0x1054, - 0xD2C1, 0x12FC, 0x12B8, 0xD285, 0x1230, 0xD20D, 0xD249, 0x1274, 0x1320, 0xD31D, 0xD359, 0x1364, 0xD3D1, - 0x13EC, 0x13A8, 0xD395, 0xD681, 0x16BC, 0x16F8, 0xD6C5, 0x1670, 0xD64D, 0xD609, 0x1634, 0x1760, 0xD75D, - 0xD719, 0x1724, 0xD791, 0x17AC, 0x17E8, 0xD7D5, 0x1540, 0xD57D, 0xD539, 0x1504, 0xD5B1, 0x158C, 0x15C8, - 0xD5F5, 0xD4A1, 0x149C, 0x14D8, 0xD4E5, 0x1450, 0xD46D, 0xD429, 0x1414 - }, - new ushort[] - { - 0x0000, 0xD101, 0xE201, 0x3300, 0x8401, 0x5500, 0x6600, 0xB701, 0x4801, 0x9900, 0xAA00, 0x7B01, 0xCC00, - 0x1D01, 0x2E01, 0xFF00, 0x9002, 0x4103, 0x7203, 0xA302, 0x1403, 0xC502, 0xF602, 0x2703, 0xD803, 0x0902, - 0x3A02, 0xEB03, 0x5C02, 0x8D03, 0xBE03, 0x6F02, 0x6007, 0xB106, 0x8206, 0x5307, 0xE406, 0x3507, 0x0607, - 0xD706, 0x2806, 0xF907, 0xCA07, 0x1B06, 0xAC07, 0x7D06, 0x4E06, 0x9F07, 0xF005, 0x2104, 0x1204, 0xC305, - 0x7404, 0xA505, 0x9605, 0x4704, 0xB804, 0x6905, 0x5A05, 0x8B04, 0x3C05, 0xED04, 0xDE04, 0x0F05, 0xC00E, - 0x110F, 0x220F, 0xF30E, 0x440F, 0x950E, 0xA60E, 0x770F, 0x880F, 0x590E, 0x6A0E, 0xBB0F, 0x0C0E, 0xDD0F, - 0xEE0F, 0x3F0E, 0x500C, 0x810D, 0xB20D, 0x630C, 0xD40D, 0x050C, 0x360C, 0xE70D, 0x180D, 0xC90C, 0xFA0C, - 0x2B0D, 0x9C0C, 0x4D0D, 0x7E0D, 0xAF0C, 0xA009, 0x7108, 0x4208, 0x9309, 0x2408, 0xF509, 0xC609, 0x1708, - 0xE808, 0x3909, 0x0A09, 0xDB08, 0x6C09, 0xBD08, 0x8E08, 0x5F09, 0x300B, 0xE10A, 0xD20A, 0x030B, 0xB40A, - 0x650B, 0x560B, 0x870A, 0x780A, 0xA90B, 0x9A0B, 0x4B0A, 0xFC0B, 0x2D0A, 0x1E0A, 0xCF0B, 0xC01F, 0x111E, - 0x221E, 0xF31F, 0x441E, 0x951F, 0xA61F, 0x771E, 0x881E, 0x591F, 0x6A1F, 0xBB1E, 0x0C1F, 0xDD1E, 0xEE1E, - 0x3F1F, 0x501D, 0x811C, 0xB21C, 0x631D, 0xD41C, 0x051D, 0x361D, 0xE71C, 0x181C, 0xC91D, 0xFA1D, 0x2B1C, - 0x9C1D, 0x4D1C, 0x7E1C, 0xAF1D, 0xA018, 0x7119, 0x4219, 0x9318, 0x2419, 0xF518, 0xC618, 0x1719, 0xE819, - 0x3918, 0x0A18, 0xDB19, 0x6C18, 0xBD19, 0x8E19, 0x5F18, 0x301A, 0xE11B, 0xD21B, 0x031A, 0xB41B, 0x651A, - 0x561A, 0x871B, 0x781B, 0xA91A, 0x9A1A, 0x4B1B, 0xFC1A, 0x2D1B, 0x1E1B, 0xCF1A, 0x0011, 0xD110, 0xE210, - 0x3311, 0x8410, 0x5511, 0x6611, 0xB710, 0x4810, 0x9911, 0xAA11, 0x7B10, 0xCC11, 0x1D10, 0x2E10, 0xFF11, - 0x9013, 0x4112, 0x7212, 0xA313, 0x1412, 0xC513, 0xF613, 0x2712, 0xD812, 0x0913, 0x3A13, 0xEB12, 0x5C13, - 0x8D12, 0xBE12, 0x6F13, 0x6016, 0xB117, 0x8217, 0x5316, 0xE417, 0x3516, 0x0616, 0xD717, 0x2817, 0xF916, - 0xCA16, 0x1B17, 0xAC16, 0x7D17, 0x4E17, 0x9F16, 0xF014, 0x2115, 0x1215, 0xC314, 0x7415, 0xA514, 0x9614, - 0x4715, 0xB815, 0x6914, 0x5A14, 0x8B15, 0x3C14, 0xED15, 0xDE15, 0x0F14 - }, - new ushort[] - { - 0x0000, 0xC010, 0xC023, 0x0033, 0xC045, 0x0055, 0x0066, 0xC076, 0xC089, 0x0099, 0x00AA, 0xC0BA, 0x00CC, - 0xC0DC, 0xC0EF, 0x00FF, 0xC111, 0x0101, 0x0132, 0xC122, 0x0154, 0xC144, 0xC177, 0x0167, 0x0198, 0xC188, - 0xC1BB, 0x01AB, 0xC1DD, 0x01CD, 0x01FE, 0xC1EE, 0xC221, 0x0231, 0x0202, 0xC212, 0x0264, 0xC274, 0xC247, - 0x0257, 0x02A8, 0xC2B8, 0xC28B, 0x029B, 0xC2ED, 0x02FD, 0x02CE, 0xC2DE, 0x0330, 0xC320, 0xC313, 0x0303, - 0xC375, 0x0365, 0x0356, 0xC346, 0xC3B9, 0x03A9, 0x039A, 0xC38A, 0x03FC, 0xC3EC, 0xC3DF, 0x03CF, 0xC441, - 0x0451, 0x0462, 0xC472, 0x0404, 0xC414, 0xC427, 0x0437, 0x04C8, 0xC4D8, 0xC4EB, 0x04FB, 0xC48D, 0x049D, - 0x04AE, 0xC4BE, 0x0550, 0xC540, 0xC573, 0x0563, 0xC515, 0x0505, 0x0536, 0xC526, 0xC5D9, 0x05C9, 0x05FA, - 0xC5EA, 0x059C, 0xC58C, 0xC5BF, 0x05AF, 0x0660, 0xC670, 0xC643, 0x0653, 0xC625, 0x0635, 0x0606, 0xC616, - 0xC6E9, 0x06F9, 0x06CA, 0xC6DA, 0x06AC, 0xC6BC, 0xC68F, 0x069F, 0xC771, 0x0761, 0x0752, 0xC742, 0x0734, - 0xC724, 0xC717, 0x0707, 0x07F8, 0xC7E8, 0xC7DB, 0x07CB, 0xC7BD, 0x07AD, 0x079E, 0xC78E, 0xC881, 0x0891, - 0x08A2, 0xC8B2, 0x08C4, 0xC8D4, 0xC8E7, 0x08F7, 0x0808, 0xC818, 0xC82B, 0x083B, 0xC84D, 0x085D, 0x086E, - 0xC87E, 0x0990, 0xC980, 0xC9B3, 0x09A3, 0xC9D5, 0x09C5, 0x09F6, 0xC9E6, 0xC919, 0x0909, 0x093A, 0xC92A, - 0x095C, 0xC94C, 0xC97F, 0x096F, 0x0AA0, 0xCAB0, 0xCA83, 0x0A93, 0xCAE5, 0x0AF5, 0x0AC6, 0xCAD6, 0xCA29, - 0x0A39, 0x0A0A, 0xCA1A, 0x0A6C, 0xCA7C, 0xCA4F, 0x0A5F, 0xCBB1, 0x0BA1, 0x0B92, 0xCB82, 0x0BF4, 0xCBE4, - 0xCBD7, 0x0BC7, 0x0B38, 0xCB28, 0xCB1B, 0x0B0B, 0xCB7D, 0x0B6D, 0x0B5E, 0xCB4E, 0x0CC0, 0xCCD0, 0xCCE3, - 0x0CF3, 0xCC85, 0x0C95, 0x0CA6, 0xCCB6, 0xCC49, 0x0C59, 0x0C6A, 0xCC7A, 0x0C0C, 0xCC1C, 0xCC2F, 0x0C3F, - 0xCDD1, 0x0DC1, 0x0DF2, 0xCDE2, 0x0D94, 0xCD84, 0xCDB7, 0x0DA7, 0x0D58, 0xCD48, 0xCD7B, 0x0D6B, 0xCD1D, - 0x0D0D, 0x0D3E, 0xCD2E, 0xCEE1, 0x0EF1, 0x0EC2, 0xCED2, 0x0EA4, 0xCEB4, 0xCE87, 0x0E97, 0x0E68, 0xCE78, - 0xCE4B, 0x0E5B, 0xCE2D, 0x0E3D, 0x0E0E, 0xCE1E, 0x0FF0, 0xCFE0, 0xCFD3, 0x0FC3, 0xCFB5, 0x0FA5, 0x0F96, - 0xCF86, 0xCF79, 0x0F69, 0x0F5A, 0xCF4A, 0x0F3C, 0xCF2C, 0xCF1F, 0x0F0F - }, - new ushort[] - { - 0x0000, 0xCCC1, 0xD981, 0x1540, 0xF301, 0x3FC0, 0x2A80, 0xE641, 0xA601, 0x6AC0, 0x7F80, 0xB341, 0x5500, - 0x99C1, 0x8C81, 0x4040, 0x0C01, 0xC0C0, 0xD580, 0x1941, 0xFF00, 0x33C1, 0x2681, 0xEA40, 0xAA00, 0x66C1, - 0x7381, 0xBF40, 0x5901, 0x95C0, 0x8080, 0x4C41, 0x1802, 0xD4C3, 0xC183, 0x0D42, 0xEB03, 0x27C2, 0x3282, - 0xFE43, 0xBE03, 0x72C2, 0x6782, 0xAB43, 0x4D02, 0x81C3, 0x9483, 0x5842, 0x1403, 0xD8C2, 0xCD82, 0x0143, - 0xE702, 0x2BC3, 0x3E83, 0xF242, 0xB202, 0x7EC3, 0x6B83, 0xA742, 0x4103, 0x8DC2, 0x9882, 0x5443, 0x3004, - 0xFCC5, 0xE985, 0x2544, 0xC305, 0x0FC4, 0x1A84, 0xD645, 0x9605, 0x5AC4, 0x4F84, 0x8345, 0x6504, 0xA9C5, - 0xBC85, 0x7044, 0x3C05, 0xF0C4, 0xE584, 0x2945, 0xCF04, 0x03C5, 0x1685, 0xDA44, 0x9A04, 0x56C5, 0x4385, - 0x8F44, 0x6905, 0xA5C4, 0xB084, 0x7C45, 0x2806, 0xE4C7, 0xF187, 0x3D46, 0xDB07, 0x17C6, 0x0286, 0xCE47, - 0x8E07, 0x42C6, 0x5786, 0x9B47, 0x7D06, 0xB1C7, 0xA487, 0x6846, 0x2407, 0xE8C6, 0xFD86, 0x3147, 0xD706, - 0x1BC7, 0x0E87, 0xC246, 0x8206, 0x4EC7, 0x5B87, 0x9746, 0x7107, 0xBDC6, 0xA886, 0x6447, 0x6008, 0xACC9, - 0xB989, 0x7548, 0x9309, 0x5FC8, 0x4A88, 0x8649, 0xC609, 0x0AC8, 0x1F88, 0xD349, 0x3508, 0xF9C9, 0xEC89, - 0x2048, 0x6C09, 0xA0C8, 0xB588, 0x7949, 0x9F08, 0x53C9, 0x4689, 0x8A48, 0xCA08, 0x06C9, 0x1389, 0xDF48, - 0x3909, 0xF5C8, 0xE088, 0x2C49, 0x780A, 0xB4CB, 0xA18B, 0x6D4A, 0x8B0B, 0x47CA, 0x528A, 0x9E4B, 0xDE0B, - 0x12CA, 0x078A, 0xCB4B, 0x2D0A, 0xE1CB, 0xF48B, 0x384A, 0x740B, 0xB8CA, 0xAD8A, 0x614B, 0x870A, 0x4BCB, - 0x5E8B, 0x924A, 0xD20A, 0x1ECB, 0x0B8B, 0xC74A, 0x210B, 0xEDCA, 0xF88A, 0x344B, 0x500C, 0x9CCD, 0x898D, - 0x454C, 0xA30D, 0x6FCC, 0x7A8C, 0xB64D, 0xF60D, 0x3ACC, 0x2F8C, 0xE34D, 0x050C, 0xC9CD, 0xDC8D, 0x104C, - 0x5C0D, 0x90CC, 0x858C, 0x494D, 0xAF0C, 0x63CD, 0x768D, 0xBA4C, 0xFA0C, 0x36CD, 0x238D, 0xEF4C, 0x090D, - 0xC5CC, 0xD08C, 0x1C4D, 0x480E, 0x84CF, 0x918F, 0x5D4E, 0xBB0F, 0x77CE, 0x628E, 0xAE4F, 0xEE0F, 0x22CE, - 0x378E, 0xFB4F, 0x1D0E, 0xD1CF, 0xC48F, 0x084E, 0x440F, 0x88CE, 0x9D8E, 0x514F, 0xB70E, 0x7BCF, 0x6E8F, - 0xA24E, 0xE20E, 0x2ECF, 0x3B8F, 0xF74E, 0x110F, 0xDDCE, 0xC88E, 0x044F - } - }; - - /// Initializes an instance of the CRC16 with IBM polynomial and seed. - /// - public CRC16IBMContext() : base(CRC16_IBM_POLY, CRC16_IBM_SEED, _ibmCrc16Table, false) {} - - /// Gets the hash of a file - /// File path. - public static byte[] File(string filename) + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, + 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, + 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, + 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, + 0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, + 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, + 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, + 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, + 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, + 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0, + 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, + 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41, + 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, + 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, + 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, + 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, + 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, + 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, + 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, + 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 + }, + new ushort[] { - File(filename, out byte[] hash); - - return hash; + 0x0000, 0x9001, 0x6001, 0xF000, 0xC002, 0x5003, 0xA003, 0x3002, 0xC007, 0x5006, 0xA006, 0x3007, 0x0005, + 0x9004, 0x6004, 0xF005, 0xC00D, 0x500C, 0xA00C, 0x300D, 0x000F, 0x900E, 0x600E, 0xF00F, 0x000A, 0x900B, + 0x600B, 0xF00A, 0xC008, 0x5009, 0xA009, 0x3008, 0xC019, 0x5018, 0xA018, 0x3019, 0x001B, 0x901A, 0x601A, + 0xF01B, 0x001E, 0x901F, 0x601F, 0xF01E, 0xC01C, 0x501D, 0xA01D, 0x301C, 0x0014, 0x9015, 0x6015, 0xF014, + 0xC016, 0x5017, 0xA017, 0x3016, 0xC013, 0x5012, 0xA012, 0x3013, 0x0011, 0x9010, 0x6010, 0xF011, 0xC031, + 0x5030, 0xA030, 0x3031, 0x0033, 0x9032, 0x6032, 0xF033, 0x0036, 0x9037, 0x6037, 0xF036, 0xC034, 0x5035, + 0xA035, 0x3034, 0x003C, 0x903D, 0x603D, 0xF03C, 0xC03E, 0x503F, 0xA03F, 0x303E, 0xC03B, 0x503A, 0xA03A, + 0x303B, 0x0039, 0x9038, 0x6038, 0xF039, 0x0028, 0x9029, 0x6029, 0xF028, 0xC02A, 0x502B, 0xA02B, 0x302A, + 0xC02F, 0x502E, 0xA02E, 0x302F, 0x002D, 0x902C, 0x602C, 0xF02D, 0xC025, 0x5024, 0xA024, 0x3025, 0x0027, + 0x9026, 0x6026, 0xF027, 0x0022, 0x9023, 0x6023, 0xF022, 0xC020, 0x5021, 0xA021, 0x3020, 0xC061, 0x5060, + 0xA060, 0x3061, 0x0063, 0x9062, 0x6062, 0xF063, 0x0066, 0x9067, 0x6067, 0xF066, 0xC064, 0x5065, 0xA065, + 0x3064, 0x006C, 0x906D, 0x606D, 0xF06C, 0xC06E, 0x506F, 0xA06F, 0x306E, 0xC06B, 0x506A, 0xA06A, 0x306B, + 0x0069, 0x9068, 0x6068, 0xF069, 0x0078, 0x9079, 0x6079, 0xF078, 0xC07A, 0x507B, 0xA07B, 0x307A, 0xC07F, + 0x507E, 0xA07E, 0x307F, 0x007D, 0x907C, 0x607C, 0xF07D, 0xC075, 0x5074, 0xA074, 0x3075, 0x0077, 0x9076, + 0x6076, 0xF077, 0x0072, 0x9073, 0x6073, 0xF072, 0xC070, 0x5071, 0xA071, 0x3070, 0x0050, 0x9051, 0x6051, + 0xF050, 0xC052, 0x5053, 0xA053, 0x3052, 0xC057, 0x5056, 0xA056, 0x3057, 0x0055, 0x9054, 0x6054, 0xF055, + 0xC05D, 0x505C, 0xA05C, 0x305D, 0x005F, 0x905E, 0x605E, 0xF05F, 0x005A, 0x905B, 0x605B, 0xF05A, 0xC058, + 0x5059, 0xA059, 0x3058, 0xC049, 0x5048, 0xA048, 0x3049, 0x004B, 0x904A, 0x604A, 0xF04B, 0x004E, 0x904F, + 0x604F, 0xF04E, 0xC04C, 0x504D, 0xA04D, 0x304C, 0x0044, 0x9045, 0x6045, 0xF044, 0xC046, 0x5047, 0xA047, + 0x3046, 0xC043, 0x5042, 0xA042, 0x3043, 0x0041, 0x9040, 0x6040, 0xF041 + }, + new ushort[] + { + 0x0000, 0xC051, 0xC0A1, 0x00F0, 0xC141, 0x0110, 0x01E0, 0xC1B1, 0xC281, 0x02D0, 0x0220, 0xC271, 0x03C0, + 0xC391, 0xC361, 0x0330, 0xC501, 0x0550, 0x05A0, 0xC5F1, 0x0440, 0xC411, 0xC4E1, 0x04B0, 0x0780, 0xC7D1, + 0xC721, 0x0770, 0xC6C1, 0x0690, 0x0660, 0xC631, 0xCA01, 0x0A50, 0x0AA0, 0xCAF1, 0x0B40, 0xCB11, 0xCBE1, + 0x0BB0, 0x0880, 0xC8D1, 0xC821, 0x0870, 0xC9C1, 0x0990, 0x0960, 0xC931, 0x0F00, 0xCF51, 0xCFA1, 0x0FF0, + 0xCE41, 0x0E10, 0x0EE0, 0xCEB1, 0xCD81, 0x0DD0, 0x0D20, 0xCD71, 0x0CC0, 0xCC91, 0xCC61, 0x0C30, 0xD401, + 0x1450, 0x14A0, 0xD4F1, 0x1540, 0xD511, 0xD5E1, 0x15B0, 0x1680, 0xD6D1, 0xD621, 0x1670, 0xD7C1, 0x1790, + 0x1760, 0xD731, 0x1100, 0xD151, 0xD1A1, 0x11F0, 0xD041, 0x1010, 0x10E0, 0xD0B1, 0xD381, 0x13D0, 0x1320, + 0xD371, 0x12C0, 0xD291, 0xD261, 0x1230, 0x1E00, 0xDE51, 0xDEA1, 0x1EF0, 0xDF41, 0x1F10, 0x1FE0, 0xDFB1, + 0xDC81, 0x1CD0, 0x1C20, 0xDC71, 0x1DC0, 0xDD91, 0xDD61, 0x1D30, 0xDB01, 0x1B50, 0x1BA0, 0xDBF1, 0x1A40, + 0xDA11, 0xDAE1, 0x1AB0, 0x1980, 0xD9D1, 0xD921, 0x1970, 0xD8C1, 0x1890, 0x1860, 0xD831, 0xE801, 0x2850, + 0x28A0, 0xE8F1, 0x2940, 0xE911, 0xE9E1, 0x29B0, 0x2A80, 0xEAD1, 0xEA21, 0x2A70, 0xEBC1, 0x2B90, 0x2B60, + 0xEB31, 0x2D00, 0xED51, 0xEDA1, 0x2DF0, 0xEC41, 0x2C10, 0x2CE0, 0xECB1, 0xEF81, 0x2FD0, 0x2F20, 0xEF71, + 0x2EC0, 0xEE91, 0xEE61, 0x2E30, 0x2200, 0xE251, 0xE2A1, 0x22F0, 0xE341, 0x2310, 0x23E0, 0xE3B1, 0xE081, + 0x20D0, 0x2020, 0xE071, 0x21C0, 0xE191, 0xE161, 0x2130, 0xE701, 0x2750, 0x27A0, 0xE7F1, 0x2640, 0xE611, + 0xE6E1, 0x26B0, 0x2580, 0xE5D1, 0xE521, 0x2570, 0xE4C1, 0x2490, 0x2460, 0xE431, 0x3C00, 0xFC51, 0xFCA1, + 0x3CF0, 0xFD41, 0x3D10, 0x3DE0, 0xFDB1, 0xFE81, 0x3ED0, 0x3E20, 0xFE71, 0x3FC0, 0xFF91, 0xFF61, 0x3F30, + 0xF901, 0x3950, 0x39A0, 0xF9F1, 0x3840, 0xF811, 0xF8E1, 0x38B0, 0x3B80, 0xFBD1, 0xFB21, 0x3B70, 0xFAC1, + 0x3A90, 0x3A60, 0xFA31, 0xF601, 0x3650, 0x36A0, 0xF6F1, 0x3740, 0xF711, 0xF7E1, 0x37B0, 0x3480, 0xF4D1, + 0xF421, 0x3470, 0xF5C1, 0x3590, 0x3560, 0xF531, 0x3300, 0xF351, 0xF3A1, 0x33F0, 0xF241, 0x3210, 0x32E0, + 0xF2B1, 0xF181, 0x31D0, 0x3120, 0xF171, 0x30C0, 0xF091, 0xF061, 0x3030 + }, + new ushort[] + { + 0x0000, 0xFC01, 0xB801, 0x4400, 0x3001, 0xCC00, 0x8800, 0x7401, 0x6002, 0x9C03, 0xD803, 0x2402, 0x5003, + 0xAC02, 0xE802, 0x1403, 0xC004, 0x3C05, 0x7805, 0x8404, 0xF005, 0x0C04, 0x4804, 0xB405, 0xA006, 0x5C07, + 0x1807, 0xE406, 0x9007, 0x6C06, 0x2806, 0xD407, 0xC00B, 0x3C0A, 0x780A, 0x840B, 0xF00A, 0x0C0B, 0x480B, + 0xB40A, 0xA009, 0x5C08, 0x1808, 0xE409, 0x9008, 0x6C09, 0x2809, 0xD408, 0x000F, 0xFC0E, 0xB80E, 0x440F, + 0x300E, 0xCC0F, 0x880F, 0x740E, 0x600D, 0x9C0C, 0xD80C, 0x240D, 0x500C, 0xAC0D, 0xE80D, 0x140C, 0xC015, + 0x3C14, 0x7814, 0x8415, 0xF014, 0x0C15, 0x4815, 0xB414, 0xA017, 0x5C16, 0x1816, 0xE417, 0x9016, 0x6C17, + 0x2817, 0xD416, 0x0011, 0xFC10, 0xB810, 0x4411, 0x3010, 0xCC11, 0x8811, 0x7410, 0x6013, 0x9C12, 0xD812, + 0x2413, 0x5012, 0xAC13, 0xE813, 0x1412, 0x001E, 0xFC1F, 0xB81F, 0x441E, 0x301F, 0xCC1E, 0x881E, 0x741F, + 0x601C, 0x9C1D, 0xD81D, 0x241C, 0x501D, 0xAC1C, 0xE81C, 0x141D, 0xC01A, 0x3C1B, 0x781B, 0x841A, 0xF01B, + 0x0C1A, 0x481A, 0xB41B, 0xA018, 0x5C19, 0x1819, 0xE418, 0x9019, 0x6C18, 0x2818, 0xD419, 0xC029, 0x3C28, + 0x7828, 0x8429, 0xF028, 0x0C29, 0x4829, 0xB428, 0xA02B, 0x5C2A, 0x182A, 0xE42B, 0x902A, 0x6C2B, 0x282B, + 0xD42A, 0x002D, 0xFC2C, 0xB82C, 0x442D, 0x302C, 0xCC2D, 0x882D, 0x742C, 0x602F, 0x9C2E, 0xD82E, 0x242F, + 0x502E, 0xAC2F, 0xE82F, 0x142E, 0x0022, 0xFC23, 0xB823, 0x4422, 0x3023, 0xCC22, 0x8822, 0x7423, 0x6020, + 0x9C21, 0xD821, 0x2420, 0x5021, 0xAC20, 0xE820, 0x1421, 0xC026, 0x3C27, 0x7827, 0x8426, 0xF027, 0x0C26, + 0x4826, 0xB427, 0xA024, 0x5C25, 0x1825, 0xE424, 0x9025, 0x6C24, 0x2824, 0xD425, 0x003C, 0xFC3D, 0xB83D, + 0x443C, 0x303D, 0xCC3C, 0x883C, 0x743D, 0x603E, 0x9C3F, 0xD83F, 0x243E, 0x503F, 0xAC3E, 0xE83E, 0x143F, + 0xC038, 0x3C39, 0x7839, 0x8438, 0xF039, 0x0C38, 0x4838, 0xB439, 0xA03A, 0x5C3B, 0x183B, 0xE43A, 0x903B, + 0x6C3A, 0x283A, 0xD43B, 0xC037, 0x3C36, 0x7836, 0x8437, 0xF036, 0x0C37, 0x4837, 0xB436, 0xA035, 0x5C34, + 0x1834, 0xE435, 0x9034, 0x6C35, 0x2835, 0xD434, 0x0033, 0xFC32, 0xB832, 0x4433, 0x3032, 0xCC33, 0x8833, + 0x7432, 0x6031, 0x9C30, 0xD830, 0x2431, 0x5030, 0xAC31, 0xE831, 0x1430 + }, + new ushort[] + { + 0x0000, 0xC03D, 0xC079, 0x0044, 0xC0F1, 0x00CC, 0x0088, 0xC0B5, 0xC1E1, 0x01DC, 0x0198, 0xC1A5, 0x0110, + 0xC12D, 0xC169, 0x0154, 0xC3C1, 0x03FC, 0x03B8, 0xC385, 0x0330, 0xC30D, 0xC349, 0x0374, 0x0220, 0xC21D, + 0xC259, 0x0264, 0xC2D1, 0x02EC, 0x02A8, 0xC295, 0xC781, 0x07BC, 0x07F8, 0xC7C5, 0x0770, 0xC74D, 0xC709, + 0x0734, 0x0660, 0xC65D, 0xC619, 0x0624, 0xC691, 0x06AC, 0x06E8, 0xC6D5, 0x0440, 0xC47D, 0xC439, 0x0404, + 0xC4B1, 0x048C, 0x04C8, 0xC4F5, 0xC5A1, 0x059C, 0x05D8, 0xC5E5, 0x0550, 0xC56D, 0xC529, 0x0514, 0xCF01, + 0x0F3C, 0x0F78, 0xCF45, 0x0FF0, 0xCFCD, 0xCF89, 0x0FB4, 0x0EE0, 0xCEDD, 0xCE99, 0x0EA4, 0xCE11, 0x0E2C, + 0x0E68, 0xCE55, 0x0CC0, 0xCCFD, 0xCCB9, 0x0C84, 0xCC31, 0x0C0C, 0x0C48, 0xCC75, 0xCD21, 0x0D1C, 0x0D58, + 0xCD65, 0x0DD0, 0xCDED, 0xCDA9, 0x0D94, 0x0880, 0xC8BD, 0xC8F9, 0x08C4, 0xC871, 0x084C, 0x0808, 0xC835, + 0xC961, 0x095C, 0x0918, 0xC925, 0x0990, 0xC9AD, 0xC9E9, 0x09D4, 0xCB41, 0x0B7C, 0x0B38, 0xCB05, 0x0BB0, + 0xCB8D, 0xCBC9, 0x0BF4, 0x0AA0, 0xCA9D, 0xCAD9, 0x0AE4, 0xCA51, 0x0A6C, 0x0A28, 0xCA15, 0xDE01, 0x1E3C, + 0x1E78, 0xDE45, 0x1EF0, 0xDECD, 0xDE89, 0x1EB4, 0x1FE0, 0xDFDD, 0xDF99, 0x1FA4, 0xDF11, 0x1F2C, 0x1F68, + 0xDF55, 0x1DC0, 0xDDFD, 0xDDB9, 0x1D84, 0xDD31, 0x1D0C, 0x1D48, 0xDD75, 0xDC21, 0x1C1C, 0x1C58, 0xDC65, + 0x1CD0, 0xDCED, 0xDCA9, 0x1C94, 0x1980, 0xD9BD, 0xD9F9, 0x19C4, 0xD971, 0x194C, 0x1908, 0xD935, 0xD861, + 0x185C, 0x1818, 0xD825, 0x1890, 0xD8AD, 0xD8E9, 0x18D4, 0xDA41, 0x1A7C, 0x1A38, 0xDA05, 0x1AB0, 0xDA8D, + 0xDAC9, 0x1AF4, 0x1BA0, 0xDB9D, 0xDBD9, 0x1BE4, 0xDB51, 0x1B6C, 0x1B28, 0xDB15, 0x1100, 0xD13D, 0xD179, + 0x1144, 0xD1F1, 0x11CC, 0x1188, 0xD1B5, 0xD0E1, 0x10DC, 0x1098, 0xD0A5, 0x1010, 0xD02D, 0xD069, 0x1054, + 0xD2C1, 0x12FC, 0x12B8, 0xD285, 0x1230, 0xD20D, 0xD249, 0x1274, 0x1320, 0xD31D, 0xD359, 0x1364, 0xD3D1, + 0x13EC, 0x13A8, 0xD395, 0xD681, 0x16BC, 0x16F8, 0xD6C5, 0x1670, 0xD64D, 0xD609, 0x1634, 0x1760, 0xD75D, + 0xD719, 0x1724, 0xD791, 0x17AC, 0x17E8, 0xD7D5, 0x1540, 0xD57D, 0xD539, 0x1504, 0xD5B1, 0x158C, 0x15C8, + 0xD5F5, 0xD4A1, 0x149C, 0x14D8, 0xD4E5, 0x1450, 0xD46D, 0xD429, 0x1414 + }, + new ushort[] + { + 0x0000, 0xD101, 0xE201, 0x3300, 0x8401, 0x5500, 0x6600, 0xB701, 0x4801, 0x9900, 0xAA00, 0x7B01, 0xCC00, + 0x1D01, 0x2E01, 0xFF00, 0x9002, 0x4103, 0x7203, 0xA302, 0x1403, 0xC502, 0xF602, 0x2703, 0xD803, 0x0902, + 0x3A02, 0xEB03, 0x5C02, 0x8D03, 0xBE03, 0x6F02, 0x6007, 0xB106, 0x8206, 0x5307, 0xE406, 0x3507, 0x0607, + 0xD706, 0x2806, 0xF907, 0xCA07, 0x1B06, 0xAC07, 0x7D06, 0x4E06, 0x9F07, 0xF005, 0x2104, 0x1204, 0xC305, + 0x7404, 0xA505, 0x9605, 0x4704, 0xB804, 0x6905, 0x5A05, 0x8B04, 0x3C05, 0xED04, 0xDE04, 0x0F05, 0xC00E, + 0x110F, 0x220F, 0xF30E, 0x440F, 0x950E, 0xA60E, 0x770F, 0x880F, 0x590E, 0x6A0E, 0xBB0F, 0x0C0E, 0xDD0F, + 0xEE0F, 0x3F0E, 0x500C, 0x810D, 0xB20D, 0x630C, 0xD40D, 0x050C, 0x360C, 0xE70D, 0x180D, 0xC90C, 0xFA0C, + 0x2B0D, 0x9C0C, 0x4D0D, 0x7E0D, 0xAF0C, 0xA009, 0x7108, 0x4208, 0x9309, 0x2408, 0xF509, 0xC609, 0x1708, + 0xE808, 0x3909, 0x0A09, 0xDB08, 0x6C09, 0xBD08, 0x8E08, 0x5F09, 0x300B, 0xE10A, 0xD20A, 0x030B, 0xB40A, + 0x650B, 0x560B, 0x870A, 0x780A, 0xA90B, 0x9A0B, 0x4B0A, 0xFC0B, 0x2D0A, 0x1E0A, 0xCF0B, 0xC01F, 0x111E, + 0x221E, 0xF31F, 0x441E, 0x951F, 0xA61F, 0x771E, 0x881E, 0x591F, 0x6A1F, 0xBB1E, 0x0C1F, 0xDD1E, 0xEE1E, + 0x3F1F, 0x501D, 0x811C, 0xB21C, 0x631D, 0xD41C, 0x051D, 0x361D, 0xE71C, 0x181C, 0xC91D, 0xFA1D, 0x2B1C, + 0x9C1D, 0x4D1C, 0x7E1C, 0xAF1D, 0xA018, 0x7119, 0x4219, 0x9318, 0x2419, 0xF518, 0xC618, 0x1719, 0xE819, + 0x3918, 0x0A18, 0xDB19, 0x6C18, 0xBD19, 0x8E19, 0x5F18, 0x301A, 0xE11B, 0xD21B, 0x031A, 0xB41B, 0x651A, + 0x561A, 0x871B, 0x781B, 0xA91A, 0x9A1A, 0x4B1B, 0xFC1A, 0x2D1B, 0x1E1B, 0xCF1A, 0x0011, 0xD110, 0xE210, + 0x3311, 0x8410, 0x5511, 0x6611, 0xB710, 0x4810, 0x9911, 0xAA11, 0x7B10, 0xCC11, 0x1D10, 0x2E10, 0xFF11, + 0x9013, 0x4112, 0x7212, 0xA313, 0x1412, 0xC513, 0xF613, 0x2712, 0xD812, 0x0913, 0x3A13, 0xEB12, 0x5C13, + 0x8D12, 0xBE12, 0x6F13, 0x6016, 0xB117, 0x8217, 0x5316, 0xE417, 0x3516, 0x0616, 0xD717, 0x2817, 0xF916, + 0xCA16, 0x1B17, 0xAC16, 0x7D17, 0x4E17, 0x9F16, 0xF014, 0x2115, 0x1215, 0xC314, 0x7415, 0xA514, 0x9614, + 0x4715, 0xB815, 0x6914, 0x5A14, 0x8B15, 0x3C14, 0xED15, 0xDE15, 0x0F14 + }, + new ushort[] + { + 0x0000, 0xC010, 0xC023, 0x0033, 0xC045, 0x0055, 0x0066, 0xC076, 0xC089, 0x0099, 0x00AA, 0xC0BA, 0x00CC, + 0xC0DC, 0xC0EF, 0x00FF, 0xC111, 0x0101, 0x0132, 0xC122, 0x0154, 0xC144, 0xC177, 0x0167, 0x0198, 0xC188, + 0xC1BB, 0x01AB, 0xC1DD, 0x01CD, 0x01FE, 0xC1EE, 0xC221, 0x0231, 0x0202, 0xC212, 0x0264, 0xC274, 0xC247, + 0x0257, 0x02A8, 0xC2B8, 0xC28B, 0x029B, 0xC2ED, 0x02FD, 0x02CE, 0xC2DE, 0x0330, 0xC320, 0xC313, 0x0303, + 0xC375, 0x0365, 0x0356, 0xC346, 0xC3B9, 0x03A9, 0x039A, 0xC38A, 0x03FC, 0xC3EC, 0xC3DF, 0x03CF, 0xC441, + 0x0451, 0x0462, 0xC472, 0x0404, 0xC414, 0xC427, 0x0437, 0x04C8, 0xC4D8, 0xC4EB, 0x04FB, 0xC48D, 0x049D, + 0x04AE, 0xC4BE, 0x0550, 0xC540, 0xC573, 0x0563, 0xC515, 0x0505, 0x0536, 0xC526, 0xC5D9, 0x05C9, 0x05FA, + 0xC5EA, 0x059C, 0xC58C, 0xC5BF, 0x05AF, 0x0660, 0xC670, 0xC643, 0x0653, 0xC625, 0x0635, 0x0606, 0xC616, + 0xC6E9, 0x06F9, 0x06CA, 0xC6DA, 0x06AC, 0xC6BC, 0xC68F, 0x069F, 0xC771, 0x0761, 0x0752, 0xC742, 0x0734, + 0xC724, 0xC717, 0x0707, 0x07F8, 0xC7E8, 0xC7DB, 0x07CB, 0xC7BD, 0x07AD, 0x079E, 0xC78E, 0xC881, 0x0891, + 0x08A2, 0xC8B2, 0x08C4, 0xC8D4, 0xC8E7, 0x08F7, 0x0808, 0xC818, 0xC82B, 0x083B, 0xC84D, 0x085D, 0x086E, + 0xC87E, 0x0990, 0xC980, 0xC9B3, 0x09A3, 0xC9D5, 0x09C5, 0x09F6, 0xC9E6, 0xC919, 0x0909, 0x093A, 0xC92A, + 0x095C, 0xC94C, 0xC97F, 0x096F, 0x0AA0, 0xCAB0, 0xCA83, 0x0A93, 0xCAE5, 0x0AF5, 0x0AC6, 0xCAD6, 0xCA29, + 0x0A39, 0x0A0A, 0xCA1A, 0x0A6C, 0xCA7C, 0xCA4F, 0x0A5F, 0xCBB1, 0x0BA1, 0x0B92, 0xCB82, 0x0BF4, 0xCBE4, + 0xCBD7, 0x0BC7, 0x0B38, 0xCB28, 0xCB1B, 0x0B0B, 0xCB7D, 0x0B6D, 0x0B5E, 0xCB4E, 0x0CC0, 0xCCD0, 0xCCE3, + 0x0CF3, 0xCC85, 0x0C95, 0x0CA6, 0xCCB6, 0xCC49, 0x0C59, 0x0C6A, 0xCC7A, 0x0C0C, 0xCC1C, 0xCC2F, 0x0C3F, + 0xCDD1, 0x0DC1, 0x0DF2, 0xCDE2, 0x0D94, 0xCD84, 0xCDB7, 0x0DA7, 0x0D58, 0xCD48, 0xCD7B, 0x0D6B, 0xCD1D, + 0x0D0D, 0x0D3E, 0xCD2E, 0xCEE1, 0x0EF1, 0x0EC2, 0xCED2, 0x0EA4, 0xCEB4, 0xCE87, 0x0E97, 0x0E68, 0xCE78, + 0xCE4B, 0x0E5B, 0xCE2D, 0x0E3D, 0x0E0E, 0xCE1E, 0x0FF0, 0xCFE0, 0xCFD3, 0x0FC3, 0xCFB5, 0x0FA5, 0x0F96, + 0xCF86, 0xCF79, 0x0F69, 0x0F5A, 0xCF4A, 0x0F3C, 0xCF2C, 0xCF1F, 0x0F0F + }, + new ushort[] + { + 0x0000, 0xCCC1, 0xD981, 0x1540, 0xF301, 0x3FC0, 0x2A80, 0xE641, 0xA601, 0x6AC0, 0x7F80, 0xB341, 0x5500, + 0x99C1, 0x8C81, 0x4040, 0x0C01, 0xC0C0, 0xD580, 0x1941, 0xFF00, 0x33C1, 0x2681, 0xEA40, 0xAA00, 0x66C1, + 0x7381, 0xBF40, 0x5901, 0x95C0, 0x8080, 0x4C41, 0x1802, 0xD4C3, 0xC183, 0x0D42, 0xEB03, 0x27C2, 0x3282, + 0xFE43, 0xBE03, 0x72C2, 0x6782, 0xAB43, 0x4D02, 0x81C3, 0x9483, 0x5842, 0x1403, 0xD8C2, 0xCD82, 0x0143, + 0xE702, 0x2BC3, 0x3E83, 0xF242, 0xB202, 0x7EC3, 0x6B83, 0xA742, 0x4103, 0x8DC2, 0x9882, 0x5443, 0x3004, + 0xFCC5, 0xE985, 0x2544, 0xC305, 0x0FC4, 0x1A84, 0xD645, 0x9605, 0x5AC4, 0x4F84, 0x8345, 0x6504, 0xA9C5, + 0xBC85, 0x7044, 0x3C05, 0xF0C4, 0xE584, 0x2945, 0xCF04, 0x03C5, 0x1685, 0xDA44, 0x9A04, 0x56C5, 0x4385, + 0x8F44, 0x6905, 0xA5C4, 0xB084, 0x7C45, 0x2806, 0xE4C7, 0xF187, 0x3D46, 0xDB07, 0x17C6, 0x0286, 0xCE47, + 0x8E07, 0x42C6, 0x5786, 0x9B47, 0x7D06, 0xB1C7, 0xA487, 0x6846, 0x2407, 0xE8C6, 0xFD86, 0x3147, 0xD706, + 0x1BC7, 0x0E87, 0xC246, 0x8206, 0x4EC7, 0x5B87, 0x9746, 0x7107, 0xBDC6, 0xA886, 0x6447, 0x6008, 0xACC9, + 0xB989, 0x7548, 0x9309, 0x5FC8, 0x4A88, 0x8649, 0xC609, 0x0AC8, 0x1F88, 0xD349, 0x3508, 0xF9C9, 0xEC89, + 0x2048, 0x6C09, 0xA0C8, 0xB588, 0x7949, 0x9F08, 0x53C9, 0x4689, 0x8A48, 0xCA08, 0x06C9, 0x1389, 0xDF48, + 0x3909, 0xF5C8, 0xE088, 0x2C49, 0x780A, 0xB4CB, 0xA18B, 0x6D4A, 0x8B0B, 0x47CA, 0x528A, 0x9E4B, 0xDE0B, + 0x12CA, 0x078A, 0xCB4B, 0x2D0A, 0xE1CB, 0xF48B, 0x384A, 0x740B, 0xB8CA, 0xAD8A, 0x614B, 0x870A, 0x4BCB, + 0x5E8B, 0x924A, 0xD20A, 0x1ECB, 0x0B8B, 0xC74A, 0x210B, 0xEDCA, 0xF88A, 0x344B, 0x500C, 0x9CCD, 0x898D, + 0x454C, 0xA30D, 0x6FCC, 0x7A8C, 0xB64D, 0xF60D, 0x3ACC, 0x2F8C, 0xE34D, 0x050C, 0xC9CD, 0xDC8D, 0x104C, + 0x5C0D, 0x90CC, 0x858C, 0x494D, 0xAF0C, 0x63CD, 0x768D, 0xBA4C, 0xFA0C, 0x36CD, 0x238D, 0xEF4C, 0x090D, + 0xC5CC, 0xD08C, 0x1C4D, 0x480E, 0x84CF, 0x918F, 0x5D4E, 0xBB0F, 0x77CE, 0x628E, 0xAE4F, 0xEE0F, 0x22CE, + 0x378E, 0xFB4F, 0x1D0E, 0xD1CF, 0xC48F, 0x084E, 0x440F, 0x88CE, 0x9D8E, 0x514F, 0xB70E, 0x7BCF, 0x6E8F, + 0xA24E, 0xE20E, 0x2ECF, 0x3B8F, 0xF74E, 0x110F, 0xDDCE, 0xC88E, 0x044F } + }; - /// Gets the hash of a file in hexadecimal and as a byte array. - /// File path. - /// Byte array of the hash value. - public static string File(string filename, out byte[] hash) => - File(filename, out hash, CRC16_IBM_POLY, CRC16_IBM_SEED, _ibmCrc16Table, false); + /// Initializes an instance of the CRC16 with IBM polynomial and seed. + /// + public CRC16IBMContext() : base(CRC16_IBM_POLY, CRC16_IBM_SEED, _ibmCrc16Table, false) {} - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Length of the data buffer to hash. - /// Byte array of the hash value. - public static string Data(byte[] data, uint len, out byte[] hash) => - Data(data, len, out hash, CRC16_IBM_POLY, CRC16_IBM_SEED, _ibmCrc16Table, false); + /// Gets the hash of a file + /// File path. + public static byte[] File(string filename) + { + File(filename, out byte[] hash); - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Byte array of the hash value. - public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); + return hash; } + + /// Gets the hash of a file in hexadecimal and as a byte array. + /// File path. + /// Byte array of the hash value. + public static string File(string filename, out byte[] hash) => + File(filename, out hash, CRC16_IBM_POLY, CRC16_IBM_SEED, _ibmCrc16Table, false); + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Length of the data buffer to hash. + /// Byte array of the hash value. + public static string Data(byte[] data, uint len, out byte[] hash) => + Data(data, len, out hash, CRC16_IBM_POLY, CRC16_IBM_SEED, _ibmCrc16Table, false); + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Byte array of the hash value. + public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); } \ No newline at end of file diff --git a/Aaru6.Checksums/CRC32/arm_simd.cs b/Aaru6.Checksums/CRC32/arm_simd.cs index 88f5214..d4c1c79 100644 --- a/Aaru6.Checksums/CRC32/arm_simd.cs +++ b/Aaru6.Checksums/CRC32/arm_simd.cs @@ -48,92 +48,91 @@ using System; using System.Runtime.Intrinsics.Arm; -namespace Aaru6.Checksums.CRC32 +namespace Aaru6.Checksums.CRC32; + +internal static class ArmSimd { - internal static class ArmSimd + internal static uint Step64(byte[] buf, long len, uint crc) { - internal static uint Step64(byte[] buf, long len, uint crc) + uint c = crc; + + int bufPos = 0; + + while(len >= 64) { - uint c = crc; - - int bufPos = 0; - - while(len >= 64) - { - c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); - bufPos += 8; - c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); - bufPos += 8; - c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); - bufPos += 8; - c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); - bufPos += 8; - c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); - bufPos += 8; - c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); - bufPos += 8; - c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); - bufPos += 8; - c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); - bufPos += 8; - len -= 64; - } - - while(len >= 8) - { - c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); - bufPos += 8; - len -= 8; - } - - while(len-- > 0) - { - c = Crc32.ComputeCrc32(c, buf[bufPos++]); - } - - return c; + c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); + bufPos += 8; + c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); + bufPos += 8; + c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); + bufPos += 8; + c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); + bufPos += 8; + c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); + bufPos += 8; + c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); + bufPos += 8; + c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); + bufPos += 8; + c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); + bufPos += 8; + len -= 64; } - internal static uint Step32(byte[] buf, long len, uint crc) + while(len >= 8) { - uint c = crc; - - int bufPos = 0; - - while(len >= 32) - { - c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); - bufPos += 4; - c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); - bufPos += 4; - c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); - bufPos += 4; - c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); - bufPos += 4; - c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); - bufPos += 4; - c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); - bufPos += 4; - c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); - bufPos += 4; - c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); - bufPos += 4; - len -= 32; - } - - while(len >= 4) - { - c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); - bufPos += 4; - len -= 4; - } - - while(len-- > 0) - { - c = Crc32.ComputeCrc32(c, buf[bufPos++]); - } - - return c; + c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos)); + bufPos += 8; + len -= 8; } + + while(len-- > 0) + { + c = Crc32.ComputeCrc32(c, buf[bufPos++]); + } + + return c; + } + + internal static uint Step32(byte[] buf, long len, uint crc) + { + uint c = crc; + + int bufPos = 0; + + while(len >= 32) + { + c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); + bufPos += 4; + c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); + bufPos += 4; + c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); + bufPos += 4; + c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); + bufPos += 4; + c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); + bufPos += 4; + c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); + bufPos += 4; + c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); + bufPos += 4; + c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); + bufPos += 4; + len -= 32; + } + + while(len >= 4) + { + c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos)); + bufPos += 4; + len -= 4; + } + + while(len-- > 0) + { + c = Crc32.ComputeCrc32(c, buf[bufPos++]); + } + + return c; } } \ No newline at end of file diff --git a/Aaru6.Checksums/CRC32/clmul.cs b/Aaru6.Checksums/CRC32/clmul.cs index 0ad5641..71f30c5 100644 --- a/Aaru6.Checksums/CRC32/clmul.cs +++ b/Aaru6.Checksums/CRC32/clmul.cs @@ -52,199 +52,192 @@ using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace Aaru6.Checksums.CRC32 +namespace Aaru6.Checksums.CRC32; + +internal static class Clmul { - internal static class Clmul + static readonly uint[] _crcK = { - static readonly uint[] _crcK = + 0xccaa009e, 0x00000000, /* rk1 */ 0x751997d0, 0x00000001, /* rk2 */ 0xccaa009e, 0x00000000, /* rk5 */ + 0x63cd6124, 0x00000001, /* rk6 */ 0xf7011640, 0x00000001, /* rk7 */ 0xdb710640, 0x00000001 /* rk8 */ + }; + + static readonly Vector128[] _pshufbShfTable = + { + Vector128.Create(0x84838281, 0x88878685, 0x8c8b8a89, 0x008f8e8d), /* shl 15 (16 - 1)/shr1 */ + Vector128.Create(0x85848382, 0x89888786, 0x8d8c8b8a, 0x01008f8e), /* shl 14 (16 - 3)/shr2 */ + Vector128.Create(0x86858483, 0x8a898887, 0x8e8d8c8b, 0x0201008f), /* shl 13 (16 - 4)/shr3 */ + Vector128.Create(0x87868584, 0x8b8a8988, 0x8f8e8d8c, 0x03020100), /* shl 12 (16 - 4)/shr4 */ + Vector128.Create(0x88878685, 0x8c8b8a89, 0x008f8e8d, 0x04030201), /* shl 11 (16 - 5)/shr5 */ + Vector128.Create(0x89888786, 0x8d8c8b8a, 0x01008f8e, 0x05040302), /* shl 10 (16 - 6)/shr6 */ + Vector128.Create(0x8a898887, 0x8e8d8c8b, 0x0201008f, 0x06050403), /* shl 9 (16 - 7)/shr7 */ + Vector128.Create(0x8b8a8988, 0x8f8e8d8c, 0x03020100, 0x07060504), /* shl 8 (16 - 8)/shr8 */ + Vector128.Create(0x8c8b8a89, 0x008f8e8d, 0x04030201, 0x08070605), /* shl 7 (16 - 9)/shr9 */ + Vector128.Create(0x8d8c8b8a, 0x01008f8e, 0x05040302, 0x09080706), /* shl 6 (16 -10)/shr10*/ + Vector128.Create(0x8e8d8c8b, 0x0201008f, 0x06050403, 0x0a090807), /* shl 5 (16 -11)/shr11*/ + Vector128.Create(0x8f8e8d8c, 0x03020100, 0x07060504, 0x0b0a0908), /* shl 4 (16 -12)/shr12*/ + Vector128.Create(0x008f8e8du, 0x04030201, 0x08070605, 0x0c0b0a09), /* shl 3 (16 -13)/shr13*/ + Vector128.Create(0x01008f8eu, 0x05040302, 0x09080706, 0x0d0c0b0a), /* shl 2 (16 -14)/shr14*/ + Vector128.Create(0x0201008fu, 0x06050403, 0x0a090807, 0x0e0d0c0b) /* shl 1 (16 -15)/shr15*/ + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Fold4(ref Vector128 xmmCRC0, ref Vector128 xmmCRC1, ref Vector128 xmmCRC2, + ref Vector128 xmmCRC3) + { + Vector128 xmmFold4 = Vector128.Create(0xc6e41596, 0x00000001, 0x54442bd4, 0x00000001); + + Vector128 xTmp0 = xmmCRC0; + Vector128 xTmp1 = xmmCRC1; + Vector128 xTmp2 = xmmCRC2; + Vector128 xTmp3 = xmmCRC3; + + xmmCRC0 = Pclmulqdq.CarrylessMultiply(xmmCRC0.AsUInt64(), xmmFold4.AsUInt64(), 0x01).AsUInt32(); + xTmp0 = Pclmulqdq.CarrylessMultiply(xTmp0.AsUInt64(), xmmFold4.AsUInt64(), 0x10).AsUInt32(); + Vector128 psCRC0 = xmmCRC0.AsSingle(); + Vector128 psT0 = xTmp0.AsSingle(); + Vector128 psRes0 = Sse.Xor(psCRC0, psT0); + + xmmCRC1 = Pclmulqdq.CarrylessMultiply(xmmCRC1.AsUInt64(), xmmFold4.AsUInt64(), 0x01).AsUInt32(); + xTmp1 = Pclmulqdq.CarrylessMultiply(xTmp1.AsUInt64(), xmmFold4.AsUInt64(), 0x10).AsUInt32(); + Vector128 psCRC1 = xmmCRC1.AsSingle(); + Vector128 psT1 = xTmp1.AsSingle(); + Vector128 psRes1 = Sse.Xor(psCRC1, psT1); + + xmmCRC2 = Pclmulqdq.CarrylessMultiply(xmmCRC2.AsUInt64(), xmmFold4.AsUInt64(), 0x01).AsUInt32(); + xTmp2 = Pclmulqdq.CarrylessMultiply(xTmp2.AsUInt64(), xmmFold4.AsUInt64(), 0x10).AsUInt32(); + Vector128 psCRC2 = xmmCRC2.AsSingle(); + Vector128 psT2 = xTmp2.AsSingle(); + Vector128 psRes2 = Sse.Xor(psCRC2, psT2); + + xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), xmmFold4.AsUInt64(), 0x01).AsUInt32(); + xTmp3 = Pclmulqdq.CarrylessMultiply(xTmp3.AsUInt64(), xmmFold4.AsUInt64(), 0x10).AsUInt32(); + Vector128 psCRC3 = xmmCRC3.AsSingle(); + Vector128 psT3 = xTmp3.AsSingle(); + Vector128 psRes3 = Sse.Xor(psCRC3, psT3); + + xmmCRC0 = psRes0.AsUInt32(); + xmmCRC1 = psRes1.AsUInt32(); + xmmCRC2 = psRes2.AsUInt32(); + xmmCRC3 = psRes3.AsUInt32(); + } + + internal static uint Step(byte[] src, long len, uint initialCRC) + { + Vector128 xmmT0, xmmT1, xmmT2; + Vector128 xmmInitial = Sse2.ConvertScalarToVector128UInt32(initialCRC); + Vector128 xmmCRC0 = Sse2.ConvertScalarToVector128UInt32(0x9db42487); + Vector128 xmmCRC1 = Vector128.Zero; + Vector128 xmmCRC2 = Vector128.Zero; + Vector128 xmmCRC3 = Vector128.Zero; + int bufPos = 0; + + bool first = true; + + /* fold 512 to 32 step variable declarations for ISO-C90 compat. */ + Vector128 xmmMask = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000); + Vector128 xmmMask2 = Vector128.Create(0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); + + while((len -= 64) >= 0) { - 0xccaa009e, 0x00000000, /* rk1 */ 0x751997d0, 0x00000001, /* rk2 */ 0xccaa009e, 0x00000000, /* rk5 */ - 0x63cd6124, 0x00000001, /* rk6 */ 0xf7011640, 0x00000001, /* rk7 */ 0xdb710640, 0x00000001 /* rk8 */ - }; + xmmT0 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), + BitConverter.ToUInt32(src, bufPos + 8), BitConverter.ToUInt32(src, bufPos + 12)); - static readonly Vector128[] _pshufbShfTable = - { - Vector128.Create(0x84838281, 0x88878685, 0x8c8b8a89, 0x008f8e8d), /* shl 15 (16 - 1)/shr1 */ - Vector128.Create(0x85848382, 0x89888786, 0x8d8c8b8a, 0x01008f8e), /* shl 14 (16 - 3)/shr2 */ - Vector128.Create(0x86858483, 0x8a898887, 0x8e8d8c8b, 0x0201008f), /* shl 13 (16 - 4)/shr3 */ - Vector128.Create(0x87868584, 0x8b8a8988, 0x8f8e8d8c, 0x03020100), /* shl 12 (16 - 4)/shr4 */ - Vector128.Create(0x88878685, 0x8c8b8a89, 0x008f8e8d, 0x04030201), /* shl 11 (16 - 5)/shr5 */ - Vector128.Create(0x89888786, 0x8d8c8b8a, 0x01008f8e, 0x05040302), /* shl 10 (16 - 6)/shr6 */ - Vector128.Create(0x8a898887, 0x8e8d8c8b, 0x0201008f, 0x06050403), /* shl 9 (16 - 7)/shr7 */ - Vector128.Create(0x8b8a8988, 0x8f8e8d8c, 0x03020100, 0x07060504), /* shl 8 (16 - 8)/shr8 */ - Vector128.Create(0x8c8b8a89, 0x008f8e8d, 0x04030201, 0x08070605), /* shl 7 (16 - 9)/shr9 */ - Vector128.Create(0x8d8c8b8a, 0x01008f8e, 0x05040302, 0x09080706), /* shl 6 (16 -10)/shr10*/ - Vector128.Create(0x8e8d8c8b, 0x0201008f, 0x06050403, 0x0a090807), /* shl 5 (16 -11)/shr11*/ - Vector128.Create(0x8f8e8d8c, 0x03020100, 0x07060504, 0x0b0a0908), /* shl 4 (16 -12)/shr12*/ - Vector128.Create(0x008f8e8du, 0x04030201, 0x08070605, 0x0c0b0a09), /* shl 3 (16 -13)/shr13*/ - Vector128.Create(0x01008f8eu, 0x05040302, 0x09080706, 0x0d0c0b0a), /* shl 2 (16 -14)/shr14*/ - Vector128.Create(0x0201008fu, 0x06050403, 0x0a090807, 0x0e0d0c0b) /* shl 1 (16 -15)/shr15*/ - }; + bufPos += 16; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void Fold4(ref Vector128 xmmCRC0, ref Vector128 xmmCRC1, ref Vector128 xmmCRC2, - ref Vector128 xmmCRC3) - { - Vector128 xmmFold4 = Vector128.Create(0xc6e41596, 0x00000001, 0x54442bd4, 0x00000001); + xmmT1 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), + BitConverter.ToUInt32(src, bufPos + 8), BitConverter.ToUInt32(src, bufPos + 12)); - Vector128 xTmp0 = xmmCRC0; - Vector128 xTmp1 = xmmCRC1; - Vector128 xTmp2 = xmmCRC2; - Vector128 xTmp3 = xmmCRC3; + bufPos += 16; - xmmCRC0 = Pclmulqdq.CarrylessMultiply(xmmCRC0.AsUInt64(), xmmFold4.AsUInt64(), 0x01).AsUInt32(); - xTmp0 = Pclmulqdq.CarrylessMultiply(xTmp0.AsUInt64(), xmmFold4.AsUInt64(), 0x10).AsUInt32(); - Vector128 psCRC0 = xmmCRC0.AsSingle(); - Vector128 psT0 = xTmp0.AsSingle(); - Vector128 psRes0 = Sse.Xor(psCRC0, psT0); + xmmT2 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), + BitConverter.ToUInt32(src, bufPos + 8), BitConverter.ToUInt32(src, bufPos + 12)); - xmmCRC1 = Pclmulqdq.CarrylessMultiply(xmmCRC1.AsUInt64(), xmmFold4.AsUInt64(), 0x01).AsUInt32(); - xTmp1 = Pclmulqdq.CarrylessMultiply(xTmp1.AsUInt64(), xmmFold4.AsUInt64(), 0x10).AsUInt32(); - Vector128 psCRC1 = xmmCRC1.AsSingle(); - Vector128 psT1 = xTmp1.AsSingle(); - Vector128 psRes1 = Sse.Xor(psCRC1, psT1); + bufPos += 16; - xmmCRC2 = Pclmulqdq.CarrylessMultiply(xmmCRC2.AsUInt64(), xmmFold4.AsUInt64(), 0x01).AsUInt32(); - xTmp2 = Pclmulqdq.CarrylessMultiply(xTmp2.AsUInt64(), xmmFold4.AsUInt64(), 0x10).AsUInt32(); - Vector128 psCRC2 = xmmCRC2.AsSingle(); - Vector128 psT2 = xTmp2.AsSingle(); - Vector128 psRes2 = Sse.Xor(psCRC2, psT2); + Vector128 xmmT3 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), + BitConverter.ToUInt32(src, bufPos + 4), + BitConverter.ToUInt32(src, bufPos + 8), + BitConverter.ToUInt32(src, bufPos + 12)); - xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), xmmFold4.AsUInt64(), 0x01).AsUInt32(); - xTmp3 = Pclmulqdq.CarrylessMultiply(xTmp3.AsUInt64(), xmmFold4.AsUInt64(), 0x10).AsUInt32(); - Vector128 psCRC3 = xmmCRC3.AsSingle(); - Vector128 psT3 = xTmp3.AsSingle(); - Vector128 psRes3 = Sse.Xor(psCRC3, psT3); + bufPos += 16; - xmmCRC0 = psRes0.AsUInt32(); - xmmCRC1 = psRes1.AsUInt32(); - xmmCRC2 = psRes2.AsUInt32(); - xmmCRC3 = psRes3.AsUInt32(); - } - - internal static uint Step(byte[] src, long len, uint initialCRC) - { - Vector128 xmmT0, xmmT1, xmmT2; - Vector128 xmmInitial = Sse2.ConvertScalarToVector128UInt32(initialCRC); - Vector128 xmmCRC0 = Sse2.ConvertScalarToVector128UInt32(0x9db42487); - Vector128 xmmCRC1 = Vector128.Zero; - Vector128 xmmCRC2 = Vector128.Zero; - Vector128 xmmCRC3 = Vector128.Zero; - int bufPos = 0; - - bool first = true; - - /* fold 512 to 32 step variable declarations for ISO-C90 compat. */ - Vector128 xmmMask = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000); - Vector128 xmmMask2 = Vector128.Create(0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); - - while((len -= 64) >= 0) + if(first) { - xmmT0 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), - BitConverter.ToUInt32(src, bufPos + 8), - BitConverter.ToUInt32(src, bufPos + 12)); - - bufPos += 16; - - xmmT1 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), - BitConverter.ToUInt32(src, bufPos + 8), - BitConverter.ToUInt32(src, bufPos + 12)); - - bufPos += 16; - - xmmT2 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), - BitConverter.ToUInt32(src, bufPos + 8), - BitConverter.ToUInt32(src, bufPos + 12)); - - bufPos += 16; - - Vector128 xmmT3 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), - BitConverter.ToUInt32(src, bufPos + 4), - BitConverter.ToUInt32(src, bufPos + 8), - BitConverter.ToUInt32(src, bufPos + 12)); - - bufPos += 16; - - if(first) - { - first = false; - xmmT0 = Sse2.Xor(xmmT0, xmmInitial); - } - - Fold4(ref xmmCRC0, ref xmmCRC1, ref xmmCRC2, ref xmmCRC3); - - xmmCRC0 = Sse2.Xor(xmmCRC0, xmmT0); - xmmCRC1 = Sse2.Xor(xmmCRC1, xmmT1); - xmmCRC2 = Sse2.Xor(xmmCRC2, xmmT2); - xmmCRC3 = Sse2.Xor(xmmCRC3, xmmT3); + first = false; + xmmT0 = Sse2.Xor(xmmT0, xmmInitial); } - /* fold 512 to 32 */ + Fold4(ref xmmCRC0, ref xmmCRC1, ref xmmCRC2, ref xmmCRC3); - /* - * k1 - */ - Vector128 crcFold = Vector128.Create(_crcK[0], _crcK[1], _crcK[2], _crcK[3]); - - Vector128 xTmp0 = Pclmulqdq.CarrylessMultiply(xmmCRC0.AsUInt64(), crcFold.AsUInt64(), 0x10). - AsUInt32(); - - xmmCRC0 = Pclmulqdq.CarrylessMultiply(xmmCRC0.AsUInt64(), crcFold.AsUInt64(), 0x01).AsUInt32(); - xmmCRC1 = Sse2.Xor(xmmCRC1, xTmp0); - xmmCRC1 = Sse2.Xor(xmmCRC1, xmmCRC0); - - Vector128 xTmp1 = Pclmulqdq.CarrylessMultiply(xmmCRC1.AsUInt64(), crcFold.AsUInt64(), 0x10). - AsUInt32(); - - xmmCRC1 = Pclmulqdq.CarrylessMultiply(xmmCRC1.AsUInt64(), crcFold.AsUInt64(), 0x01).AsUInt32(); - xmmCRC2 = Sse2.Xor(xmmCRC2, xTmp1); - xmmCRC2 = Sse2.Xor(xmmCRC2, xmmCRC1); - - Vector128 xTmp2 = Pclmulqdq.CarrylessMultiply(xmmCRC2.AsUInt64(), crcFold.AsUInt64(), 0x10). - AsUInt32(); - - xmmCRC2 = Pclmulqdq.CarrylessMultiply(xmmCRC2.AsUInt64(), crcFold.AsUInt64(), 0x01).AsUInt32(); - xmmCRC3 = Sse2.Xor(xmmCRC3, xTmp2); - xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC2); - - /* - * k5 - */ - crcFold = Vector128.Create(_crcK[4], _crcK[5], _crcK[6], _crcK[7]); - - xmmCRC0 = xmmCRC3; - xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), crcFold.AsUInt64(), 0).AsUInt32(); - xmmCRC0 = Sse2.ShiftRightLogical128BitLane(xmmCRC0, 8); - xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC0); - - xmmCRC0 = xmmCRC3; - xmmCRC3 = Sse2.ShiftLeftLogical128BitLane(xmmCRC3, 4); - xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), crcFold.AsUInt64(), 0x10).AsUInt32(); - xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC0); - xmmCRC3 = Sse2.And(xmmCRC3, xmmMask2); - - /* - * k7 - */ - xmmCRC1 = xmmCRC3; - xmmCRC2 = xmmCRC3; - crcFold = Vector128.Create(_crcK[8], _crcK[9], _crcK[10], _crcK[11]); - - xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), crcFold.AsUInt64(), 0).AsUInt32(); - xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC2); - xmmCRC3 = Sse2.And(xmmCRC3, xmmMask); - - xmmCRC2 = xmmCRC3; - xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), crcFold.AsUInt64(), 0x10).AsUInt32(); - xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC2); - xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC1); - - /* - * could just as well write xmm_crc3[2], doing a movaps and truncating, but - * no real advantage - it's a tiny bit slower per call, while no additional CPUs - * would be supported by only requiring SSSE3 and CLMUL instead of SSE4.1 + CLMUL - */ - return ~Sse41.Extract(xmmCRC3, 2); + xmmCRC0 = Sse2.Xor(xmmCRC0, xmmT0); + xmmCRC1 = Sse2.Xor(xmmCRC1, xmmT1); + xmmCRC2 = Sse2.Xor(xmmCRC2, xmmT2); + xmmCRC3 = Sse2.Xor(xmmCRC3, xmmT3); } + + /* fold 512 to 32 */ + + /* + * k1 + */ + Vector128 crcFold = Vector128.Create(_crcK[0], _crcK[1], _crcK[2], _crcK[3]); + + Vector128 xTmp0 = Pclmulqdq.CarrylessMultiply(xmmCRC0.AsUInt64(), crcFold.AsUInt64(), 0x10).AsUInt32(); + + xmmCRC0 = Pclmulqdq.CarrylessMultiply(xmmCRC0.AsUInt64(), crcFold.AsUInt64(), 0x01).AsUInt32(); + xmmCRC1 = Sse2.Xor(xmmCRC1, xTmp0); + xmmCRC1 = Sse2.Xor(xmmCRC1, xmmCRC0); + + Vector128 xTmp1 = Pclmulqdq.CarrylessMultiply(xmmCRC1.AsUInt64(), crcFold.AsUInt64(), 0x10).AsUInt32(); + + xmmCRC1 = Pclmulqdq.CarrylessMultiply(xmmCRC1.AsUInt64(), crcFold.AsUInt64(), 0x01).AsUInt32(); + xmmCRC2 = Sse2.Xor(xmmCRC2, xTmp1); + xmmCRC2 = Sse2.Xor(xmmCRC2, xmmCRC1); + + Vector128 xTmp2 = Pclmulqdq.CarrylessMultiply(xmmCRC2.AsUInt64(), crcFold.AsUInt64(), 0x10).AsUInt32(); + + xmmCRC2 = Pclmulqdq.CarrylessMultiply(xmmCRC2.AsUInt64(), crcFold.AsUInt64(), 0x01).AsUInt32(); + xmmCRC3 = Sse2.Xor(xmmCRC3, xTmp2); + xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC2); + + /* + * k5 + */ + crcFold = Vector128.Create(_crcK[4], _crcK[5], _crcK[6], _crcK[7]); + + xmmCRC0 = xmmCRC3; + xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), crcFold.AsUInt64(), 0).AsUInt32(); + xmmCRC0 = Sse2.ShiftRightLogical128BitLane(xmmCRC0, 8); + xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC0); + + xmmCRC0 = xmmCRC3; + xmmCRC3 = Sse2.ShiftLeftLogical128BitLane(xmmCRC3, 4); + xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), crcFold.AsUInt64(), 0x10).AsUInt32(); + xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC0); + xmmCRC3 = Sse2.And(xmmCRC3, xmmMask2); + + /* + * k7 + */ + xmmCRC1 = xmmCRC3; + xmmCRC2 = xmmCRC3; + crcFold = Vector128.Create(_crcK[8], _crcK[9], _crcK[10], _crcK[11]); + + xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), crcFold.AsUInt64(), 0).AsUInt32(); + xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC2); + xmmCRC3 = Sse2.And(xmmCRC3, xmmMask); + + xmmCRC2 = xmmCRC3; + xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), crcFold.AsUInt64(), 0x10).AsUInt32(); + xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC2); + xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC1); + + /* + * could just as well write xmm_crc3[2], doing a movaps and truncating, but + * no real advantage - it's a tiny bit slower per call, while no additional CPUs + * would be supported by only requiring SSSE3 and CLMUL instead of SSE4.1 + CLMUL + */ + return ~Sse41.Extract(xmmCRC3, 2); } } \ No newline at end of file diff --git a/Aaru6.Checksums/CRC32/vmull.cs b/Aaru6.Checksums/CRC32/vmull.cs index 266204d..94c4b50 100644 --- a/Aaru6.Checksums/CRC32/vmull.cs +++ b/Aaru6.Checksums/CRC32/vmull.cs @@ -52,354 +52,352 @@ using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.Arm; -namespace Aaru6.Checksums.CRC32 +namespace Aaru6.Checksums.CRC32; + +internal static class Vmull { - internal static class Vmull + static readonly uint[] _crcK = { - static readonly uint[] _crcK = + 0xccaa009e, 0x00000000, /* rk1 */ 0x751997d0, 0x00000001, /* rk2 */ 0xccaa009e, 0x00000000, /* rk5 */ + 0x63cd6124, 0x00000001, /* rk6 */ 0xf7011640, 0x00000001, /* rk7 */ 0xdb710640, 0x00000001 /* rk8 */ + }; + + static readonly Vector128[] _pshufbShfTable = + { + Vector128.Create(0x84838281, 0x88878685, 0x8c8b8a89, 0x008f8e8d), /* shl 15 (16 - 1)/shr1 */ + Vector128.Create(0x85848382, 0x89888786, 0x8d8c8b8a, 0x01008f8e), /* shl 14 (16 - 3)/shr2 */ + Vector128.Create(0x86858483, 0x8a898887, 0x8e8d8c8b, 0x0201008f), /* shl 13 (16 - 4)/shr3 */ + Vector128.Create(0x87868584, 0x8b8a8988, 0x8f8e8d8c, 0x03020100), /* shl 12 (16 - 4)/shr4 */ + Vector128.Create(0x88878685, 0x8c8b8a89, 0x008f8e8d, 0x04030201), /* shl 11 (16 - 5)/shr5 */ + Vector128.Create(0x89888786, 0x8d8c8b8a, 0x01008f8e, 0x05040302), /* shl 10 (16 - 6)/shr6 */ + Vector128.Create(0x8a898887, 0x8e8d8c8b, 0x0201008f, 0x06050403), /* shl 9 (16 - 7)/shr7 */ + Vector128.Create(0x8b8a8988, 0x8f8e8d8c, 0x03020100, 0x07060504), /* shl 8 (16 - 8)/shr8 */ + Vector128.Create(0x8c8b8a89, 0x008f8e8d, 0x04030201, 0x08070605), /* shl 7 (16 - 9)/shr9 */ + Vector128.Create(0x8d8c8b8a, 0x01008f8e, 0x05040302, 0x09080706), /* shl 6 (16 -10)/shr10*/ + Vector128.Create(0x8e8d8c8b, 0x0201008f, 0x06050403, 0x0a090807), /* shl 5 (16 -11)/shr11*/ + Vector128.Create(0x8f8e8d8c, 0x03020100, 0x07060504, 0x0b0a0908), /* shl 4 (16 -12)/shr12*/ + Vector128.Create(0x008f8e8du, 0x04030201, 0x08070605, 0x0c0b0a09), /* shl 3 (16 -13)/shr13*/ + Vector128.Create(0x01008f8eu, 0x05040302, 0x09080706, 0x0d0c0b0a), /* shl 2 (16 -14)/shr14*/ + Vector128.Create(0x0201008fu, 0x06050403, 0x0a090807, 0x0e0d0c0b) /* shl 1 (16 -15)/shr15*/ + }; + + static Vector128 vmull_p64(Vector64 a, Vector64 b) + { + if(Aes.IsSupported) { - 0xccaa009e, 0x00000000, /* rk1 */ 0x751997d0, 0x00000001, /* rk2 */ 0xccaa009e, 0x00000000, /* rk5 */ - 0x63cd6124, 0x00000001, /* rk6 */ 0xf7011640, 0x00000001, /* rk7 */ 0xdb710640, 0x00000001 /* rk8 */ - }; - - static readonly Vector128[] _pshufbShfTable = - { - Vector128.Create(0x84838281, 0x88878685, 0x8c8b8a89, 0x008f8e8d), /* shl 15 (16 - 1)/shr1 */ - Vector128.Create(0x85848382, 0x89888786, 0x8d8c8b8a, 0x01008f8e), /* shl 14 (16 - 3)/shr2 */ - Vector128.Create(0x86858483, 0x8a898887, 0x8e8d8c8b, 0x0201008f), /* shl 13 (16 - 4)/shr3 */ - Vector128.Create(0x87868584, 0x8b8a8988, 0x8f8e8d8c, 0x03020100), /* shl 12 (16 - 4)/shr4 */ - Vector128.Create(0x88878685, 0x8c8b8a89, 0x008f8e8d, 0x04030201), /* shl 11 (16 - 5)/shr5 */ - Vector128.Create(0x89888786, 0x8d8c8b8a, 0x01008f8e, 0x05040302), /* shl 10 (16 - 6)/shr6 */ - Vector128.Create(0x8a898887, 0x8e8d8c8b, 0x0201008f, 0x06050403), /* shl 9 (16 - 7)/shr7 */ - Vector128.Create(0x8b8a8988, 0x8f8e8d8c, 0x03020100, 0x07060504), /* shl 8 (16 - 8)/shr8 */ - Vector128.Create(0x8c8b8a89, 0x008f8e8d, 0x04030201, 0x08070605), /* shl 7 (16 - 9)/shr9 */ - Vector128.Create(0x8d8c8b8a, 0x01008f8e, 0x05040302, 0x09080706), /* shl 6 (16 -10)/shr10*/ - Vector128.Create(0x8e8d8c8b, 0x0201008f, 0x06050403, 0x0a090807), /* shl 5 (16 -11)/shr11*/ - Vector128.Create(0x8f8e8d8c, 0x03020100, 0x07060504, 0x0b0a0908), /* shl 4 (16 -12)/shr12*/ - Vector128.Create(0x008f8e8du, 0x04030201, 0x08070605, 0x0c0b0a09), /* shl 3 (16 -13)/shr13*/ - Vector128.Create(0x01008f8eu, 0x05040302, 0x09080706, 0x0d0c0b0a), /* shl 2 (16 -14)/shr14*/ - Vector128.Create(0x0201008fu, 0x06050403, 0x0a090807, 0x0e0d0c0b) /* shl 1 (16 -15)/shr15*/ - }; - - static Vector128 vmull_p64(Vector64 a, Vector64 b) - { - if(Aes.IsSupported) - { - return Aes.PolynomialMultiplyWideningLower(a, b); - } - - // Masks - Vector128 k4832 = Vector128.Create(Vector64.Create(0x0000fffffffffffful), - Vector64.Create(0x00000000fffffffful)).AsByte(); - - Vector128 k1600 = Vector128.Create(Vector64.Create(0x000000000000fffful), - Vector64.Create(0x0000000000000000ul)).AsByte(); - - // Do the multiplies, rotating with vext to get all combinations - Vector128 d = AdvSimd.PolynomialMultiplyWideningLower(a.AsByte(), b.AsByte()).AsByte(); // D = A0 * B0 - - Vector128 e = AdvSimd. - PolynomialMultiplyWideningLower(a.AsByte(), - AdvSimd.ExtractVector64(b.AsByte(), b.AsByte(), 1)). - AsByte(); // E = A0 * B1 - - Vector128 f = AdvSimd. - PolynomialMultiplyWideningLower(AdvSimd.ExtractVector64(a.AsByte(), a.AsByte(), 1), - b.AsByte()).AsByte(); // F = A1 * B0 - - Vector128 g = AdvSimd. - PolynomialMultiplyWideningLower(a.AsByte(), - AdvSimd.ExtractVector64(b.AsByte(), b.AsByte(), 2)). - AsByte(); // G = A0 * B2 - - Vector128 h = AdvSimd. - PolynomialMultiplyWideningLower(AdvSimd.ExtractVector64(a.AsByte(), a.AsByte(), 2), - b.AsByte()).AsByte(); // H = A2 * B0 - - Vector128 i = AdvSimd. - PolynomialMultiplyWideningLower(a.AsByte(), - AdvSimd.ExtractVector64(b.AsByte(), b.AsByte(), 3)). - AsByte(); // I = A0 * B3 - - Vector128 j = AdvSimd. - PolynomialMultiplyWideningLower(AdvSimd.ExtractVector64(a.AsByte(), a.AsByte(), 3), - b.AsByte()).AsByte(); // J = A3 * B0 - - Vector128 k = AdvSimd. - PolynomialMultiplyWideningLower(a.AsByte(), - AdvSimd.ExtractVector64(b.AsByte(), b.AsByte(), 4)). - AsByte(); // L = A0 * B4 - - // Add cross products - Vector128 l = AdvSimd.Xor(e, f); // L = E + F - Vector128 m = AdvSimd.Xor(g, h); // M = G + H - Vector128 n = AdvSimd.Xor(i, j); // N = I + J - - Vector128 lmP0; - Vector128 lmP1; - Vector128 nkP0; - Vector128 nkP1; - - // Interleave. Using vzip1 and vzip2 prevents Clang from emitting TBL - // instructions. - if(AdvSimd.Arm64.IsSupported) - { - lmP0 = AdvSimd.Arm64.ZipLow(l.AsUInt64(), m.AsUInt64()).AsByte(); - lmP1 = AdvSimd.Arm64.ZipHigh(l.AsUInt64(), m.AsUInt64()).AsByte(); - nkP0 = AdvSimd.Arm64.ZipLow(n.AsUInt64(), k.AsUInt64()).AsByte(); - nkP1 = AdvSimd.Arm64.ZipHigh(n.AsUInt64(), k.AsUInt64()).AsByte(); - } - else - { - lmP0 = Vector128.Create(l.GetLower(), m.GetLower()); - lmP1 = Vector128.Create(l.GetUpper(), m.GetUpper()); - nkP0 = Vector128.Create(n.GetLower(), k.GetLower()); - nkP1 = Vector128.Create(n.GetUpper(), k.GetUpper()); - } - - // t0 = (L) (P0 + P1) << 8 - // t1 = (M) (P2 + P3) << 16 - Vector128 t0T1Tmp = AdvSimd.Xor(lmP0, lmP1); - Vector128 t0T1H = AdvSimd.And(lmP1, k4832); - Vector128 t0T1L = AdvSimd.Xor(t0T1Tmp, t0T1H); - - // t2 = (N) (P4 + P5) << 24 - // t3 = (K) (P6 + P7) << 32 - Vector128 t2T3Tmp = AdvSimd.Xor(nkP0, nkP1); - Vector128 t2T3H = AdvSimd.And(nkP1, k1600); - Vector128 t2T3L = AdvSimd.Xor(t2T3Tmp, t2T3H); - - Vector128 t1; - Vector128 t0; - Vector128 t3; - Vector128 t2; - - // De-interleave - if(AdvSimd.Arm64.IsSupported) - { - t0 = AdvSimd.Arm64.UnzipEven(t0T1L.AsUInt64(), t0T1H.AsUInt64()).AsByte(); - t1 = AdvSimd.Arm64.UnzipOdd(t0T1L.AsUInt64(), t0T1H.AsUInt64()).AsByte(); - t2 = AdvSimd.Arm64.UnzipEven(t2T3L.AsUInt64(), t2T3H.AsUInt64()).AsByte(); - t3 = AdvSimd.Arm64.UnzipOdd(t2T3L.AsUInt64(), t2T3H.AsUInt64()).AsByte(); - } - else - { - t1 = Vector128.Create(t0T1L.GetUpper(), t0T1H.GetUpper()); - t0 = Vector128.Create(t0T1L.GetLower(), t0T1H.GetLower()); - t3 = Vector128.Create(t2T3L.GetUpper(), t2T3H.GetUpper()); - t2 = Vector128.Create(t2T3L.GetLower(), t2T3H.GetLower()); - } - - // Shift the cross products - Vector128 t0Shift = AdvSimd.ExtractVector128(t0, t0, 15); // t0 << 8 - Vector128 t1Shift = AdvSimd.ExtractVector128(t1, t1, 14); // t1 << 16 - Vector128 t2Shift = AdvSimd.ExtractVector128(t2, t2, 13); // t2 << 24 - Vector128 t3Shift = AdvSimd.ExtractVector128(t3, t3, 12); // t3 << 32 - - // Accumulate the products - Vector128 cross1 = AdvSimd.Xor(t0Shift, t1Shift); - Vector128 cross2 = AdvSimd.Xor(t2Shift, t3Shift); - Vector128 mix = AdvSimd.Xor(d, cross1); - Vector128 r = AdvSimd.Xor(mix, cross2); - - return r.AsUInt64(); + return Aes.PolynomialMultiplyWideningLower(a, b); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static Vector128 mm_shuffle_epi8(Vector128 a, Vector128 b) + // Masks + Vector128 k4832 = Vector128.Create(Vector64.Create(0x0000fffffffffffful), + Vector64.Create(0x00000000fffffffful)).AsByte(); + + Vector128 k1600 = Vector128.Create(Vector64.Create(0x000000000000fffful), + Vector64.Create(0x0000000000000000ul)).AsByte(); + + // Do the multiplies, rotating with vext to get all combinations + Vector128 d = AdvSimd.PolynomialMultiplyWideningLower(a.AsByte(), b.AsByte()).AsByte(); // D = A0 * B0 + + Vector128 e = AdvSimd. + PolynomialMultiplyWideningLower(a.AsByte(), + AdvSimd.ExtractVector64(b.AsByte(), b.AsByte(), 1)). + AsByte(); // E = A0 * B1 + + Vector128 f = AdvSimd. + PolynomialMultiplyWideningLower(AdvSimd.ExtractVector64(a.AsByte(), a.AsByte(), 1), + b.AsByte()).AsByte(); // F = A1 * B0 + + Vector128 g = AdvSimd. + PolynomialMultiplyWideningLower(a.AsByte(), + AdvSimd.ExtractVector64(b.AsByte(), b.AsByte(), 2)). + AsByte(); // G = A0 * B2 + + Vector128 h = AdvSimd. + PolynomialMultiplyWideningLower(AdvSimd.ExtractVector64(a.AsByte(), a.AsByte(), 2), + b.AsByte()).AsByte(); // H = A2 * B0 + + Vector128 i = AdvSimd. + PolynomialMultiplyWideningLower(a.AsByte(), + AdvSimd.ExtractVector64(b.AsByte(), b.AsByte(), 3)). + AsByte(); // I = A0 * B3 + + Vector128 j = AdvSimd. + PolynomialMultiplyWideningLower(AdvSimd.ExtractVector64(a.AsByte(), a.AsByte(), 3), + b.AsByte()).AsByte(); // J = A3 * B0 + + Vector128 k = AdvSimd. + PolynomialMultiplyWideningLower(a.AsByte(), + AdvSimd.ExtractVector64(b.AsByte(), b.AsByte(), 4)). + AsByte(); // L = A0 * B4 + + // Add cross products + Vector128 l = AdvSimd.Xor(e, f); // L = E + F + Vector128 m = AdvSimd.Xor(g, h); // M = G + H + Vector128 n = AdvSimd.Xor(i, j); // N = I + J + + Vector128 lmP0; + Vector128 lmP1; + Vector128 nkP0; + Vector128 nkP1; + + // Interleave. Using vzip1 and vzip2 prevents Clang from emitting TBL + // instructions. + if(AdvSimd.Arm64.IsSupported) { - Vector128 tbl = a.AsByte(); // input a - Vector128 idx = b.AsByte(); // input b - - Vector128 - idxMasked = AdvSimd.And(idx, AdvSimd.DuplicateToVector128((byte)0x8F)); // avoid using meaningless bits - - return AdvSimd.Arm64.VectorTableLookup(tbl, idxMasked).AsUInt64(); + lmP0 = AdvSimd.Arm64.ZipLow(l.AsUInt64(), m.AsUInt64()).AsByte(); + lmP1 = AdvSimd.Arm64.ZipHigh(l.AsUInt64(), m.AsUInt64()).AsByte(); + nkP0 = AdvSimd.Arm64.ZipLow(n.AsUInt64(), k.AsUInt64()).AsByte(); + nkP1 = AdvSimd.Arm64.ZipHigh(n.AsUInt64(), k.AsUInt64()).AsByte(); + } + else + { + lmP0 = Vector128.Create(l.GetLower(), m.GetLower()); + lmP1 = Vector128.Create(l.GetUpper(), m.GetUpper()); + nkP0 = Vector128.Create(n.GetLower(), k.GetLower()); + nkP1 = Vector128.Create(n.GetUpper(), k.GetUpper()); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void Fold4(ref Vector128 qCRC0, ref Vector128 qCRC1, ref Vector128 qCRC2, - ref Vector128 qCRC3) + // t0 = (L) (P0 + P1) << 8 + // t1 = (M) (P2 + P3) << 16 + Vector128 t0T1Tmp = AdvSimd.Xor(lmP0, lmP1); + Vector128 t0T1H = AdvSimd.And(lmP1, k4832); + Vector128 t0T1L = AdvSimd.Xor(t0T1Tmp, t0T1H); + + // t2 = (N) (P4 + P5) << 24 + // t3 = (K) (P6 + P7) << 32 + Vector128 t2T3Tmp = AdvSimd.Xor(nkP0, nkP1); + Vector128 t2T3H = AdvSimd.And(nkP1, k1600); + Vector128 t2T3L = AdvSimd.Xor(t2T3Tmp, t2T3H); + + Vector128 t1; + Vector128 t0; + Vector128 t3; + Vector128 t2; + + // De-interleave + if(AdvSimd.Arm64.IsSupported) { - Vector128 qFold4 = Vector128.Create(0xc6e41596, 0x00000001, 0x54442bd4, 0x00000001).AsUInt64(); - - Vector128 xTmp0 = qCRC0; - Vector128 xTmp1 = qCRC1; - Vector128 xTmp2 = qCRC2; - Vector128 xTmp3 = qCRC3; - - qCRC0 = vmull_p64(qCRC0.GetUpper(), qFold4.GetLower()); - xTmp0 = vmull_p64(xTmp0.GetLower(), qFold4.GetUpper()); - Vector128 psCRC0 = qCRC0.AsUInt32(); - Vector128 psT0 = xTmp0.AsUInt32(); - Vector128 psRes0 = AdvSimd.Xor(psCRC0, psT0); - - qCRC1 = vmull_p64(qCRC1.GetUpper(), qFold4.GetLower()); - xTmp1 = vmull_p64(xTmp1.GetLower(), qFold4.GetUpper()); - Vector128 psCRC1 = qCRC1.AsUInt32(); - Vector128 psT1 = xTmp1.AsUInt32(); - Vector128 psRes1 = AdvSimd.Xor(psCRC1, psT1); - - qCRC2 = vmull_p64(qCRC2.GetUpper(), qFold4.GetLower()); - xTmp2 = vmull_p64(xTmp2.GetLower(), qFold4.GetUpper()); - Vector128 psCRC2 = qCRC2.AsUInt32(); - Vector128 psT2 = xTmp2.AsUInt32(); - Vector128 psRes2 = AdvSimd.Xor(psCRC2, psT2); - - qCRC3 = vmull_p64(qCRC3.GetUpper(), qFold4.GetLower()); - xTmp3 = vmull_p64(xTmp3.GetLower(), qFold4.GetUpper()); - Vector128 psCRC3 = qCRC3.AsUInt32(); - Vector128 psT3 = xTmp3.AsUInt32(); - Vector128 psRes3 = AdvSimd.Xor(psCRC3, psT3); - - qCRC0 = psRes0.AsUInt64(); - qCRC1 = psRes1.AsUInt64(); - qCRC2 = psRes2.AsUInt64(); - qCRC3 = psRes3.AsUInt64(); + t0 = AdvSimd.Arm64.UnzipEven(t0T1L.AsUInt64(), t0T1H.AsUInt64()).AsByte(); + t1 = AdvSimd.Arm64.UnzipOdd(t0T1L.AsUInt64(), t0T1H.AsUInt64()).AsByte(); + t2 = AdvSimd.Arm64.UnzipEven(t2T3L.AsUInt64(), t2T3H.AsUInt64()).AsByte(); + t3 = AdvSimd.Arm64.UnzipOdd(t2T3L.AsUInt64(), t2T3H.AsUInt64()).AsByte(); + } + else + { + t1 = Vector128.Create(t0T1L.GetUpper(), t0T1H.GetUpper()); + t0 = Vector128.Create(t0T1L.GetLower(), t0T1H.GetLower()); + t3 = Vector128.Create(t2T3L.GetUpper(), t2T3H.GetUpper()); + t2 = Vector128.Create(t2T3L.GetLower(), t2T3H.GetLower()); } - internal static uint Step(byte[] src, long len, uint initialCRC) + // Shift the cross products + Vector128 t0Shift = AdvSimd.ExtractVector128(t0, t0, 15); // t0 << 8 + Vector128 t1Shift = AdvSimd.ExtractVector128(t1, t1, 14); // t1 << 16 + Vector128 t2Shift = AdvSimd.ExtractVector128(t2, t2, 13); // t2 << 24 + Vector128 t3Shift = AdvSimd.ExtractVector128(t3, t3, 12); // t3 << 32 + + // Accumulate the products + Vector128 cross1 = AdvSimd.Xor(t0Shift, t1Shift); + Vector128 cross2 = AdvSimd.Xor(t2Shift, t3Shift); + Vector128 mix = AdvSimd.Xor(d, cross1); + Vector128 r = AdvSimd.Xor(mix, cross2); + + return r.AsUInt64(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static Vector128 mm_shuffle_epi8(Vector128 a, Vector128 b) + { + Vector128 tbl = a.AsByte(); // input a + Vector128 idx = b.AsByte(); // input b + + Vector128 + idxMasked = AdvSimd.And(idx, AdvSimd.DuplicateToVector128((byte)0x8F)); // avoid using meaningless bits + + return AdvSimd.Arm64.VectorTableLookup(tbl, idxMasked).AsUInt64(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Fold4(ref Vector128 qCRC0, ref Vector128 qCRC1, ref Vector128 qCRC2, + ref Vector128 qCRC3) + { + Vector128 qFold4 = Vector128.Create(0xc6e41596, 0x00000001, 0x54442bd4, 0x00000001).AsUInt64(); + + Vector128 xTmp0 = qCRC0; + Vector128 xTmp1 = qCRC1; + Vector128 xTmp2 = qCRC2; + Vector128 xTmp3 = qCRC3; + + qCRC0 = vmull_p64(qCRC0.GetUpper(), qFold4.GetLower()); + xTmp0 = vmull_p64(xTmp0.GetLower(), qFold4.GetUpper()); + Vector128 psCRC0 = qCRC0.AsUInt32(); + Vector128 psT0 = xTmp0.AsUInt32(); + Vector128 psRes0 = AdvSimd.Xor(psCRC0, psT0); + + qCRC1 = vmull_p64(qCRC1.GetUpper(), qFold4.GetLower()); + xTmp1 = vmull_p64(xTmp1.GetLower(), qFold4.GetUpper()); + Vector128 psCRC1 = qCRC1.AsUInt32(); + Vector128 psT1 = xTmp1.AsUInt32(); + Vector128 psRes1 = AdvSimd.Xor(psCRC1, psT1); + + qCRC2 = vmull_p64(qCRC2.GetUpper(), qFold4.GetLower()); + xTmp2 = vmull_p64(xTmp2.GetLower(), qFold4.GetUpper()); + Vector128 psCRC2 = qCRC2.AsUInt32(); + Vector128 psT2 = xTmp2.AsUInt32(); + Vector128 psRes2 = AdvSimd.Xor(psCRC2, psT2); + + qCRC3 = vmull_p64(qCRC3.GetUpper(), qFold4.GetLower()); + xTmp3 = vmull_p64(xTmp3.GetLower(), qFold4.GetUpper()); + Vector128 psCRC3 = qCRC3.AsUInt32(); + Vector128 psT3 = xTmp3.AsUInt32(); + Vector128 psRes3 = AdvSimd.Xor(psCRC3, psT3); + + qCRC0 = psRes0.AsUInt64(); + qCRC1 = psRes1.AsUInt64(); + qCRC2 = psRes2.AsUInt64(); + qCRC3 = psRes3.AsUInt64(); + } + + internal static uint Step(byte[] src, long len, uint initialCRC) + { + Vector128 qT0; + Vector128 qT1; + Vector128 qT2; + Vector128 qT3; + Vector128 qInitial = AdvSimd.Insert(Vector128.Zero, 0, initialCRC).AsUInt64(); + Vector128 qCRC0 = AdvSimd.Insert(Vector128.Zero, 0, 0x9db42487).AsUInt64(); + Vector128 qCRC1 = Vector128.Zero; + Vector128 qCRC2 = Vector128.Zero; + Vector128 qCRC3 = Vector128.Zero; + int bufPos = 0; + + bool first = true; + + /* fold 512 to 32 step variable declarations for ISO-C90 compat. */ + Vector128 qMask = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000); + Vector128 qMask2 = Vector128.Create(0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); + + uint crc; + Vector128 xTmp0; + Vector128 xTmp1; + Vector128 xTmp2; + Vector128 crcFold; + + while((len -= 64) >= 0) { - Vector128 qT0; - Vector128 qT1; - Vector128 qT2; - Vector128 qT3; - Vector128 qInitial = AdvSimd.Insert(Vector128.Zero, 0, initialCRC).AsUInt64(); - Vector128 qCRC0 = AdvSimd.Insert(Vector128.Zero, 0, 0x9db42487).AsUInt64(); - Vector128 qCRC1 = Vector128.Zero; - Vector128 qCRC2 = Vector128.Zero; - Vector128 qCRC3 = Vector128.Zero; - int bufPos = 0; + qT0 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), + BitConverter.ToUInt32(src, bufPos + 8), BitConverter.ToUInt32(src, bufPos + 12)). + AsUInt64(); - bool first = true; + bufPos += 16; - /* fold 512 to 32 step variable declarations for ISO-C90 compat. */ - Vector128 qMask = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000); - Vector128 qMask2 = Vector128.Create(0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); + qT1 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), + BitConverter.ToUInt32(src, bufPos + 8), BitConverter.ToUInt32(src, bufPos + 12)). + AsUInt64(); - uint crc; - Vector128 xTmp0; - Vector128 xTmp1; - Vector128 xTmp2; - Vector128 crcFold; + bufPos += 16; - while((len -= 64) >= 0) + qT2 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), + BitConverter.ToUInt32(src, bufPos + 8), BitConverter.ToUInt32(src, bufPos + 12)). + AsUInt64(); + + bufPos += 16; + + qT3 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), + BitConverter.ToUInt32(src, bufPos + 8), BitConverter.ToUInt32(src, bufPos + 12)). + AsUInt64(); + + bufPos += 16; + + if(first) { - qT0 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), - BitConverter.ToUInt32(src, bufPos + 8), BitConverter.ToUInt32(src, bufPos + 12)). - AsUInt64(); + first = false; - bufPos += 16; - - qT1 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), - BitConverter.ToUInt32(src, bufPos + 8), BitConverter.ToUInt32(src, bufPos + 12)). - AsUInt64(); - - bufPos += 16; - - qT2 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), - BitConverter.ToUInt32(src, bufPos + 8), BitConverter.ToUInt32(src, bufPos + 12)). - AsUInt64(); - - bufPos += 16; - - qT3 = Vector128.Create(BitConverter.ToUInt32(src, bufPos), BitConverter.ToUInt32(src, bufPos + 4), - BitConverter.ToUInt32(src, bufPos + 8), BitConverter.ToUInt32(src, bufPos + 12)). - AsUInt64(); - - bufPos += 16; - - if(first) - { - first = false; - - qT0 = AdvSimd.Xor(qT0.AsUInt32(), qInitial.AsUInt32()).AsUInt64(); - } - - Fold4(ref qCRC0, ref qCRC1, ref qCRC2, ref qCRC3); - - qCRC0 = AdvSimd.Xor(qCRC0.AsUInt32(), qT0.AsUInt32()).AsUInt64(); - qCRC1 = AdvSimd.Xor(qCRC1.AsUInt32(), qT1.AsUInt32()).AsUInt64(); - qCRC2 = AdvSimd.Xor(qCRC2.AsUInt32(), qT2.AsUInt32()).AsUInt64(); - qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qT3.AsUInt32()).AsUInt64(); + qT0 = AdvSimd.Xor(qT0.AsUInt32(), qInitial.AsUInt32()).AsUInt64(); } - /* fold 512 to 32 */ + Fold4(ref qCRC0, ref qCRC1, ref qCRC2, ref qCRC3); - /* - * k1 - */ - crcFold = Vector128.Create(_crcK[0], _crcK[1], _crcK[2], _crcK[3]).AsUInt64(); - - xTmp0 = vmull_p64(qCRC0.GetLower(), crcFold.GetUpper()); - qCRC0 = vmull_p64(qCRC0.GetUpper(), crcFold.GetLower()); - qCRC1 = AdvSimd.Xor(qCRC1.AsUInt32(), xTmp0.AsUInt32()).AsUInt64(); - qCRC1 = AdvSimd.Xor(qCRC1.AsUInt32(), qCRC0.AsUInt32()).AsUInt64(); - - xTmp1 = vmull_p64(qCRC1.GetLower(), crcFold.GetUpper()); - qCRC1 = vmull_p64(qCRC1.GetUpper(), crcFold.GetLower()); - qCRC2 = AdvSimd.Xor(qCRC2.AsUInt32(), xTmp1.AsUInt32()).AsUInt64(); - qCRC2 = AdvSimd.Xor(qCRC2.AsUInt32(), qCRC1.AsUInt32()).AsUInt64(); - - xTmp2 = vmull_p64(qCRC2.GetLower(), crcFold.GetUpper()); - qCRC2 = vmull_p64(qCRC2.GetUpper(), crcFold.GetLower()); - qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), xTmp2.AsUInt32()).AsUInt64(); - qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qCRC2.AsUInt32()).AsUInt64(); - - /* - * k5 - */ - crcFold = Vector128.Create(_crcK[4], _crcK[5], _crcK[6], _crcK[7]).AsUInt64(); - - qCRC0 = qCRC3; - qCRC3 = vmull_p64(qCRC3.GetLower(), crcFold.GetLower()); - - Vector128 qCRC0B = qCRC0.AsByte(); - - qCRC0 = Vector128.Create(AdvSimd.Extract(qCRC0B, 8), AdvSimd.Extract(qCRC0B, 9), - AdvSimd.Extract(qCRC0B, 10), AdvSimd.Extract(qCRC0B, 11), - AdvSimd.Extract(qCRC0B, 12), AdvSimd.Extract(qCRC0B, 13), - AdvSimd.Extract(qCRC0B, 14), AdvSimd.Extract(qCRC0B, 15), 0, 0, 0, 0, 0, 0, 0, 0). - AsUInt64(); - - qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qCRC0.AsUInt32()).AsUInt64(); - - qCRC0 = qCRC3; - - Vector128 qCRC3B = qCRC3.AsByte(); - - qCRC3 = Vector128.Create(0, 0, 0, 0, AdvSimd.Extract(qCRC3B, 0), AdvSimd.Extract(qCRC3B, 1), - AdvSimd.Extract(qCRC3B, 2), AdvSimd.Extract(qCRC3B, 3), AdvSimd.Extract(qCRC3B, 4), - AdvSimd.Extract(qCRC3B, 5), AdvSimd.Extract(qCRC3B, 6), AdvSimd.Extract(qCRC3B, 7), - AdvSimd.Extract(qCRC3B, 8), AdvSimd.Extract(qCRC3B, 9), - AdvSimd.Extract(qCRC3B, 10), AdvSimd.Extract(qCRC3B, 11)).AsUInt64(); - - qCRC3 = vmull_p64(qCRC3.GetLower(), crcFold.GetUpper()); - qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qCRC0.AsUInt32()).AsUInt64(); - qCRC3 = AdvSimd.And(qCRC3.AsUInt32(), qMask2.AsUInt32()).AsUInt64(); - - /* - * k7 - */ - qCRC1 = qCRC3; - qCRC2 = qCRC3; - crcFold = Vector128.Create(_crcK[8], _crcK[9], _crcK[10], _crcK[11]).AsUInt64(); - - qCRC3 = vmull_p64(qCRC3.GetLower(), crcFold.GetLower()); - qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qCRC2.AsUInt32()).AsUInt64(); - qCRC3 = AdvSimd.And(qCRC3.AsUInt32(), qMask.AsUInt32()).AsUInt64(); - - qCRC2 = qCRC3; - qCRC3 = vmull_p64(qCRC3.GetLower(), crcFold.GetUpper()); - qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qCRC2.AsUInt32()).AsUInt64(); - qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qCRC1.AsUInt32()).AsUInt64(); - - /* - * could just as well write q_crc3[2], doing a movaps and truncating, but - * no real advantage - it's a tiny bit slower per call, while no additional CPUs - * would be supported by only requiring SSSE3 and CLMUL instead of SSE4.1 + CLMUL - */ - return ~AdvSimd.Extract(qCRC3.AsUInt32(), 2); + qCRC0 = AdvSimd.Xor(qCRC0.AsUInt32(), qT0.AsUInt32()).AsUInt64(); + qCRC1 = AdvSimd.Xor(qCRC1.AsUInt32(), qT1.AsUInt32()).AsUInt64(); + qCRC2 = AdvSimd.Xor(qCRC2.AsUInt32(), qT2.AsUInt32()).AsUInt64(); + qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qT3.AsUInt32()).AsUInt64(); } + + /* fold 512 to 32 */ + + /* + * k1 + */ + crcFold = Vector128.Create(_crcK[0], _crcK[1], _crcK[2], _crcK[3]).AsUInt64(); + + xTmp0 = vmull_p64(qCRC0.GetLower(), crcFold.GetUpper()); + qCRC0 = vmull_p64(qCRC0.GetUpper(), crcFold.GetLower()); + qCRC1 = AdvSimd.Xor(qCRC1.AsUInt32(), xTmp0.AsUInt32()).AsUInt64(); + qCRC1 = AdvSimd.Xor(qCRC1.AsUInt32(), qCRC0.AsUInt32()).AsUInt64(); + + xTmp1 = vmull_p64(qCRC1.GetLower(), crcFold.GetUpper()); + qCRC1 = vmull_p64(qCRC1.GetUpper(), crcFold.GetLower()); + qCRC2 = AdvSimd.Xor(qCRC2.AsUInt32(), xTmp1.AsUInt32()).AsUInt64(); + qCRC2 = AdvSimd.Xor(qCRC2.AsUInt32(), qCRC1.AsUInt32()).AsUInt64(); + + xTmp2 = vmull_p64(qCRC2.GetLower(), crcFold.GetUpper()); + qCRC2 = vmull_p64(qCRC2.GetUpper(), crcFold.GetLower()); + qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), xTmp2.AsUInt32()).AsUInt64(); + qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qCRC2.AsUInt32()).AsUInt64(); + + /* + * k5 + */ + crcFold = Vector128.Create(_crcK[4], _crcK[5], _crcK[6], _crcK[7]).AsUInt64(); + + qCRC0 = qCRC3; + qCRC3 = vmull_p64(qCRC3.GetLower(), crcFold.GetLower()); + + Vector128 qCRC0B = qCRC0.AsByte(); + + qCRC0 = Vector128.Create(AdvSimd.Extract(qCRC0B, 8), AdvSimd.Extract(qCRC0B, 9), AdvSimd.Extract(qCRC0B, 10), + AdvSimd.Extract(qCRC0B, 11), AdvSimd.Extract(qCRC0B, 12), AdvSimd.Extract(qCRC0B, 13), + AdvSimd.Extract(qCRC0B, 14), AdvSimd.Extract(qCRC0B, 15), 0, 0, 0, 0, 0, 0, 0, 0). + AsUInt64(); + + qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qCRC0.AsUInt32()).AsUInt64(); + + qCRC0 = qCRC3; + + Vector128 qCRC3B = qCRC3.AsByte(); + + qCRC3 = Vector128.Create(0, 0, 0, 0, AdvSimd.Extract(qCRC3B, 0), AdvSimd.Extract(qCRC3B, 1), + AdvSimd.Extract(qCRC3B, 2), AdvSimd.Extract(qCRC3B, 3), AdvSimd.Extract(qCRC3B, 4), + AdvSimd.Extract(qCRC3B, 5), AdvSimd.Extract(qCRC3B, 6), AdvSimd.Extract(qCRC3B, 7), + AdvSimd.Extract(qCRC3B, 8), AdvSimd.Extract(qCRC3B, 9), AdvSimd.Extract(qCRC3B, 10), + AdvSimd.Extract(qCRC3B, 11)).AsUInt64(); + + qCRC3 = vmull_p64(qCRC3.GetLower(), crcFold.GetUpper()); + qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qCRC0.AsUInt32()).AsUInt64(); + qCRC3 = AdvSimd.And(qCRC3.AsUInt32(), qMask2.AsUInt32()).AsUInt64(); + + /* + * k7 + */ + qCRC1 = qCRC3; + qCRC2 = qCRC3; + crcFold = Vector128.Create(_crcK[8], _crcK[9], _crcK[10], _crcK[11]).AsUInt64(); + + qCRC3 = vmull_p64(qCRC3.GetLower(), crcFold.GetLower()); + qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qCRC2.AsUInt32()).AsUInt64(); + qCRC3 = AdvSimd.And(qCRC3.AsUInt32(), qMask.AsUInt32()).AsUInt64(); + + qCRC2 = qCRC3; + qCRC3 = vmull_p64(qCRC3.GetLower(), crcFold.GetUpper()); + qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qCRC2.AsUInt32()).AsUInt64(); + qCRC3 = AdvSimd.Xor(qCRC3.AsUInt32(), qCRC1.AsUInt32()).AsUInt64(); + + /* + * could just as well write q_crc3[2], doing a movaps and truncating, but + * no real advantage - it's a tiny bit slower per call, while no additional CPUs + * would be supported by only requiring SSSE3 and CLMUL instead of SSE4.1 + CLMUL + */ + return ~AdvSimd.Extract(qCRC3.AsUInt32(), 2); } } \ No newline at end of file diff --git a/Aaru6.Checksums/CRC32Context.cs b/Aaru6.Checksums/CRC32Context.cs index 4891b93..8bf533b 100644 --- a/Aaru6.Checksums/CRC32Context.cs +++ b/Aaru6.Checksums/CRC32Context.cs @@ -40,642 +40,617 @@ using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; using Aaru6.Checksums.CRC32; -namespace Aaru6.Checksums +namespace Aaru6.Checksums; + +/// +/// Implements a CRC32 algorithm +public sealed class Crc32Context : IChecksum { - /// - /// Implements a CRC32 algorithm - public sealed class Crc32Context : IChecksum + const uint CRC32_ISO_POLY = 0xEDB88320; + const uint CRC32_ISO_SEED = 0xFFFFFFFF; + + internal static readonly uint[][] _isoCrc32Table = { - const uint CRC32_ISO_POLY = 0xEDB88320; - const uint CRC32_ISO_SEED = 0xFFFFFFFF; - - internal static readonly uint[][] _isoCrc32Table = + new uint[] { - new uint[] - { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, - 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, - 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, - 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, - 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, - 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, - 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, - 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, - 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, - 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, - 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, - 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, - 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, - 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, - 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, - 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, - 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, - 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, - 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, - 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, - 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, - 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, - 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, - 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, - 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, - 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, - 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, - 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, - 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D - }, - new uint[] - { - 0x00000000, 0x191B3141, 0x32366282, 0x2B2D53C3, 0x646CC504, 0x7D77F445, 0x565AA786, 0x4F4196C7, - 0xC8D98A08, 0xD1C2BB49, 0xFAEFE88A, 0xE3F4D9CB, 0xACB54F0C, 0xB5AE7E4D, 0x9E832D8E, 0x87981CCF, - 0x4AC21251, 0x53D92310, 0x78F470D3, 0x61EF4192, 0x2EAED755, 0x37B5E614, 0x1C98B5D7, 0x05838496, - 0x821B9859, 0x9B00A918, 0xB02DFADB, 0xA936CB9A, 0xE6775D5D, 0xFF6C6C1C, 0xD4413FDF, 0xCD5A0E9E, - 0x958424A2, 0x8C9F15E3, 0xA7B24620, 0xBEA97761, 0xF1E8E1A6, 0xE8F3D0E7, 0xC3DE8324, 0xDAC5B265, - 0x5D5DAEAA, 0x44469FEB, 0x6F6BCC28, 0x7670FD69, 0x39316BAE, 0x202A5AEF, 0x0B07092C, 0x121C386D, - 0xDF4636F3, 0xC65D07B2, 0xED705471, 0xF46B6530, 0xBB2AF3F7, 0xA231C2B6, 0x891C9175, 0x9007A034, - 0x179FBCFB, 0x0E848DBA, 0x25A9DE79, 0x3CB2EF38, 0x73F379FF, 0x6AE848BE, 0x41C51B7D, 0x58DE2A3C, - 0xF0794F05, 0xE9627E44, 0xC24F2D87, 0xDB541CC6, 0x94158A01, 0x8D0EBB40, 0xA623E883, 0xBF38D9C2, - 0x38A0C50D, 0x21BBF44C, 0x0A96A78F, 0x138D96CE, 0x5CCC0009, 0x45D73148, 0x6EFA628B, 0x77E153CA, - 0xBABB5D54, 0xA3A06C15, 0x888D3FD6, 0x91960E97, 0xDED79850, 0xC7CCA911, 0xECE1FAD2, 0xF5FACB93, - 0x7262D75C, 0x6B79E61D, 0x4054B5DE, 0x594F849F, 0x160E1258, 0x0F152319, 0x243870DA, 0x3D23419B, - 0x65FD6BA7, 0x7CE65AE6, 0x57CB0925, 0x4ED03864, 0x0191AEA3, 0x188A9FE2, 0x33A7CC21, 0x2ABCFD60, - 0xAD24E1AF, 0xB43FD0EE, 0x9F12832D, 0x8609B26C, 0xC94824AB, 0xD05315EA, 0xFB7E4629, 0xE2657768, - 0x2F3F79F6, 0x362448B7, 0x1D091B74, 0x04122A35, 0x4B53BCF2, 0x52488DB3, 0x7965DE70, 0x607EEF31, - 0xE7E6F3FE, 0xFEFDC2BF, 0xD5D0917C, 0xCCCBA03D, 0x838A36FA, 0x9A9107BB, 0xB1BC5478, 0xA8A76539, - 0x3B83984B, 0x2298A90A, 0x09B5FAC9, 0x10AECB88, 0x5FEF5D4F, 0x46F46C0E, 0x6DD93FCD, 0x74C20E8C, - 0xF35A1243, 0xEA412302, 0xC16C70C1, 0xD8774180, 0x9736D747, 0x8E2DE606, 0xA500B5C5, 0xBC1B8484, - 0x71418A1A, 0x685ABB5B, 0x4377E898, 0x5A6CD9D9, 0x152D4F1E, 0x0C367E5F, 0x271B2D9C, 0x3E001CDD, - 0xB9980012, 0xA0833153, 0x8BAE6290, 0x92B553D1, 0xDDF4C516, 0xC4EFF457, 0xEFC2A794, 0xF6D996D5, - 0xAE07BCE9, 0xB71C8DA8, 0x9C31DE6B, 0x852AEF2A, 0xCA6B79ED, 0xD37048AC, 0xF85D1B6F, 0xE1462A2E, - 0x66DE36E1, 0x7FC507A0, 0x54E85463, 0x4DF36522, 0x02B2F3E5, 0x1BA9C2A4, 0x30849167, 0x299FA026, - 0xE4C5AEB8, 0xFDDE9FF9, 0xD6F3CC3A, 0xCFE8FD7B, 0x80A96BBC, 0x99B25AFD, 0xB29F093E, 0xAB84387F, - 0x2C1C24B0, 0x350715F1, 0x1E2A4632, 0x07317773, 0x4870E1B4, 0x516BD0F5, 0x7A468336, 0x635DB277, - 0xCBFAD74E, 0xD2E1E60F, 0xF9CCB5CC, 0xE0D7848D, 0xAF96124A, 0xB68D230B, 0x9DA070C8, 0x84BB4189, - 0x03235D46, 0x1A386C07, 0x31153FC4, 0x280E0E85, 0x674F9842, 0x7E54A903, 0x5579FAC0, 0x4C62CB81, - 0x8138C51F, 0x9823F45E, 0xB30EA79D, 0xAA1596DC, 0xE554001B, 0xFC4F315A, 0xD7626299, 0xCE7953D8, - 0x49E14F17, 0x50FA7E56, 0x7BD72D95, 0x62CC1CD4, 0x2D8D8A13, 0x3496BB52, 0x1FBBE891, 0x06A0D9D0, - 0x5E7EF3EC, 0x4765C2AD, 0x6C48916E, 0x7553A02F, 0x3A1236E8, 0x230907A9, 0x0824546A, 0x113F652B, - 0x96A779E4, 0x8FBC48A5, 0xA4911B66, 0xBD8A2A27, 0xF2CBBCE0, 0xEBD08DA1, 0xC0FDDE62, 0xD9E6EF23, - 0x14BCE1BD, 0x0DA7D0FC, 0x268A833F, 0x3F91B27E, 0x70D024B9, 0x69CB15F8, 0x42E6463B, 0x5BFD777A, - 0xDC656BB5, 0xC57E5AF4, 0xEE530937, 0xF7483876, 0xB809AEB1, 0xA1129FF0, 0x8A3FCC33, 0x9324FD72 - }, - new uint[] - { - 0x00000000, 0x01C26A37, 0x0384D46E, 0x0246BE59, 0x0709A8DC, 0x06CBC2EB, 0x048D7CB2, 0x054F1685, - 0x0E1351B8, 0x0FD13B8F, 0x0D9785D6, 0x0C55EFE1, 0x091AF964, 0x08D89353, 0x0A9E2D0A, 0x0B5C473D, - 0x1C26A370, 0x1DE4C947, 0x1FA2771E, 0x1E601D29, 0x1B2F0BAC, 0x1AED619B, 0x18ABDFC2, 0x1969B5F5, - 0x1235F2C8, 0x13F798FF, 0x11B126A6, 0x10734C91, 0x153C5A14, 0x14FE3023, 0x16B88E7A, 0x177AE44D, - 0x384D46E0, 0x398F2CD7, 0x3BC9928E, 0x3A0BF8B9, 0x3F44EE3C, 0x3E86840B, 0x3CC03A52, 0x3D025065, - 0x365E1758, 0x379C7D6F, 0x35DAC336, 0x3418A901, 0x3157BF84, 0x3095D5B3, 0x32D36BEA, 0x331101DD, - 0x246BE590, 0x25A98FA7, 0x27EF31FE, 0x262D5BC9, 0x23624D4C, 0x22A0277B, 0x20E69922, 0x2124F315, - 0x2A78B428, 0x2BBADE1F, 0x29FC6046, 0x283E0A71, 0x2D711CF4, 0x2CB376C3, 0x2EF5C89A, 0x2F37A2AD, - 0x709A8DC0, 0x7158E7F7, 0x731E59AE, 0x72DC3399, 0x7793251C, 0x76514F2B, 0x7417F172, 0x75D59B45, - 0x7E89DC78, 0x7F4BB64F, 0x7D0D0816, 0x7CCF6221, 0x798074A4, 0x78421E93, 0x7A04A0CA, 0x7BC6CAFD, - 0x6CBC2EB0, 0x6D7E4487, 0x6F38FADE, 0x6EFA90E9, 0x6BB5866C, 0x6A77EC5B, 0x68315202, 0x69F33835, - 0x62AF7F08, 0x636D153F, 0x612BAB66, 0x60E9C151, 0x65A6D7D4, 0x6464BDE3, 0x662203BA, 0x67E0698D, - 0x48D7CB20, 0x4915A117, 0x4B531F4E, 0x4A917579, 0x4FDE63FC, 0x4E1C09CB, 0x4C5AB792, 0x4D98DDA5, - 0x46C49A98, 0x4706F0AF, 0x45404EF6, 0x448224C1, 0x41CD3244, 0x400F5873, 0x4249E62A, 0x438B8C1D, - 0x54F16850, 0x55330267, 0x5775BC3E, 0x56B7D609, 0x53F8C08C, 0x523AAABB, 0x507C14E2, 0x51BE7ED5, - 0x5AE239E8, 0x5B2053DF, 0x5966ED86, 0x58A487B1, 0x5DEB9134, 0x5C29FB03, 0x5E6F455A, 0x5FAD2F6D, - 0xE1351B80, 0xE0F771B7, 0xE2B1CFEE, 0xE373A5D9, 0xE63CB35C, 0xE7FED96B, 0xE5B86732, 0xE47A0D05, - 0xEF264A38, 0xEEE4200F, 0xECA29E56, 0xED60F461, 0xE82FE2E4, 0xE9ED88D3, 0xEBAB368A, 0xEA695CBD, - 0xFD13B8F0, 0xFCD1D2C7, 0xFE976C9E, 0xFF5506A9, 0xFA1A102C, 0xFBD87A1B, 0xF99EC442, 0xF85CAE75, - 0xF300E948, 0xF2C2837F, 0xF0843D26, 0xF1465711, 0xF4094194, 0xF5CB2BA3, 0xF78D95FA, 0xF64FFFCD, - 0xD9785D60, 0xD8BA3757, 0xDAFC890E, 0xDB3EE339, 0xDE71F5BC, 0xDFB39F8B, 0xDDF521D2, 0xDC374BE5, - 0xD76B0CD8, 0xD6A966EF, 0xD4EFD8B6, 0xD52DB281, 0xD062A404, 0xD1A0CE33, 0xD3E6706A, 0xD2241A5D, - 0xC55EFE10, 0xC49C9427, 0xC6DA2A7E, 0xC7184049, 0xC25756CC, 0xC3953CFB, 0xC1D382A2, 0xC011E895, - 0xCB4DAFA8, 0xCA8FC59F, 0xC8C97BC6, 0xC90B11F1, 0xCC440774, 0xCD866D43, 0xCFC0D31A, 0xCE02B92D, - 0x91AF9640, 0x906DFC77, 0x922B422E, 0x93E92819, 0x96A63E9C, 0x976454AB, 0x9522EAF2, 0x94E080C5, - 0x9FBCC7F8, 0x9E7EADCF, 0x9C381396, 0x9DFA79A1, 0x98B56F24, 0x99770513, 0x9B31BB4A, 0x9AF3D17D, - 0x8D893530, 0x8C4B5F07, 0x8E0DE15E, 0x8FCF8B69, 0x8A809DEC, 0x8B42F7DB, 0x89044982, 0x88C623B5, - 0x839A6488, 0x82580EBF, 0x801EB0E6, 0x81DCDAD1, 0x8493CC54, 0x8551A663, 0x8717183A, 0x86D5720D, - 0xA9E2D0A0, 0xA820BA97, 0xAA6604CE, 0xABA46EF9, 0xAEEB787C, 0xAF29124B, 0xAD6FAC12, 0xACADC625, - 0xA7F18118, 0xA633EB2F, 0xA4755576, 0xA5B73F41, 0xA0F829C4, 0xA13A43F3, 0xA37CFDAA, 0xA2BE979D, - 0xB5C473D0, 0xB40619E7, 0xB640A7BE, 0xB782CD89, 0xB2CDDB0C, 0xB30FB13B, 0xB1490F62, 0xB08B6555, - 0xBBD72268, 0xBA15485F, 0xB853F606, 0xB9919C31, 0xBCDE8AB4, 0xBD1CE083, 0xBF5A5EDA, 0xBE9834ED - }, - new uint[] - { - 0x00000000, 0xB8BC6765, 0xAA09C88B, 0x12B5AFEE, 0x8F629757, 0x37DEF032, 0x256B5FDC, 0x9DD738B9, - 0xC5B428EF, 0x7D084F8A, 0x6FBDE064, 0xD7018701, 0x4AD6BFB8, 0xF26AD8DD, 0xE0DF7733, 0x58631056, - 0x5019579F, 0xE8A530FA, 0xFA109F14, 0x42ACF871, 0xDF7BC0C8, 0x67C7A7AD, 0x75720843, 0xCDCE6F26, - 0x95AD7F70, 0x2D111815, 0x3FA4B7FB, 0x8718D09E, 0x1ACFE827, 0xA2738F42, 0xB0C620AC, 0x087A47C9, - 0xA032AF3E, 0x188EC85B, 0x0A3B67B5, 0xB28700D0, 0x2F503869, 0x97EC5F0C, 0x8559F0E2, 0x3DE59787, - 0x658687D1, 0xDD3AE0B4, 0xCF8F4F5A, 0x7733283F, 0xEAE41086, 0x525877E3, 0x40EDD80D, 0xF851BF68, - 0xF02BF8A1, 0x48979FC4, 0x5A22302A, 0xE29E574F, 0x7F496FF6, 0xC7F50893, 0xD540A77D, 0x6DFCC018, - 0x359FD04E, 0x8D23B72B, 0x9F9618C5, 0x272A7FA0, 0xBAFD4719, 0x0241207C, 0x10F48F92, 0xA848E8F7, - 0x9B14583D, 0x23A83F58, 0x311D90B6, 0x89A1F7D3, 0x1476CF6A, 0xACCAA80F, 0xBE7F07E1, 0x06C36084, - 0x5EA070D2, 0xE61C17B7, 0xF4A9B859, 0x4C15DF3C, 0xD1C2E785, 0x697E80E0, 0x7BCB2F0E, 0xC377486B, - 0xCB0D0FA2, 0x73B168C7, 0x6104C729, 0xD9B8A04C, 0x446F98F5, 0xFCD3FF90, 0xEE66507E, 0x56DA371B, - 0x0EB9274D, 0xB6054028, 0xA4B0EFC6, 0x1C0C88A3, 0x81DBB01A, 0x3967D77F, 0x2BD27891, 0x936E1FF4, - 0x3B26F703, 0x839A9066, 0x912F3F88, 0x299358ED, 0xB4446054, 0x0CF80731, 0x1E4DA8DF, 0xA6F1CFBA, - 0xFE92DFEC, 0x462EB889, 0x549B1767, 0xEC277002, 0x71F048BB, 0xC94C2FDE, 0xDBF98030, 0x6345E755, - 0x6B3FA09C, 0xD383C7F9, 0xC1366817, 0x798A0F72, 0xE45D37CB, 0x5CE150AE, 0x4E54FF40, 0xF6E89825, - 0xAE8B8873, 0x1637EF16, 0x048240F8, 0xBC3E279D, 0x21E91F24, 0x99557841, 0x8BE0D7AF, 0x335CB0CA, - 0xED59B63B, 0x55E5D15E, 0x47507EB0, 0xFFEC19D5, 0x623B216C, 0xDA874609, 0xC832E9E7, 0x708E8E82, - 0x28ED9ED4, 0x9051F9B1, 0x82E4565F, 0x3A58313A, 0xA78F0983, 0x1F336EE6, 0x0D86C108, 0xB53AA66D, - 0xBD40E1A4, 0x05FC86C1, 0x1749292F, 0xAFF54E4A, 0x322276F3, 0x8A9E1196, 0x982BBE78, 0x2097D91D, - 0x78F4C94B, 0xC048AE2E, 0xD2FD01C0, 0x6A4166A5, 0xF7965E1C, 0x4F2A3979, 0x5D9F9697, 0xE523F1F2, - 0x4D6B1905, 0xF5D77E60, 0xE762D18E, 0x5FDEB6EB, 0xC2098E52, 0x7AB5E937, 0x680046D9, 0xD0BC21BC, - 0x88DF31EA, 0x3063568F, 0x22D6F961, 0x9A6A9E04, 0x07BDA6BD, 0xBF01C1D8, 0xADB46E36, 0x15080953, - 0x1D724E9A, 0xA5CE29FF, 0xB77B8611, 0x0FC7E174, 0x9210D9CD, 0x2AACBEA8, 0x38191146, 0x80A57623, - 0xD8C66675, 0x607A0110, 0x72CFAEFE, 0xCA73C99B, 0x57A4F122, 0xEF189647, 0xFDAD39A9, 0x45115ECC, - 0x764DEE06, 0xCEF18963, 0xDC44268D, 0x64F841E8, 0xF92F7951, 0x41931E34, 0x5326B1DA, 0xEB9AD6BF, - 0xB3F9C6E9, 0x0B45A18C, 0x19F00E62, 0xA14C6907, 0x3C9B51BE, 0x842736DB, 0x96929935, 0x2E2EFE50, - 0x2654B999, 0x9EE8DEFC, 0x8C5D7112, 0x34E11677, 0xA9362ECE, 0x118A49AB, 0x033FE645, 0xBB838120, - 0xE3E09176, 0x5B5CF613, 0x49E959FD, 0xF1553E98, 0x6C820621, 0xD43E6144, 0xC68BCEAA, 0x7E37A9CF, - 0xD67F4138, 0x6EC3265D, 0x7C7689B3, 0xC4CAEED6, 0x591DD66F, 0xE1A1B10A, 0xF3141EE4, 0x4BA87981, - 0x13CB69D7, 0xAB770EB2, 0xB9C2A15C, 0x017EC639, 0x9CA9FE80, 0x241599E5, 0x36A0360B, 0x8E1C516E, - 0x866616A7, 0x3EDA71C2, 0x2C6FDE2C, 0x94D3B949, 0x090481F0, 0xB1B8E695, 0xA30D497B, 0x1BB12E1E, - 0x43D23E48, 0xFB6E592D, 0xE9DBF6C3, 0x516791A6, 0xCCB0A91F, 0x740CCE7A, 0x66B96194, 0xDE0506F1 - }, - new uint[] - { - 0x00000000, 0x3D6029B0, 0x7AC05360, 0x47A07AD0, 0xF580A6C0, 0xC8E08F70, 0x8F40F5A0, 0xB220DC10, - 0x30704BC1, 0x0D106271, 0x4AB018A1, 0x77D03111, 0xC5F0ED01, 0xF890C4B1, 0xBF30BE61, 0x825097D1, - 0x60E09782, 0x5D80BE32, 0x1A20C4E2, 0x2740ED52, 0x95603142, 0xA80018F2, 0xEFA06222, 0xD2C04B92, - 0x5090DC43, 0x6DF0F5F3, 0x2A508F23, 0x1730A693, 0xA5107A83, 0x98705333, 0xDFD029E3, 0xE2B00053, - 0xC1C12F04, 0xFCA106B4, 0xBB017C64, 0x866155D4, 0x344189C4, 0x0921A074, 0x4E81DAA4, 0x73E1F314, - 0xF1B164C5, 0xCCD14D75, 0x8B7137A5, 0xB6111E15, 0x0431C205, 0x3951EBB5, 0x7EF19165, 0x4391B8D5, - 0xA121B886, 0x9C419136, 0xDBE1EBE6, 0xE681C256, 0x54A11E46, 0x69C137F6, 0x2E614D26, 0x13016496, - 0x9151F347, 0xAC31DAF7, 0xEB91A027, 0xD6F18997, 0x64D15587, 0x59B17C37, 0x1E1106E7, 0x23712F57, - 0x58F35849, 0x659371F9, 0x22330B29, 0x1F532299, 0xAD73FE89, 0x9013D739, 0xD7B3ADE9, 0xEAD38459, - 0x68831388, 0x55E33A38, 0x124340E8, 0x2F236958, 0x9D03B548, 0xA0639CF8, 0xE7C3E628, 0xDAA3CF98, - 0x3813CFCB, 0x0573E67B, 0x42D39CAB, 0x7FB3B51B, 0xCD93690B, 0xF0F340BB, 0xB7533A6B, 0x8A3313DB, - 0x0863840A, 0x3503ADBA, 0x72A3D76A, 0x4FC3FEDA, 0xFDE322CA, 0xC0830B7A, 0x872371AA, 0xBA43581A, - 0x9932774D, 0xA4525EFD, 0xE3F2242D, 0xDE920D9D, 0x6CB2D18D, 0x51D2F83D, 0x167282ED, 0x2B12AB5D, - 0xA9423C8C, 0x9422153C, 0xD3826FEC, 0xEEE2465C, 0x5CC29A4C, 0x61A2B3FC, 0x2602C92C, 0x1B62E09C, - 0xF9D2E0CF, 0xC4B2C97F, 0x8312B3AF, 0xBE729A1F, 0x0C52460F, 0x31326FBF, 0x7692156F, 0x4BF23CDF, - 0xC9A2AB0E, 0xF4C282BE, 0xB362F86E, 0x8E02D1DE, 0x3C220DCE, 0x0142247E, 0x46E25EAE, 0x7B82771E, - 0xB1E6B092, 0x8C869922, 0xCB26E3F2, 0xF646CA42, 0x44661652, 0x79063FE2, 0x3EA64532, 0x03C66C82, - 0x8196FB53, 0xBCF6D2E3, 0xFB56A833, 0xC6368183, 0x74165D93, 0x49767423, 0x0ED60EF3, 0x33B62743, - 0xD1062710, 0xEC660EA0, 0xABC67470, 0x96A65DC0, 0x248681D0, 0x19E6A860, 0x5E46D2B0, 0x6326FB00, - 0xE1766CD1, 0xDC164561, 0x9BB63FB1, 0xA6D61601, 0x14F6CA11, 0x2996E3A1, 0x6E369971, 0x5356B0C1, - 0x70279F96, 0x4D47B626, 0x0AE7CCF6, 0x3787E546, 0x85A73956, 0xB8C710E6, 0xFF676A36, 0xC2074386, - 0x4057D457, 0x7D37FDE7, 0x3A978737, 0x07F7AE87, 0xB5D77297, 0x88B75B27, 0xCF1721F7, 0xF2770847, - 0x10C70814, 0x2DA721A4, 0x6A075B74, 0x576772C4, 0xE547AED4, 0xD8278764, 0x9F87FDB4, 0xA2E7D404, - 0x20B743D5, 0x1DD76A65, 0x5A7710B5, 0x67173905, 0xD537E515, 0xE857CCA5, 0xAFF7B675, 0x92979FC5, - 0xE915E8DB, 0xD475C16B, 0x93D5BBBB, 0xAEB5920B, 0x1C954E1B, 0x21F567AB, 0x66551D7B, 0x5B3534CB, - 0xD965A31A, 0xE4058AAA, 0xA3A5F07A, 0x9EC5D9CA, 0x2CE505DA, 0x11852C6A, 0x562556BA, 0x6B457F0A, - 0x89F57F59, 0xB49556E9, 0xF3352C39, 0xCE550589, 0x7C75D999, 0x4115F029, 0x06B58AF9, 0x3BD5A349, - 0xB9853498, 0x84E51D28, 0xC34567F8, 0xFE254E48, 0x4C059258, 0x7165BBE8, 0x36C5C138, 0x0BA5E888, - 0x28D4C7DF, 0x15B4EE6F, 0x521494BF, 0x6F74BD0F, 0xDD54611F, 0xE03448AF, 0xA794327F, 0x9AF41BCF, - 0x18A48C1E, 0x25C4A5AE, 0x6264DF7E, 0x5F04F6CE, 0xED242ADE, 0xD044036E, 0x97E479BE, 0xAA84500E, - 0x4834505D, 0x755479ED, 0x32F4033D, 0x0F942A8D, 0xBDB4F69D, 0x80D4DF2D, 0xC774A5FD, 0xFA148C4D, - 0x78441B9C, 0x4524322C, 0x028448FC, 0x3FE4614C, 0x8DC4BD5C, 0xB0A494EC, 0xF704EE3C, 0xCA64C78C - }, - new uint[] - { - 0x00000000, 0xCB5CD3A5, 0x4DC8A10B, 0x869472AE, 0x9B914216, 0x50CD91B3, 0xD659E31D, 0x1D0530B8, - 0xEC53826D, 0x270F51C8, 0xA19B2366, 0x6AC7F0C3, 0x77C2C07B, 0xBC9E13DE, 0x3A0A6170, 0xF156B2D5, - 0x03D6029B, 0xC88AD13E, 0x4E1EA390, 0x85427035, 0x9847408D, 0x531B9328, 0xD58FE186, 0x1ED33223, - 0xEF8580F6, 0x24D95353, 0xA24D21FD, 0x6911F258, 0x7414C2E0, 0xBF481145, 0x39DC63EB, 0xF280B04E, - 0x07AC0536, 0xCCF0D693, 0x4A64A43D, 0x81387798, 0x9C3D4720, 0x57619485, 0xD1F5E62B, 0x1AA9358E, - 0xEBFF875B, 0x20A354FE, 0xA6372650, 0x6D6BF5F5, 0x706EC54D, 0xBB3216E8, 0x3DA66446, 0xF6FAB7E3, - 0x047A07AD, 0xCF26D408, 0x49B2A6A6, 0x82EE7503, 0x9FEB45BB, 0x54B7961E, 0xD223E4B0, 0x197F3715, - 0xE82985C0, 0x23755665, 0xA5E124CB, 0x6EBDF76E, 0x73B8C7D6, 0xB8E41473, 0x3E7066DD, 0xF52CB578, - 0x0F580A6C, 0xC404D9C9, 0x4290AB67, 0x89CC78C2, 0x94C9487A, 0x5F959BDF, 0xD901E971, 0x125D3AD4, - 0xE30B8801, 0x28575BA4, 0xAEC3290A, 0x659FFAAF, 0x789ACA17, 0xB3C619B2, 0x35526B1C, 0xFE0EB8B9, - 0x0C8E08F7, 0xC7D2DB52, 0x4146A9FC, 0x8A1A7A59, 0x971F4AE1, 0x5C439944, 0xDAD7EBEA, 0x118B384F, - 0xE0DD8A9A, 0x2B81593F, 0xAD152B91, 0x6649F834, 0x7B4CC88C, 0xB0101B29, 0x36846987, 0xFDD8BA22, - 0x08F40F5A, 0xC3A8DCFF, 0x453CAE51, 0x8E607DF4, 0x93654D4C, 0x58399EE9, 0xDEADEC47, 0x15F13FE2, - 0xE4A78D37, 0x2FFB5E92, 0xA96F2C3C, 0x6233FF99, 0x7F36CF21, 0xB46A1C84, 0x32FE6E2A, 0xF9A2BD8F, - 0x0B220DC1, 0xC07EDE64, 0x46EAACCA, 0x8DB67F6F, 0x90B34FD7, 0x5BEF9C72, 0xDD7BEEDC, 0x16273D79, - 0xE7718FAC, 0x2C2D5C09, 0xAAB92EA7, 0x61E5FD02, 0x7CE0CDBA, 0xB7BC1E1F, 0x31286CB1, 0xFA74BF14, - 0x1EB014D8, 0xD5ECC77D, 0x5378B5D3, 0x98246676, 0x852156CE, 0x4E7D856B, 0xC8E9F7C5, 0x03B52460, - 0xF2E396B5, 0x39BF4510, 0xBF2B37BE, 0x7477E41B, 0x6972D4A3, 0xA22E0706, 0x24BA75A8, 0xEFE6A60D, - 0x1D661643, 0xD63AC5E6, 0x50AEB748, 0x9BF264ED, 0x86F75455, 0x4DAB87F0, 0xCB3FF55E, 0x006326FB, - 0xF135942E, 0x3A69478B, 0xBCFD3525, 0x77A1E680, 0x6AA4D638, 0xA1F8059D, 0x276C7733, 0xEC30A496, - 0x191C11EE, 0xD240C24B, 0x54D4B0E5, 0x9F886340, 0x828D53F8, 0x49D1805D, 0xCF45F2F3, 0x04192156, - 0xF54F9383, 0x3E134026, 0xB8873288, 0x73DBE12D, 0x6EDED195, 0xA5820230, 0x2316709E, 0xE84AA33B, - 0x1ACA1375, 0xD196C0D0, 0x5702B27E, 0x9C5E61DB, 0x815B5163, 0x4A0782C6, 0xCC93F068, 0x07CF23CD, - 0xF6999118, 0x3DC542BD, 0xBB513013, 0x700DE3B6, 0x6D08D30E, 0xA65400AB, 0x20C07205, 0xEB9CA1A0, - 0x11E81EB4, 0xDAB4CD11, 0x5C20BFBF, 0x977C6C1A, 0x8A795CA2, 0x41258F07, 0xC7B1FDA9, 0x0CED2E0C, - 0xFDBB9CD9, 0x36E74F7C, 0xB0733DD2, 0x7B2FEE77, 0x662ADECF, 0xAD760D6A, 0x2BE27FC4, 0xE0BEAC61, - 0x123E1C2F, 0xD962CF8A, 0x5FF6BD24, 0x94AA6E81, 0x89AF5E39, 0x42F38D9C, 0xC467FF32, 0x0F3B2C97, - 0xFE6D9E42, 0x35314DE7, 0xB3A53F49, 0x78F9ECEC, 0x65FCDC54, 0xAEA00FF1, 0x28347D5F, 0xE368AEFA, - 0x16441B82, 0xDD18C827, 0x5B8CBA89, 0x90D0692C, 0x8DD55994, 0x46898A31, 0xC01DF89F, 0x0B412B3A, - 0xFA1799EF, 0x314B4A4A, 0xB7DF38E4, 0x7C83EB41, 0x6186DBF9, 0xAADA085C, 0x2C4E7AF2, 0xE712A957, - 0x15921919, 0xDECECABC, 0x585AB812, 0x93066BB7, 0x8E035B0F, 0x455F88AA, 0xC3CBFA04, 0x089729A1, - 0xF9C19B74, 0x329D48D1, 0xB4093A7F, 0x7F55E9DA, 0x6250D962, 0xA90C0AC7, 0x2F987869, 0xE4C4ABCC - }, - new uint[] - { - 0x00000000, 0xA6770BB4, 0x979F1129, 0x31E81A9D, 0xF44F2413, 0x52382FA7, 0x63D0353A, 0xC5A73E8E, - 0x33EF4E67, 0x959845D3, 0xA4705F4E, 0x020754FA, 0xC7A06A74, 0x61D761C0, 0x503F7B5D, 0xF64870E9, - 0x67DE9CCE, 0xC1A9977A, 0xF0418DE7, 0x56368653, 0x9391B8DD, 0x35E6B369, 0x040EA9F4, 0xA279A240, - 0x5431D2A9, 0xF246D91D, 0xC3AEC380, 0x65D9C834, 0xA07EF6BA, 0x0609FD0E, 0x37E1E793, 0x9196EC27, - 0xCFBD399C, 0x69CA3228, 0x582228B5, 0xFE552301, 0x3BF21D8F, 0x9D85163B, 0xAC6D0CA6, 0x0A1A0712, - 0xFC5277FB, 0x5A257C4F, 0x6BCD66D2, 0xCDBA6D66, 0x081D53E8, 0xAE6A585C, 0x9F8242C1, 0x39F54975, - 0xA863A552, 0x0E14AEE6, 0x3FFCB47B, 0x998BBFCF, 0x5C2C8141, 0xFA5B8AF5, 0xCBB39068, 0x6DC49BDC, - 0x9B8CEB35, 0x3DFBE081, 0x0C13FA1C, 0xAA64F1A8, 0x6FC3CF26, 0xC9B4C492, 0xF85CDE0F, 0x5E2BD5BB, - 0x440B7579, 0xE27C7ECD, 0xD3946450, 0x75E36FE4, 0xB044516A, 0x16335ADE, 0x27DB4043, 0x81AC4BF7, - 0x77E43B1E, 0xD19330AA, 0xE07B2A37, 0x460C2183, 0x83AB1F0D, 0x25DC14B9, 0x14340E24, 0xB2430590, - 0x23D5E9B7, 0x85A2E203, 0xB44AF89E, 0x123DF32A, 0xD79ACDA4, 0x71EDC610, 0x4005DC8D, 0xE672D739, - 0x103AA7D0, 0xB64DAC64, 0x87A5B6F9, 0x21D2BD4D, 0xE47583C3, 0x42028877, 0x73EA92EA, 0xD59D995E, - 0x8BB64CE5, 0x2DC14751, 0x1C295DCC, 0xBA5E5678, 0x7FF968F6, 0xD98E6342, 0xE86679DF, 0x4E11726B, - 0xB8590282, 0x1E2E0936, 0x2FC613AB, 0x89B1181F, 0x4C162691, 0xEA612D25, 0xDB8937B8, 0x7DFE3C0C, - 0xEC68D02B, 0x4A1FDB9F, 0x7BF7C102, 0xDD80CAB6, 0x1827F438, 0xBE50FF8C, 0x8FB8E511, 0x29CFEEA5, - 0xDF879E4C, 0x79F095F8, 0x48188F65, 0xEE6F84D1, 0x2BC8BA5F, 0x8DBFB1EB, 0xBC57AB76, 0x1A20A0C2, - 0x8816EAF2, 0x2E61E146, 0x1F89FBDB, 0xB9FEF06F, 0x7C59CEE1, 0xDA2EC555, 0xEBC6DFC8, 0x4DB1D47C, - 0xBBF9A495, 0x1D8EAF21, 0x2C66B5BC, 0x8A11BE08, 0x4FB68086, 0xE9C18B32, 0xD82991AF, 0x7E5E9A1B, - 0xEFC8763C, 0x49BF7D88, 0x78576715, 0xDE206CA1, 0x1B87522F, 0xBDF0599B, 0x8C184306, 0x2A6F48B2, - 0xDC27385B, 0x7A5033EF, 0x4BB82972, 0xEDCF22C6, 0x28681C48, 0x8E1F17FC, 0xBFF70D61, 0x198006D5, - 0x47ABD36E, 0xE1DCD8DA, 0xD034C247, 0x7643C9F3, 0xB3E4F77D, 0x1593FCC9, 0x247BE654, 0x820CEDE0, - 0x74449D09, 0xD23396BD, 0xE3DB8C20, 0x45AC8794, 0x800BB91A, 0x267CB2AE, 0x1794A833, 0xB1E3A387, - 0x20754FA0, 0x86024414, 0xB7EA5E89, 0x119D553D, 0xD43A6BB3, 0x724D6007, 0x43A57A9A, 0xE5D2712E, - 0x139A01C7, 0xB5ED0A73, 0x840510EE, 0x22721B5A, 0xE7D525D4, 0x41A22E60, 0x704A34FD, 0xD63D3F49, - 0xCC1D9F8B, 0x6A6A943F, 0x5B828EA2, 0xFDF58516, 0x3852BB98, 0x9E25B02C, 0xAFCDAAB1, 0x09BAA105, - 0xFFF2D1EC, 0x5985DA58, 0x686DC0C5, 0xCE1ACB71, 0x0BBDF5FF, 0xADCAFE4B, 0x9C22E4D6, 0x3A55EF62, - 0xABC30345, 0x0DB408F1, 0x3C5C126C, 0x9A2B19D8, 0x5F8C2756, 0xF9FB2CE2, 0xC813367F, 0x6E643DCB, - 0x982C4D22, 0x3E5B4696, 0x0FB35C0B, 0xA9C457BF, 0x6C636931, 0xCA146285, 0xFBFC7818, 0x5D8B73AC, - 0x03A0A617, 0xA5D7ADA3, 0x943FB73E, 0x3248BC8A, 0xF7EF8204, 0x519889B0, 0x6070932D, 0xC6079899, - 0x304FE870, 0x9638E3C4, 0xA7D0F959, 0x01A7F2ED, 0xC400CC63, 0x6277C7D7, 0x539FDD4A, 0xF5E8D6FE, - 0x647E3AD9, 0xC209316D, 0xF3E12BF0, 0x55962044, 0x90311ECA, 0x3646157E, 0x07AE0FE3, 0xA1D90457, - 0x579174BE, 0xF1E67F0A, 0xC00E6597, 0x66796E23, 0xA3DE50AD, 0x05A95B19, 0x34414184, 0x92364A30 - }, - new uint[] - { - 0x00000000, 0xCCAA009E, 0x4225077D, 0x8E8F07E3, 0x844A0EFA, 0x48E00E64, 0xC66F0987, 0x0AC50919, - 0xD3E51BB5, 0x1F4F1B2B, 0x91C01CC8, 0x5D6A1C56, 0x57AF154F, 0x9B0515D1, 0x158A1232, 0xD92012AC, - 0x7CBB312B, 0xB01131B5, 0x3E9E3656, 0xF23436C8, 0xF8F13FD1, 0x345B3F4F, 0xBAD438AC, 0x767E3832, - 0xAF5E2A9E, 0x63F42A00, 0xED7B2DE3, 0x21D12D7D, 0x2B142464, 0xE7BE24FA, 0x69312319, 0xA59B2387, - 0xF9766256, 0x35DC62C8, 0xBB53652B, 0x77F965B5, 0x7D3C6CAC, 0xB1966C32, 0x3F196BD1, 0xF3B36B4F, - 0x2A9379E3, 0xE639797D, 0x68B67E9E, 0xA41C7E00, 0xAED97719, 0x62737787, 0xECFC7064, 0x205670FA, - 0x85CD537D, 0x496753E3, 0xC7E85400, 0x0B42549E, 0x01875D87, 0xCD2D5D19, 0x43A25AFA, 0x8F085A64, - 0x562848C8, 0x9A824856, 0x140D4FB5, 0xD8A74F2B, 0xD2624632, 0x1EC846AC, 0x9047414F, 0x5CED41D1, - 0x299DC2ED, 0xE537C273, 0x6BB8C590, 0xA712C50E, 0xADD7CC17, 0x617DCC89, 0xEFF2CB6A, 0x2358CBF4, - 0xFA78D958, 0x36D2D9C6, 0xB85DDE25, 0x74F7DEBB, 0x7E32D7A2, 0xB298D73C, 0x3C17D0DF, 0xF0BDD041, - 0x5526F3C6, 0x998CF358, 0x1703F4BB, 0xDBA9F425, 0xD16CFD3C, 0x1DC6FDA2, 0x9349FA41, 0x5FE3FADF, - 0x86C3E873, 0x4A69E8ED, 0xC4E6EF0E, 0x084CEF90, 0x0289E689, 0xCE23E617, 0x40ACE1F4, 0x8C06E16A, - 0xD0EBA0BB, 0x1C41A025, 0x92CEA7C6, 0x5E64A758, 0x54A1AE41, 0x980BAEDF, 0x1684A93C, 0xDA2EA9A2, - 0x030EBB0E, 0xCFA4BB90, 0x412BBC73, 0x8D81BCED, 0x8744B5F4, 0x4BEEB56A, 0xC561B289, 0x09CBB217, - 0xAC509190, 0x60FA910E, 0xEE7596ED, 0x22DF9673, 0x281A9F6A, 0xE4B09FF4, 0x6A3F9817, 0xA6959889, - 0x7FB58A25, 0xB31F8ABB, 0x3D908D58, 0xF13A8DC6, 0xFBFF84DF, 0x37558441, 0xB9DA83A2, 0x7570833C, - 0x533B85DA, 0x9F918544, 0x111E82A7, 0xDDB48239, 0xD7718B20, 0x1BDB8BBE, 0x95548C5D, 0x59FE8CC3, - 0x80DE9E6F, 0x4C749EF1, 0xC2FB9912, 0x0E51998C, 0x04949095, 0xC83E900B, 0x46B197E8, 0x8A1B9776, - 0x2F80B4F1, 0xE32AB46F, 0x6DA5B38C, 0xA10FB312, 0xABCABA0B, 0x6760BA95, 0xE9EFBD76, 0x2545BDE8, - 0xFC65AF44, 0x30CFAFDA, 0xBE40A839, 0x72EAA8A7, 0x782FA1BE, 0xB485A120, 0x3A0AA6C3, 0xF6A0A65D, - 0xAA4DE78C, 0x66E7E712, 0xE868E0F1, 0x24C2E06F, 0x2E07E976, 0xE2ADE9E8, 0x6C22EE0B, 0xA088EE95, - 0x79A8FC39, 0xB502FCA7, 0x3B8DFB44, 0xF727FBDA, 0xFDE2F2C3, 0x3148F25D, 0xBFC7F5BE, 0x736DF520, - 0xD6F6D6A7, 0x1A5CD639, 0x94D3D1DA, 0x5879D144, 0x52BCD85D, 0x9E16D8C3, 0x1099DF20, 0xDC33DFBE, - 0x0513CD12, 0xC9B9CD8C, 0x4736CA6F, 0x8B9CCAF1, 0x8159C3E8, 0x4DF3C376, 0xC37CC495, 0x0FD6C40B, - 0x7AA64737, 0xB60C47A9, 0x3883404A, 0xF42940D4, 0xFEEC49CD, 0x32464953, 0xBCC94EB0, 0x70634E2E, - 0xA9435C82, 0x65E95C1C, 0xEB665BFF, 0x27CC5B61, 0x2D095278, 0xE1A352E6, 0x6F2C5505, 0xA386559B, - 0x061D761C, 0xCAB77682, 0x44387161, 0x889271FF, 0x825778E6, 0x4EFD7878, 0xC0727F9B, 0x0CD87F05, - 0xD5F86DA9, 0x19526D37, 0x97DD6AD4, 0x5B776A4A, 0x51B26353, 0x9D1863CD, 0x1397642E, 0xDF3D64B0, - 0x83D02561, 0x4F7A25FF, 0xC1F5221C, 0x0D5F2282, 0x079A2B9B, 0xCB302B05, 0x45BF2CE6, 0x89152C78, - 0x50353ED4, 0x9C9F3E4A, 0x121039A9, 0xDEBA3937, 0xD47F302E, 0x18D530B0, 0x965A3753, 0x5AF037CD, - 0xFF6B144A, 0x33C114D4, 0xBD4E1337, 0x71E413A9, 0x7B211AB0, 0xB78B1A2E, 0x39041DCD, 0xF5AE1D53, - 0x2C8E0FFF, 0xE0240F61, 0x6EAB0882, 0xA201081C, 0xA8C40105, 0x646E019B, 0xEAE10678, 0x264B06E6 - } - }; - - readonly uint _finalSeed; - readonly IntPtr _nativeContext; - readonly uint[][] _table; - readonly bool _useIso; - readonly bool _useNative; - uint _hashInt; - - /// Initializes the CRC32 table and seed as CRC32-ISO - public Crc32Context() + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, + 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, + 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, + 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, + 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, + 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, + 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, + 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, + 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, + 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, + 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, + 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, + 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, + 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, + 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }, + new uint[] { - _hashInt = CRC32_ISO_SEED; - _finalSeed = CRC32_ISO_SEED; - _table = _isoCrc32Table; - _useIso = true; + 0x00000000, 0x191B3141, 0x32366282, 0x2B2D53C3, 0x646CC504, 0x7D77F445, 0x565AA786, 0x4F4196C7, 0xC8D98A08, + 0xD1C2BB49, 0xFAEFE88A, 0xE3F4D9CB, 0xACB54F0C, 0xB5AE7E4D, 0x9E832D8E, 0x87981CCF, 0x4AC21251, 0x53D92310, + 0x78F470D3, 0x61EF4192, 0x2EAED755, 0x37B5E614, 0x1C98B5D7, 0x05838496, 0x821B9859, 0x9B00A918, 0xB02DFADB, + 0xA936CB9A, 0xE6775D5D, 0xFF6C6C1C, 0xD4413FDF, 0xCD5A0E9E, 0x958424A2, 0x8C9F15E3, 0xA7B24620, 0xBEA97761, + 0xF1E8E1A6, 0xE8F3D0E7, 0xC3DE8324, 0xDAC5B265, 0x5D5DAEAA, 0x44469FEB, 0x6F6BCC28, 0x7670FD69, 0x39316BAE, + 0x202A5AEF, 0x0B07092C, 0x121C386D, 0xDF4636F3, 0xC65D07B2, 0xED705471, 0xF46B6530, 0xBB2AF3F7, 0xA231C2B6, + 0x891C9175, 0x9007A034, 0x179FBCFB, 0x0E848DBA, 0x25A9DE79, 0x3CB2EF38, 0x73F379FF, 0x6AE848BE, 0x41C51B7D, + 0x58DE2A3C, 0xF0794F05, 0xE9627E44, 0xC24F2D87, 0xDB541CC6, 0x94158A01, 0x8D0EBB40, 0xA623E883, 0xBF38D9C2, + 0x38A0C50D, 0x21BBF44C, 0x0A96A78F, 0x138D96CE, 0x5CCC0009, 0x45D73148, 0x6EFA628B, 0x77E153CA, 0xBABB5D54, + 0xA3A06C15, 0x888D3FD6, 0x91960E97, 0xDED79850, 0xC7CCA911, 0xECE1FAD2, 0xF5FACB93, 0x7262D75C, 0x6B79E61D, + 0x4054B5DE, 0x594F849F, 0x160E1258, 0x0F152319, 0x243870DA, 0x3D23419B, 0x65FD6BA7, 0x7CE65AE6, 0x57CB0925, + 0x4ED03864, 0x0191AEA3, 0x188A9FE2, 0x33A7CC21, 0x2ABCFD60, 0xAD24E1AF, 0xB43FD0EE, 0x9F12832D, 0x8609B26C, + 0xC94824AB, 0xD05315EA, 0xFB7E4629, 0xE2657768, 0x2F3F79F6, 0x362448B7, 0x1D091B74, 0x04122A35, 0x4B53BCF2, + 0x52488DB3, 0x7965DE70, 0x607EEF31, 0xE7E6F3FE, 0xFEFDC2BF, 0xD5D0917C, 0xCCCBA03D, 0x838A36FA, 0x9A9107BB, + 0xB1BC5478, 0xA8A76539, 0x3B83984B, 0x2298A90A, 0x09B5FAC9, 0x10AECB88, 0x5FEF5D4F, 0x46F46C0E, 0x6DD93FCD, + 0x74C20E8C, 0xF35A1243, 0xEA412302, 0xC16C70C1, 0xD8774180, 0x9736D747, 0x8E2DE606, 0xA500B5C5, 0xBC1B8484, + 0x71418A1A, 0x685ABB5B, 0x4377E898, 0x5A6CD9D9, 0x152D4F1E, 0x0C367E5F, 0x271B2D9C, 0x3E001CDD, 0xB9980012, + 0xA0833153, 0x8BAE6290, 0x92B553D1, 0xDDF4C516, 0xC4EFF457, 0xEFC2A794, 0xF6D996D5, 0xAE07BCE9, 0xB71C8DA8, + 0x9C31DE6B, 0x852AEF2A, 0xCA6B79ED, 0xD37048AC, 0xF85D1B6F, 0xE1462A2E, 0x66DE36E1, 0x7FC507A0, 0x54E85463, + 0x4DF36522, 0x02B2F3E5, 0x1BA9C2A4, 0x30849167, 0x299FA026, 0xE4C5AEB8, 0xFDDE9FF9, 0xD6F3CC3A, 0xCFE8FD7B, + 0x80A96BBC, 0x99B25AFD, 0xB29F093E, 0xAB84387F, 0x2C1C24B0, 0x350715F1, 0x1E2A4632, 0x07317773, 0x4870E1B4, + 0x516BD0F5, 0x7A468336, 0x635DB277, 0xCBFAD74E, 0xD2E1E60F, 0xF9CCB5CC, 0xE0D7848D, 0xAF96124A, 0xB68D230B, + 0x9DA070C8, 0x84BB4189, 0x03235D46, 0x1A386C07, 0x31153FC4, 0x280E0E85, 0x674F9842, 0x7E54A903, 0x5579FAC0, + 0x4C62CB81, 0x8138C51F, 0x9823F45E, 0xB30EA79D, 0xAA1596DC, 0xE554001B, 0xFC4F315A, 0xD7626299, 0xCE7953D8, + 0x49E14F17, 0x50FA7E56, 0x7BD72D95, 0x62CC1CD4, 0x2D8D8A13, 0x3496BB52, 0x1FBBE891, 0x06A0D9D0, 0x5E7EF3EC, + 0x4765C2AD, 0x6C48916E, 0x7553A02F, 0x3A1236E8, 0x230907A9, 0x0824546A, 0x113F652B, 0x96A779E4, 0x8FBC48A5, + 0xA4911B66, 0xBD8A2A27, 0xF2CBBCE0, 0xEBD08DA1, 0xC0FDDE62, 0xD9E6EF23, 0x14BCE1BD, 0x0DA7D0FC, 0x268A833F, + 0x3F91B27E, 0x70D024B9, 0x69CB15F8, 0x42E6463B, 0x5BFD777A, 0xDC656BB5, 0xC57E5AF4, 0xEE530937, 0xF7483876, + 0xB809AEB1, 0xA1129FF0, 0x8A3FCC33, 0x9324FD72 + }, + new uint[] + { + 0x00000000, 0x01C26A37, 0x0384D46E, 0x0246BE59, 0x0709A8DC, 0x06CBC2EB, 0x048D7CB2, 0x054F1685, 0x0E1351B8, + 0x0FD13B8F, 0x0D9785D6, 0x0C55EFE1, 0x091AF964, 0x08D89353, 0x0A9E2D0A, 0x0B5C473D, 0x1C26A370, 0x1DE4C947, + 0x1FA2771E, 0x1E601D29, 0x1B2F0BAC, 0x1AED619B, 0x18ABDFC2, 0x1969B5F5, 0x1235F2C8, 0x13F798FF, 0x11B126A6, + 0x10734C91, 0x153C5A14, 0x14FE3023, 0x16B88E7A, 0x177AE44D, 0x384D46E0, 0x398F2CD7, 0x3BC9928E, 0x3A0BF8B9, + 0x3F44EE3C, 0x3E86840B, 0x3CC03A52, 0x3D025065, 0x365E1758, 0x379C7D6F, 0x35DAC336, 0x3418A901, 0x3157BF84, + 0x3095D5B3, 0x32D36BEA, 0x331101DD, 0x246BE590, 0x25A98FA7, 0x27EF31FE, 0x262D5BC9, 0x23624D4C, 0x22A0277B, + 0x20E69922, 0x2124F315, 0x2A78B428, 0x2BBADE1F, 0x29FC6046, 0x283E0A71, 0x2D711CF4, 0x2CB376C3, 0x2EF5C89A, + 0x2F37A2AD, 0x709A8DC0, 0x7158E7F7, 0x731E59AE, 0x72DC3399, 0x7793251C, 0x76514F2B, 0x7417F172, 0x75D59B45, + 0x7E89DC78, 0x7F4BB64F, 0x7D0D0816, 0x7CCF6221, 0x798074A4, 0x78421E93, 0x7A04A0CA, 0x7BC6CAFD, 0x6CBC2EB0, + 0x6D7E4487, 0x6F38FADE, 0x6EFA90E9, 0x6BB5866C, 0x6A77EC5B, 0x68315202, 0x69F33835, 0x62AF7F08, 0x636D153F, + 0x612BAB66, 0x60E9C151, 0x65A6D7D4, 0x6464BDE3, 0x662203BA, 0x67E0698D, 0x48D7CB20, 0x4915A117, 0x4B531F4E, + 0x4A917579, 0x4FDE63FC, 0x4E1C09CB, 0x4C5AB792, 0x4D98DDA5, 0x46C49A98, 0x4706F0AF, 0x45404EF6, 0x448224C1, + 0x41CD3244, 0x400F5873, 0x4249E62A, 0x438B8C1D, 0x54F16850, 0x55330267, 0x5775BC3E, 0x56B7D609, 0x53F8C08C, + 0x523AAABB, 0x507C14E2, 0x51BE7ED5, 0x5AE239E8, 0x5B2053DF, 0x5966ED86, 0x58A487B1, 0x5DEB9134, 0x5C29FB03, + 0x5E6F455A, 0x5FAD2F6D, 0xE1351B80, 0xE0F771B7, 0xE2B1CFEE, 0xE373A5D9, 0xE63CB35C, 0xE7FED96B, 0xE5B86732, + 0xE47A0D05, 0xEF264A38, 0xEEE4200F, 0xECA29E56, 0xED60F461, 0xE82FE2E4, 0xE9ED88D3, 0xEBAB368A, 0xEA695CBD, + 0xFD13B8F0, 0xFCD1D2C7, 0xFE976C9E, 0xFF5506A9, 0xFA1A102C, 0xFBD87A1B, 0xF99EC442, 0xF85CAE75, 0xF300E948, + 0xF2C2837F, 0xF0843D26, 0xF1465711, 0xF4094194, 0xF5CB2BA3, 0xF78D95FA, 0xF64FFFCD, 0xD9785D60, 0xD8BA3757, + 0xDAFC890E, 0xDB3EE339, 0xDE71F5BC, 0xDFB39F8B, 0xDDF521D2, 0xDC374BE5, 0xD76B0CD8, 0xD6A966EF, 0xD4EFD8B6, + 0xD52DB281, 0xD062A404, 0xD1A0CE33, 0xD3E6706A, 0xD2241A5D, 0xC55EFE10, 0xC49C9427, 0xC6DA2A7E, 0xC7184049, + 0xC25756CC, 0xC3953CFB, 0xC1D382A2, 0xC011E895, 0xCB4DAFA8, 0xCA8FC59F, 0xC8C97BC6, 0xC90B11F1, 0xCC440774, + 0xCD866D43, 0xCFC0D31A, 0xCE02B92D, 0x91AF9640, 0x906DFC77, 0x922B422E, 0x93E92819, 0x96A63E9C, 0x976454AB, + 0x9522EAF2, 0x94E080C5, 0x9FBCC7F8, 0x9E7EADCF, 0x9C381396, 0x9DFA79A1, 0x98B56F24, 0x99770513, 0x9B31BB4A, + 0x9AF3D17D, 0x8D893530, 0x8C4B5F07, 0x8E0DE15E, 0x8FCF8B69, 0x8A809DEC, 0x8B42F7DB, 0x89044982, 0x88C623B5, + 0x839A6488, 0x82580EBF, 0x801EB0E6, 0x81DCDAD1, 0x8493CC54, 0x8551A663, 0x8717183A, 0x86D5720D, 0xA9E2D0A0, + 0xA820BA97, 0xAA6604CE, 0xABA46EF9, 0xAEEB787C, 0xAF29124B, 0xAD6FAC12, 0xACADC625, 0xA7F18118, 0xA633EB2F, + 0xA4755576, 0xA5B73F41, 0xA0F829C4, 0xA13A43F3, 0xA37CFDAA, 0xA2BE979D, 0xB5C473D0, 0xB40619E7, 0xB640A7BE, + 0xB782CD89, 0xB2CDDB0C, 0xB30FB13B, 0xB1490F62, 0xB08B6555, 0xBBD72268, 0xBA15485F, 0xB853F606, 0xB9919C31, + 0xBCDE8AB4, 0xBD1CE083, 0xBF5A5EDA, 0xBE9834ED + }, + new uint[] + { + 0x00000000, 0xB8BC6765, 0xAA09C88B, 0x12B5AFEE, 0x8F629757, 0x37DEF032, 0x256B5FDC, 0x9DD738B9, 0xC5B428EF, + 0x7D084F8A, 0x6FBDE064, 0xD7018701, 0x4AD6BFB8, 0xF26AD8DD, 0xE0DF7733, 0x58631056, 0x5019579F, 0xE8A530FA, + 0xFA109F14, 0x42ACF871, 0xDF7BC0C8, 0x67C7A7AD, 0x75720843, 0xCDCE6F26, 0x95AD7F70, 0x2D111815, 0x3FA4B7FB, + 0x8718D09E, 0x1ACFE827, 0xA2738F42, 0xB0C620AC, 0x087A47C9, 0xA032AF3E, 0x188EC85B, 0x0A3B67B5, 0xB28700D0, + 0x2F503869, 0x97EC5F0C, 0x8559F0E2, 0x3DE59787, 0x658687D1, 0xDD3AE0B4, 0xCF8F4F5A, 0x7733283F, 0xEAE41086, + 0x525877E3, 0x40EDD80D, 0xF851BF68, 0xF02BF8A1, 0x48979FC4, 0x5A22302A, 0xE29E574F, 0x7F496FF6, 0xC7F50893, + 0xD540A77D, 0x6DFCC018, 0x359FD04E, 0x8D23B72B, 0x9F9618C5, 0x272A7FA0, 0xBAFD4719, 0x0241207C, 0x10F48F92, + 0xA848E8F7, 0x9B14583D, 0x23A83F58, 0x311D90B6, 0x89A1F7D3, 0x1476CF6A, 0xACCAA80F, 0xBE7F07E1, 0x06C36084, + 0x5EA070D2, 0xE61C17B7, 0xF4A9B859, 0x4C15DF3C, 0xD1C2E785, 0x697E80E0, 0x7BCB2F0E, 0xC377486B, 0xCB0D0FA2, + 0x73B168C7, 0x6104C729, 0xD9B8A04C, 0x446F98F5, 0xFCD3FF90, 0xEE66507E, 0x56DA371B, 0x0EB9274D, 0xB6054028, + 0xA4B0EFC6, 0x1C0C88A3, 0x81DBB01A, 0x3967D77F, 0x2BD27891, 0x936E1FF4, 0x3B26F703, 0x839A9066, 0x912F3F88, + 0x299358ED, 0xB4446054, 0x0CF80731, 0x1E4DA8DF, 0xA6F1CFBA, 0xFE92DFEC, 0x462EB889, 0x549B1767, 0xEC277002, + 0x71F048BB, 0xC94C2FDE, 0xDBF98030, 0x6345E755, 0x6B3FA09C, 0xD383C7F9, 0xC1366817, 0x798A0F72, 0xE45D37CB, + 0x5CE150AE, 0x4E54FF40, 0xF6E89825, 0xAE8B8873, 0x1637EF16, 0x048240F8, 0xBC3E279D, 0x21E91F24, 0x99557841, + 0x8BE0D7AF, 0x335CB0CA, 0xED59B63B, 0x55E5D15E, 0x47507EB0, 0xFFEC19D5, 0x623B216C, 0xDA874609, 0xC832E9E7, + 0x708E8E82, 0x28ED9ED4, 0x9051F9B1, 0x82E4565F, 0x3A58313A, 0xA78F0983, 0x1F336EE6, 0x0D86C108, 0xB53AA66D, + 0xBD40E1A4, 0x05FC86C1, 0x1749292F, 0xAFF54E4A, 0x322276F3, 0x8A9E1196, 0x982BBE78, 0x2097D91D, 0x78F4C94B, + 0xC048AE2E, 0xD2FD01C0, 0x6A4166A5, 0xF7965E1C, 0x4F2A3979, 0x5D9F9697, 0xE523F1F2, 0x4D6B1905, 0xF5D77E60, + 0xE762D18E, 0x5FDEB6EB, 0xC2098E52, 0x7AB5E937, 0x680046D9, 0xD0BC21BC, 0x88DF31EA, 0x3063568F, 0x22D6F961, + 0x9A6A9E04, 0x07BDA6BD, 0xBF01C1D8, 0xADB46E36, 0x15080953, 0x1D724E9A, 0xA5CE29FF, 0xB77B8611, 0x0FC7E174, + 0x9210D9CD, 0x2AACBEA8, 0x38191146, 0x80A57623, 0xD8C66675, 0x607A0110, 0x72CFAEFE, 0xCA73C99B, 0x57A4F122, + 0xEF189647, 0xFDAD39A9, 0x45115ECC, 0x764DEE06, 0xCEF18963, 0xDC44268D, 0x64F841E8, 0xF92F7951, 0x41931E34, + 0x5326B1DA, 0xEB9AD6BF, 0xB3F9C6E9, 0x0B45A18C, 0x19F00E62, 0xA14C6907, 0x3C9B51BE, 0x842736DB, 0x96929935, + 0x2E2EFE50, 0x2654B999, 0x9EE8DEFC, 0x8C5D7112, 0x34E11677, 0xA9362ECE, 0x118A49AB, 0x033FE645, 0xBB838120, + 0xE3E09176, 0x5B5CF613, 0x49E959FD, 0xF1553E98, 0x6C820621, 0xD43E6144, 0xC68BCEAA, 0x7E37A9CF, 0xD67F4138, + 0x6EC3265D, 0x7C7689B3, 0xC4CAEED6, 0x591DD66F, 0xE1A1B10A, 0xF3141EE4, 0x4BA87981, 0x13CB69D7, 0xAB770EB2, + 0xB9C2A15C, 0x017EC639, 0x9CA9FE80, 0x241599E5, 0x36A0360B, 0x8E1C516E, 0x866616A7, 0x3EDA71C2, 0x2C6FDE2C, + 0x94D3B949, 0x090481F0, 0xB1B8E695, 0xA30D497B, 0x1BB12E1E, 0x43D23E48, 0xFB6E592D, 0xE9DBF6C3, 0x516791A6, + 0xCCB0A91F, 0x740CCE7A, 0x66B96194, 0xDE0506F1 + }, + new uint[] + { + 0x00000000, 0x3D6029B0, 0x7AC05360, 0x47A07AD0, 0xF580A6C0, 0xC8E08F70, 0x8F40F5A0, 0xB220DC10, 0x30704BC1, + 0x0D106271, 0x4AB018A1, 0x77D03111, 0xC5F0ED01, 0xF890C4B1, 0xBF30BE61, 0x825097D1, 0x60E09782, 0x5D80BE32, + 0x1A20C4E2, 0x2740ED52, 0x95603142, 0xA80018F2, 0xEFA06222, 0xD2C04B92, 0x5090DC43, 0x6DF0F5F3, 0x2A508F23, + 0x1730A693, 0xA5107A83, 0x98705333, 0xDFD029E3, 0xE2B00053, 0xC1C12F04, 0xFCA106B4, 0xBB017C64, 0x866155D4, + 0x344189C4, 0x0921A074, 0x4E81DAA4, 0x73E1F314, 0xF1B164C5, 0xCCD14D75, 0x8B7137A5, 0xB6111E15, 0x0431C205, + 0x3951EBB5, 0x7EF19165, 0x4391B8D5, 0xA121B886, 0x9C419136, 0xDBE1EBE6, 0xE681C256, 0x54A11E46, 0x69C137F6, + 0x2E614D26, 0x13016496, 0x9151F347, 0xAC31DAF7, 0xEB91A027, 0xD6F18997, 0x64D15587, 0x59B17C37, 0x1E1106E7, + 0x23712F57, 0x58F35849, 0x659371F9, 0x22330B29, 0x1F532299, 0xAD73FE89, 0x9013D739, 0xD7B3ADE9, 0xEAD38459, + 0x68831388, 0x55E33A38, 0x124340E8, 0x2F236958, 0x9D03B548, 0xA0639CF8, 0xE7C3E628, 0xDAA3CF98, 0x3813CFCB, + 0x0573E67B, 0x42D39CAB, 0x7FB3B51B, 0xCD93690B, 0xF0F340BB, 0xB7533A6B, 0x8A3313DB, 0x0863840A, 0x3503ADBA, + 0x72A3D76A, 0x4FC3FEDA, 0xFDE322CA, 0xC0830B7A, 0x872371AA, 0xBA43581A, 0x9932774D, 0xA4525EFD, 0xE3F2242D, + 0xDE920D9D, 0x6CB2D18D, 0x51D2F83D, 0x167282ED, 0x2B12AB5D, 0xA9423C8C, 0x9422153C, 0xD3826FEC, 0xEEE2465C, + 0x5CC29A4C, 0x61A2B3FC, 0x2602C92C, 0x1B62E09C, 0xF9D2E0CF, 0xC4B2C97F, 0x8312B3AF, 0xBE729A1F, 0x0C52460F, + 0x31326FBF, 0x7692156F, 0x4BF23CDF, 0xC9A2AB0E, 0xF4C282BE, 0xB362F86E, 0x8E02D1DE, 0x3C220DCE, 0x0142247E, + 0x46E25EAE, 0x7B82771E, 0xB1E6B092, 0x8C869922, 0xCB26E3F2, 0xF646CA42, 0x44661652, 0x79063FE2, 0x3EA64532, + 0x03C66C82, 0x8196FB53, 0xBCF6D2E3, 0xFB56A833, 0xC6368183, 0x74165D93, 0x49767423, 0x0ED60EF3, 0x33B62743, + 0xD1062710, 0xEC660EA0, 0xABC67470, 0x96A65DC0, 0x248681D0, 0x19E6A860, 0x5E46D2B0, 0x6326FB00, 0xE1766CD1, + 0xDC164561, 0x9BB63FB1, 0xA6D61601, 0x14F6CA11, 0x2996E3A1, 0x6E369971, 0x5356B0C1, 0x70279F96, 0x4D47B626, + 0x0AE7CCF6, 0x3787E546, 0x85A73956, 0xB8C710E6, 0xFF676A36, 0xC2074386, 0x4057D457, 0x7D37FDE7, 0x3A978737, + 0x07F7AE87, 0xB5D77297, 0x88B75B27, 0xCF1721F7, 0xF2770847, 0x10C70814, 0x2DA721A4, 0x6A075B74, 0x576772C4, + 0xE547AED4, 0xD8278764, 0x9F87FDB4, 0xA2E7D404, 0x20B743D5, 0x1DD76A65, 0x5A7710B5, 0x67173905, 0xD537E515, + 0xE857CCA5, 0xAFF7B675, 0x92979FC5, 0xE915E8DB, 0xD475C16B, 0x93D5BBBB, 0xAEB5920B, 0x1C954E1B, 0x21F567AB, + 0x66551D7B, 0x5B3534CB, 0xD965A31A, 0xE4058AAA, 0xA3A5F07A, 0x9EC5D9CA, 0x2CE505DA, 0x11852C6A, 0x562556BA, + 0x6B457F0A, 0x89F57F59, 0xB49556E9, 0xF3352C39, 0xCE550589, 0x7C75D999, 0x4115F029, 0x06B58AF9, 0x3BD5A349, + 0xB9853498, 0x84E51D28, 0xC34567F8, 0xFE254E48, 0x4C059258, 0x7165BBE8, 0x36C5C138, 0x0BA5E888, 0x28D4C7DF, + 0x15B4EE6F, 0x521494BF, 0x6F74BD0F, 0xDD54611F, 0xE03448AF, 0xA794327F, 0x9AF41BCF, 0x18A48C1E, 0x25C4A5AE, + 0x6264DF7E, 0x5F04F6CE, 0xED242ADE, 0xD044036E, 0x97E479BE, 0xAA84500E, 0x4834505D, 0x755479ED, 0x32F4033D, + 0x0F942A8D, 0xBDB4F69D, 0x80D4DF2D, 0xC774A5FD, 0xFA148C4D, 0x78441B9C, 0x4524322C, 0x028448FC, 0x3FE4614C, + 0x8DC4BD5C, 0xB0A494EC, 0xF704EE3C, 0xCA64C78C + }, + new uint[] + { + 0x00000000, 0xCB5CD3A5, 0x4DC8A10B, 0x869472AE, 0x9B914216, 0x50CD91B3, 0xD659E31D, 0x1D0530B8, 0xEC53826D, + 0x270F51C8, 0xA19B2366, 0x6AC7F0C3, 0x77C2C07B, 0xBC9E13DE, 0x3A0A6170, 0xF156B2D5, 0x03D6029B, 0xC88AD13E, + 0x4E1EA390, 0x85427035, 0x9847408D, 0x531B9328, 0xD58FE186, 0x1ED33223, 0xEF8580F6, 0x24D95353, 0xA24D21FD, + 0x6911F258, 0x7414C2E0, 0xBF481145, 0x39DC63EB, 0xF280B04E, 0x07AC0536, 0xCCF0D693, 0x4A64A43D, 0x81387798, + 0x9C3D4720, 0x57619485, 0xD1F5E62B, 0x1AA9358E, 0xEBFF875B, 0x20A354FE, 0xA6372650, 0x6D6BF5F5, 0x706EC54D, + 0xBB3216E8, 0x3DA66446, 0xF6FAB7E3, 0x047A07AD, 0xCF26D408, 0x49B2A6A6, 0x82EE7503, 0x9FEB45BB, 0x54B7961E, + 0xD223E4B0, 0x197F3715, 0xE82985C0, 0x23755665, 0xA5E124CB, 0x6EBDF76E, 0x73B8C7D6, 0xB8E41473, 0x3E7066DD, + 0xF52CB578, 0x0F580A6C, 0xC404D9C9, 0x4290AB67, 0x89CC78C2, 0x94C9487A, 0x5F959BDF, 0xD901E971, 0x125D3AD4, + 0xE30B8801, 0x28575BA4, 0xAEC3290A, 0x659FFAAF, 0x789ACA17, 0xB3C619B2, 0x35526B1C, 0xFE0EB8B9, 0x0C8E08F7, + 0xC7D2DB52, 0x4146A9FC, 0x8A1A7A59, 0x971F4AE1, 0x5C439944, 0xDAD7EBEA, 0x118B384F, 0xE0DD8A9A, 0x2B81593F, + 0xAD152B91, 0x6649F834, 0x7B4CC88C, 0xB0101B29, 0x36846987, 0xFDD8BA22, 0x08F40F5A, 0xC3A8DCFF, 0x453CAE51, + 0x8E607DF4, 0x93654D4C, 0x58399EE9, 0xDEADEC47, 0x15F13FE2, 0xE4A78D37, 0x2FFB5E92, 0xA96F2C3C, 0x6233FF99, + 0x7F36CF21, 0xB46A1C84, 0x32FE6E2A, 0xF9A2BD8F, 0x0B220DC1, 0xC07EDE64, 0x46EAACCA, 0x8DB67F6F, 0x90B34FD7, + 0x5BEF9C72, 0xDD7BEEDC, 0x16273D79, 0xE7718FAC, 0x2C2D5C09, 0xAAB92EA7, 0x61E5FD02, 0x7CE0CDBA, 0xB7BC1E1F, + 0x31286CB1, 0xFA74BF14, 0x1EB014D8, 0xD5ECC77D, 0x5378B5D3, 0x98246676, 0x852156CE, 0x4E7D856B, 0xC8E9F7C5, + 0x03B52460, 0xF2E396B5, 0x39BF4510, 0xBF2B37BE, 0x7477E41B, 0x6972D4A3, 0xA22E0706, 0x24BA75A8, 0xEFE6A60D, + 0x1D661643, 0xD63AC5E6, 0x50AEB748, 0x9BF264ED, 0x86F75455, 0x4DAB87F0, 0xCB3FF55E, 0x006326FB, 0xF135942E, + 0x3A69478B, 0xBCFD3525, 0x77A1E680, 0x6AA4D638, 0xA1F8059D, 0x276C7733, 0xEC30A496, 0x191C11EE, 0xD240C24B, + 0x54D4B0E5, 0x9F886340, 0x828D53F8, 0x49D1805D, 0xCF45F2F3, 0x04192156, 0xF54F9383, 0x3E134026, 0xB8873288, + 0x73DBE12D, 0x6EDED195, 0xA5820230, 0x2316709E, 0xE84AA33B, 0x1ACA1375, 0xD196C0D0, 0x5702B27E, 0x9C5E61DB, + 0x815B5163, 0x4A0782C6, 0xCC93F068, 0x07CF23CD, 0xF6999118, 0x3DC542BD, 0xBB513013, 0x700DE3B6, 0x6D08D30E, + 0xA65400AB, 0x20C07205, 0xEB9CA1A0, 0x11E81EB4, 0xDAB4CD11, 0x5C20BFBF, 0x977C6C1A, 0x8A795CA2, 0x41258F07, + 0xC7B1FDA9, 0x0CED2E0C, 0xFDBB9CD9, 0x36E74F7C, 0xB0733DD2, 0x7B2FEE77, 0x662ADECF, 0xAD760D6A, 0x2BE27FC4, + 0xE0BEAC61, 0x123E1C2F, 0xD962CF8A, 0x5FF6BD24, 0x94AA6E81, 0x89AF5E39, 0x42F38D9C, 0xC467FF32, 0x0F3B2C97, + 0xFE6D9E42, 0x35314DE7, 0xB3A53F49, 0x78F9ECEC, 0x65FCDC54, 0xAEA00FF1, 0x28347D5F, 0xE368AEFA, 0x16441B82, + 0xDD18C827, 0x5B8CBA89, 0x90D0692C, 0x8DD55994, 0x46898A31, 0xC01DF89F, 0x0B412B3A, 0xFA1799EF, 0x314B4A4A, + 0xB7DF38E4, 0x7C83EB41, 0x6186DBF9, 0xAADA085C, 0x2C4E7AF2, 0xE712A957, 0x15921919, 0xDECECABC, 0x585AB812, + 0x93066BB7, 0x8E035B0F, 0x455F88AA, 0xC3CBFA04, 0x089729A1, 0xF9C19B74, 0x329D48D1, 0xB4093A7F, 0x7F55E9DA, + 0x6250D962, 0xA90C0AC7, 0x2F987869, 0xE4C4ABCC + }, + new uint[] + { + 0x00000000, 0xA6770BB4, 0x979F1129, 0x31E81A9D, 0xF44F2413, 0x52382FA7, 0x63D0353A, 0xC5A73E8E, 0x33EF4E67, + 0x959845D3, 0xA4705F4E, 0x020754FA, 0xC7A06A74, 0x61D761C0, 0x503F7B5D, 0xF64870E9, 0x67DE9CCE, 0xC1A9977A, + 0xF0418DE7, 0x56368653, 0x9391B8DD, 0x35E6B369, 0x040EA9F4, 0xA279A240, 0x5431D2A9, 0xF246D91D, 0xC3AEC380, + 0x65D9C834, 0xA07EF6BA, 0x0609FD0E, 0x37E1E793, 0x9196EC27, 0xCFBD399C, 0x69CA3228, 0x582228B5, 0xFE552301, + 0x3BF21D8F, 0x9D85163B, 0xAC6D0CA6, 0x0A1A0712, 0xFC5277FB, 0x5A257C4F, 0x6BCD66D2, 0xCDBA6D66, 0x081D53E8, + 0xAE6A585C, 0x9F8242C1, 0x39F54975, 0xA863A552, 0x0E14AEE6, 0x3FFCB47B, 0x998BBFCF, 0x5C2C8141, 0xFA5B8AF5, + 0xCBB39068, 0x6DC49BDC, 0x9B8CEB35, 0x3DFBE081, 0x0C13FA1C, 0xAA64F1A8, 0x6FC3CF26, 0xC9B4C492, 0xF85CDE0F, + 0x5E2BD5BB, 0x440B7579, 0xE27C7ECD, 0xD3946450, 0x75E36FE4, 0xB044516A, 0x16335ADE, 0x27DB4043, 0x81AC4BF7, + 0x77E43B1E, 0xD19330AA, 0xE07B2A37, 0x460C2183, 0x83AB1F0D, 0x25DC14B9, 0x14340E24, 0xB2430590, 0x23D5E9B7, + 0x85A2E203, 0xB44AF89E, 0x123DF32A, 0xD79ACDA4, 0x71EDC610, 0x4005DC8D, 0xE672D739, 0x103AA7D0, 0xB64DAC64, + 0x87A5B6F9, 0x21D2BD4D, 0xE47583C3, 0x42028877, 0x73EA92EA, 0xD59D995E, 0x8BB64CE5, 0x2DC14751, 0x1C295DCC, + 0xBA5E5678, 0x7FF968F6, 0xD98E6342, 0xE86679DF, 0x4E11726B, 0xB8590282, 0x1E2E0936, 0x2FC613AB, 0x89B1181F, + 0x4C162691, 0xEA612D25, 0xDB8937B8, 0x7DFE3C0C, 0xEC68D02B, 0x4A1FDB9F, 0x7BF7C102, 0xDD80CAB6, 0x1827F438, + 0xBE50FF8C, 0x8FB8E511, 0x29CFEEA5, 0xDF879E4C, 0x79F095F8, 0x48188F65, 0xEE6F84D1, 0x2BC8BA5F, 0x8DBFB1EB, + 0xBC57AB76, 0x1A20A0C2, 0x8816EAF2, 0x2E61E146, 0x1F89FBDB, 0xB9FEF06F, 0x7C59CEE1, 0xDA2EC555, 0xEBC6DFC8, + 0x4DB1D47C, 0xBBF9A495, 0x1D8EAF21, 0x2C66B5BC, 0x8A11BE08, 0x4FB68086, 0xE9C18B32, 0xD82991AF, 0x7E5E9A1B, + 0xEFC8763C, 0x49BF7D88, 0x78576715, 0xDE206CA1, 0x1B87522F, 0xBDF0599B, 0x8C184306, 0x2A6F48B2, 0xDC27385B, + 0x7A5033EF, 0x4BB82972, 0xEDCF22C6, 0x28681C48, 0x8E1F17FC, 0xBFF70D61, 0x198006D5, 0x47ABD36E, 0xE1DCD8DA, + 0xD034C247, 0x7643C9F3, 0xB3E4F77D, 0x1593FCC9, 0x247BE654, 0x820CEDE0, 0x74449D09, 0xD23396BD, 0xE3DB8C20, + 0x45AC8794, 0x800BB91A, 0x267CB2AE, 0x1794A833, 0xB1E3A387, 0x20754FA0, 0x86024414, 0xB7EA5E89, 0x119D553D, + 0xD43A6BB3, 0x724D6007, 0x43A57A9A, 0xE5D2712E, 0x139A01C7, 0xB5ED0A73, 0x840510EE, 0x22721B5A, 0xE7D525D4, + 0x41A22E60, 0x704A34FD, 0xD63D3F49, 0xCC1D9F8B, 0x6A6A943F, 0x5B828EA2, 0xFDF58516, 0x3852BB98, 0x9E25B02C, + 0xAFCDAAB1, 0x09BAA105, 0xFFF2D1EC, 0x5985DA58, 0x686DC0C5, 0xCE1ACB71, 0x0BBDF5FF, 0xADCAFE4B, 0x9C22E4D6, + 0x3A55EF62, 0xABC30345, 0x0DB408F1, 0x3C5C126C, 0x9A2B19D8, 0x5F8C2756, 0xF9FB2CE2, 0xC813367F, 0x6E643DCB, + 0x982C4D22, 0x3E5B4696, 0x0FB35C0B, 0xA9C457BF, 0x6C636931, 0xCA146285, 0xFBFC7818, 0x5D8B73AC, 0x03A0A617, + 0xA5D7ADA3, 0x943FB73E, 0x3248BC8A, 0xF7EF8204, 0x519889B0, 0x6070932D, 0xC6079899, 0x304FE870, 0x9638E3C4, + 0xA7D0F959, 0x01A7F2ED, 0xC400CC63, 0x6277C7D7, 0x539FDD4A, 0xF5E8D6FE, 0x647E3AD9, 0xC209316D, 0xF3E12BF0, + 0x55962044, 0x90311ECA, 0x3646157E, 0x07AE0FE3, 0xA1D90457, 0x579174BE, 0xF1E67F0A, 0xC00E6597, 0x66796E23, + 0xA3DE50AD, 0x05A95B19, 0x34414184, 0x92364A30 + }, + new uint[] + { + 0x00000000, 0xCCAA009E, 0x4225077D, 0x8E8F07E3, 0x844A0EFA, 0x48E00E64, 0xC66F0987, 0x0AC50919, 0xD3E51BB5, + 0x1F4F1B2B, 0x91C01CC8, 0x5D6A1C56, 0x57AF154F, 0x9B0515D1, 0x158A1232, 0xD92012AC, 0x7CBB312B, 0xB01131B5, + 0x3E9E3656, 0xF23436C8, 0xF8F13FD1, 0x345B3F4F, 0xBAD438AC, 0x767E3832, 0xAF5E2A9E, 0x63F42A00, 0xED7B2DE3, + 0x21D12D7D, 0x2B142464, 0xE7BE24FA, 0x69312319, 0xA59B2387, 0xF9766256, 0x35DC62C8, 0xBB53652B, 0x77F965B5, + 0x7D3C6CAC, 0xB1966C32, 0x3F196BD1, 0xF3B36B4F, 0x2A9379E3, 0xE639797D, 0x68B67E9E, 0xA41C7E00, 0xAED97719, + 0x62737787, 0xECFC7064, 0x205670FA, 0x85CD537D, 0x496753E3, 0xC7E85400, 0x0B42549E, 0x01875D87, 0xCD2D5D19, + 0x43A25AFA, 0x8F085A64, 0x562848C8, 0x9A824856, 0x140D4FB5, 0xD8A74F2B, 0xD2624632, 0x1EC846AC, 0x9047414F, + 0x5CED41D1, 0x299DC2ED, 0xE537C273, 0x6BB8C590, 0xA712C50E, 0xADD7CC17, 0x617DCC89, 0xEFF2CB6A, 0x2358CBF4, + 0xFA78D958, 0x36D2D9C6, 0xB85DDE25, 0x74F7DEBB, 0x7E32D7A2, 0xB298D73C, 0x3C17D0DF, 0xF0BDD041, 0x5526F3C6, + 0x998CF358, 0x1703F4BB, 0xDBA9F425, 0xD16CFD3C, 0x1DC6FDA2, 0x9349FA41, 0x5FE3FADF, 0x86C3E873, 0x4A69E8ED, + 0xC4E6EF0E, 0x084CEF90, 0x0289E689, 0xCE23E617, 0x40ACE1F4, 0x8C06E16A, 0xD0EBA0BB, 0x1C41A025, 0x92CEA7C6, + 0x5E64A758, 0x54A1AE41, 0x980BAEDF, 0x1684A93C, 0xDA2EA9A2, 0x030EBB0E, 0xCFA4BB90, 0x412BBC73, 0x8D81BCED, + 0x8744B5F4, 0x4BEEB56A, 0xC561B289, 0x09CBB217, 0xAC509190, 0x60FA910E, 0xEE7596ED, 0x22DF9673, 0x281A9F6A, + 0xE4B09FF4, 0x6A3F9817, 0xA6959889, 0x7FB58A25, 0xB31F8ABB, 0x3D908D58, 0xF13A8DC6, 0xFBFF84DF, 0x37558441, + 0xB9DA83A2, 0x7570833C, 0x533B85DA, 0x9F918544, 0x111E82A7, 0xDDB48239, 0xD7718B20, 0x1BDB8BBE, 0x95548C5D, + 0x59FE8CC3, 0x80DE9E6F, 0x4C749EF1, 0xC2FB9912, 0x0E51998C, 0x04949095, 0xC83E900B, 0x46B197E8, 0x8A1B9776, + 0x2F80B4F1, 0xE32AB46F, 0x6DA5B38C, 0xA10FB312, 0xABCABA0B, 0x6760BA95, 0xE9EFBD76, 0x2545BDE8, 0xFC65AF44, + 0x30CFAFDA, 0xBE40A839, 0x72EAA8A7, 0x782FA1BE, 0xB485A120, 0x3A0AA6C3, 0xF6A0A65D, 0xAA4DE78C, 0x66E7E712, + 0xE868E0F1, 0x24C2E06F, 0x2E07E976, 0xE2ADE9E8, 0x6C22EE0B, 0xA088EE95, 0x79A8FC39, 0xB502FCA7, 0x3B8DFB44, + 0xF727FBDA, 0xFDE2F2C3, 0x3148F25D, 0xBFC7F5BE, 0x736DF520, 0xD6F6D6A7, 0x1A5CD639, 0x94D3D1DA, 0x5879D144, + 0x52BCD85D, 0x9E16D8C3, 0x1099DF20, 0xDC33DFBE, 0x0513CD12, 0xC9B9CD8C, 0x4736CA6F, 0x8B9CCAF1, 0x8159C3E8, + 0x4DF3C376, 0xC37CC495, 0x0FD6C40B, 0x7AA64737, 0xB60C47A9, 0x3883404A, 0xF42940D4, 0xFEEC49CD, 0x32464953, + 0xBCC94EB0, 0x70634E2E, 0xA9435C82, 0x65E95C1C, 0xEB665BFF, 0x27CC5B61, 0x2D095278, 0xE1A352E6, 0x6F2C5505, + 0xA386559B, 0x061D761C, 0xCAB77682, 0x44387161, 0x889271FF, 0x825778E6, 0x4EFD7878, 0xC0727F9B, 0x0CD87F05, + 0xD5F86DA9, 0x19526D37, 0x97DD6AD4, 0x5B776A4A, 0x51B26353, 0x9D1863CD, 0x1397642E, 0xDF3D64B0, 0x83D02561, + 0x4F7A25FF, 0xC1F5221C, 0x0D5F2282, 0x079A2B9B, 0xCB302B05, 0x45BF2CE6, 0x89152C78, 0x50353ED4, 0x9C9F3E4A, + 0x121039A9, 0xDEBA3937, 0xD47F302E, 0x18D530B0, 0x965A3753, 0x5AF037CD, 0xFF6B144A, 0x33C114D4, 0xBD4E1337, + 0x71E413A9, 0x7B211AB0, 0xB78B1A2E, 0x39041DCD, 0xF5AE1D53, 0x2C8E0FFF, 0xE0240F61, 0x6EAB0882, 0xA201081C, + 0xA8C40105, 0x646E019B, 0xEAE10678, 0x264B06E6 + } + }; - if(!Native.IsSupported) - return; + readonly uint _finalSeed; + readonly IntPtr _nativeContext; + readonly uint[][] _table; + readonly bool _useIso; + readonly bool _useNative; + uint _hashInt; + /// Initializes the CRC32 table and seed as CRC32-ISO + public Crc32Context() + { + _hashInt = CRC32_ISO_SEED; + _finalSeed = CRC32_ISO_SEED; + _table = _isoCrc32Table; + _useIso = true; + + if(!Native.IsSupported) + return; + + _nativeContext = crc32_init(); + _useNative = _nativeContext != IntPtr.Zero; + } + + /// Initializes the CRC32 table with a custom polynomial and seed + public Crc32Context(uint polynomial, uint seed) + { + _hashInt = seed; + _finalSeed = seed; + _useIso = polynomial == CRC32_ISO_POLY && seed == CRC32_ISO_SEED; + + if(Native.IsSupported && _useIso) + { _nativeContext = crc32_init(); _useNative = _nativeContext != IntPtr.Zero; } + else + _table = GenerateTable(polynomial); + } - /// Initializes the CRC32 table with a custom polynomial and seed - public Crc32Context(uint polynomial, uint seed) + /// + /// Updates the hash with data. + /// Data buffer. + /// Length of buffer to hash. + public void Update(byte[] data, uint len) => + Step(ref _hashInt, _table, data, len, _useIso, _useNative, _nativeContext); + + /// + /// Updates the hash with data. + /// Data buffer. + public void Update(byte[] data) => Update(data, (uint)data.Length); + + /// + /// Returns a byte array of the hash value. + public byte[] Final() + { + uint crc = _hashInt ^ _finalSeed; + + if(!_useNative || + !_useIso) + return BigEndianBitConverter.GetBytes(crc); + + crc32_final(_nativeContext, ref crc); + crc32_free(_nativeContext); + + return BigEndianBitConverter.GetBytes(crc); + } + + /// + /// Returns a hexadecimal representation of the hash value. + public string End() + { + uint crc = _hashInt ^ _finalSeed; + + var crc32Output = new StringBuilder(); + + if(_useNative && _useIso) { - _hashInt = seed; - _finalSeed = seed; - _useIso = polynomial == CRC32_ISO_POLY && seed == CRC32_ISO_SEED; - - if(Native.IsSupported && _useIso) - { - _nativeContext = crc32_init(); - _useNative = _nativeContext != IntPtr.Zero; - } - else - _table = GenerateTable(polynomial); - } - - /// - /// Updates the hash with data. - /// Data buffer. - /// Length of buffer to hash. - public void Update(byte[] data, uint len) => - Step(ref _hashInt, _table, data, len, _useIso, _useNative, _nativeContext); - - /// - /// Updates the hash with data. - /// Data buffer. - public void Update(byte[] data) => Update(data, (uint)data.Length); - - /// - /// Returns a byte array of the hash value. - public byte[] Final() - { - uint crc = _hashInt ^ _finalSeed; - - if(!_useNative || - !_useIso) - return BigEndianBitConverter.GetBytes(crc); - crc32_final(_nativeContext, ref crc); crc32_free(_nativeContext); - - return BigEndianBitConverter.GetBytes(crc); } - /// - /// Returns a hexadecimal representation of the hash value. - public string End() + for(int i = 0; i < BigEndianBitConverter.GetBytes(crc).Length; i++) + crc32Output.Append(BigEndianBitConverter.GetBytes(crc)[i].ToString("x2")); + + return crc32Output.ToString(); + } + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern IntPtr crc32_init(); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int crc32_update(IntPtr ctx, byte[] data, uint len); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int crc32_final(IntPtr ctx, ref uint crc); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern void crc32_free(IntPtr ctx); + + static uint[][] GenerateTable(uint polynomial) + { + uint[][] table = new uint[8][]; + + for(int i = 0; i < 8; i++) + table[i] = new uint[256]; + + for(int i = 0; i < 256; i++) { - uint crc = _hashInt ^ _finalSeed; + uint entry = (uint)i; - var crc32Output = new StringBuilder(); + for(int j = 0; j < 8; j++) + if((entry & 1) == 1) + entry = (entry >> 1) ^ polynomial; + else + entry >>= 1; - if(_useNative && _useIso) - { - crc32_final(_nativeContext, ref crc); - crc32_free(_nativeContext); - } - - for(int i = 0; i < BigEndianBitConverter.GetBytes(crc).Length; i++) - crc32Output.Append(BigEndianBitConverter.GetBytes(crc)[i].ToString("x2")); - - return crc32Output.ToString(); + table[0][i] = entry; } - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern IntPtr crc32_init(); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int crc32_update(IntPtr ctx, byte[] data, uint len); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int crc32_final(IntPtr ctx, ref uint crc); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern void crc32_free(IntPtr ctx); - - static uint[][] GenerateTable(uint polynomial) - { - uint[][] table = new uint[8][]; - - for(int i = 0; i < 8; i++) - table[i] = new uint[256]; - + for(int slice = 1; slice < 8; slice++) for(int i = 0; i < 256; i++) - { - uint entry = (uint)i; + table[slice][i] = (table[slice - 1][i] >> 8) ^ table[0][table[slice - 1][i] & 0xFF]; - for(int j = 0; j < 8; j++) - if((entry & 1) == 1) - entry = (entry >> 1) ^ polynomial; - else - entry >>= 1; + return table; + } - table[0][i] = entry; - } + static void Step(ref uint previousCrc, uint[][] table, byte[] data, uint len, bool useIso, bool useNative, + IntPtr nativeContext) + { + if(useNative && useIso) + { + crc32_update(nativeContext, data, len); - for(int slice = 1; slice < 8; slice++) - for(int i = 0; i < 256; i++) - table[slice][i] = (table[slice - 1][i] >> 8) ^ table[0][table[slice - 1][i] & 0xFF]; - - return table; + return; } - static void Step(ref uint previousCrc, uint[][] table, byte[] data, uint len, bool useIso, bool useNative, - IntPtr nativeContext) + int currentPos = 0; + + if(useIso) { - if(useNative && useIso) + if(Pclmulqdq.IsSupported && + Sse41.IsSupported && + Ssse3.IsSupported && + Sse2.IsSupported) { - crc32_update(nativeContext, data, len); + // Only works in blocks of 16 bytes + uint blocks = len / 64; + + if(blocks > 0) + { + previousCrc = ~Clmul.Step(data, blocks * 64, ~previousCrc); + + currentPos = (int)(blocks * 64); + len -= blocks * 64; + } + + if(len == 0) + return; + } + + if(Crc32.Arm64.IsSupported) + { + previousCrc = ArmSimd.Step64(data, len, previousCrc); return; } - int currentPos = 0; - - if(useIso) + if(Crc32.IsSupported) { - if(Pclmulqdq.IsSupported && - Sse41.IsSupported && - Ssse3.IsSupported && - Sse2.IsSupported) + previousCrc = ArmSimd.Step32(data, len, previousCrc); + + return; + } + + if(AdvSimd.IsSupported) + { + // Only works in blocks of 16 bytes + uint blocks = len / 64; + + if(blocks > 0) { - // Only works in blocks of 16 bytes - uint blocks = len / 64; + previousCrc = ~Vmull.Step(data, blocks * 64, ~previousCrc); - if(blocks > 0) - { - previousCrc = ~Clmul.Step(data, blocks * 64, ~previousCrc); - - currentPos = (int)(blocks * 64); - len -= blocks * 64; - } - - if(len == 0) - return; + currentPos = (int)(blocks * 64); + len -= blocks * 64; } - if(Crc32.Arm64.IsSupported) - { - previousCrc = ArmSimd.Step64(data, len, previousCrc); - + if(len == 0) return; - } - - if(Crc32.IsSupported) - { - previousCrc = ArmSimd.Step32(data, len, previousCrc); - - return; - } - - if(AdvSimd.IsSupported) - { - // Only works in blocks of 16 bytes - uint blocks = len / 64; - - if(blocks > 0) - { - previousCrc = ~Vmull.Step(data, blocks * 64, ~previousCrc); - - currentPos = (int)(blocks * 64); - len -= blocks * 64; - } - - if(len == 0) - return; - } } - - // Unroll according to Intel slicing by uint8_t - // http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf - // http://sourceforge.net/projects/slicing-by-8/ - const int unroll = 4; - const int bytesAtOnce = 8 * unroll; - uint crc = previousCrc; - - while(len >= bytesAtOnce) - { - int unrolling; - - for(unrolling = 0; unrolling < unroll; unrolling++) - { - uint one = BitConverter.ToUInt32(data, currentPos) ^ crc; - currentPos += 4; - uint two = BitConverter.ToUInt32(data, currentPos); - currentPos += 4; - - crc = table[0][(two >> 24) & 0xFF] ^ table[1][(two >> 16) & 0xFF] ^ table[2][(two >> 8) & 0xFF] ^ - table[3][two & 0xFF] ^ table[4][(one >> 24) & 0xFF] ^ table[5][(one >> 16) & 0xFF] ^ - table[6][(one >> 8) & 0xFF] ^ table[7][one & 0xFF]; - } - - len -= bytesAtOnce; - } - - while(len-- != 0) - crc = (crc >> 8) ^ table[0][(crc & 0xFF) ^ data[currentPos++]]; - - previousCrc = crc; } - /// Gets the hash of a file - /// File path. - public static byte[] File(string filename) + // Unroll according to Intel slicing by uint8_t + // http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf + // http://sourceforge.net/projects/slicing-by-8/ + const int unroll = 4; + const int bytesAtOnce = 8 * unroll; + uint crc = previousCrc; + + while(len >= bytesAtOnce) { - File(filename, out byte[] hash); + int unrolling; - return hash; + for(unrolling = 0; unrolling < unroll; unrolling++) + { + uint one = BitConverter.ToUInt32(data, currentPos) ^ crc; + currentPos += 4; + uint two = BitConverter.ToUInt32(data, currentPos); + currentPos += 4; + + crc = table[0][(two >> 24) & 0xFF] ^ table[1][(two >> 16) & 0xFF] ^ table[2][(two >> 8) & 0xFF] ^ + table[3][two & 0xFF] ^ table[4][(one >> 24) & 0xFF] ^ table[5][(one >> 16) & 0xFF] ^ + table[6][(one >> 8) & 0xFF] ^ table[7][one & 0xFF]; + } + + len -= bytesAtOnce; } - /// Gets the hash of a file in hexadecimal and as a byte array. - /// File path. - /// Byte array of the hash value. - public static string File(string filename, out byte[] hash) => - File(filename, out hash, CRC32_ISO_POLY, CRC32_ISO_SEED); + while(len-- != 0) + crc = (crc >> 8) ^ table[0][(crc & 0xFF) ^ data[currentPos++]]; - /// Gets the hash of a file in hexadecimal and as a byte array. - /// File path. - /// Byte array of the hash value. - /// CRC polynomial - /// CRC seed - public static string File(string filename, out byte[] hash, uint polynomial, uint seed) - { - bool useIso = polynomial == CRC32_ISO_POLY && seed == CRC32_ISO_SEED; - bool useNative = Native.IsSupported; - IntPtr nativeContext = IntPtr.Zero; - - if(useNative && useIso) - { - nativeContext = crc32_init(); - useNative = nativeContext != IntPtr.Zero; - } - - var fileStream = new FileStream(filename, FileMode.Open); - - uint localHashInt = seed; - - uint[][] localTable = GenerateTable(polynomial); - - byte[] buffer = new byte[65536]; - int read = fileStream.Read(buffer, 0, 65536); - - while(read > 0) - { - Step(ref localHashInt, localTable, buffer, (uint)read, useIso, useNative, nativeContext); - - read = fileStream.Read(buffer, 0, 65536); - } - - localHashInt ^= seed; - - if(useNative && useIso) - { - crc32_final(nativeContext, ref localHashInt); - crc32_free(nativeContext); - } - - hash = BigEndianBitConverter.GetBytes(localHashInt); - - var crc32Output = new StringBuilder(); - - foreach(byte h in hash) - crc32Output.Append(h.ToString("x2")); - - fileStream.Close(); - - return crc32Output.ToString(); - } - - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Length of the data buffer to hash. - /// Byte array of the hash value. - public static string Data(byte[] data, uint len, out byte[] hash) => - Data(data, len, out hash, CRC32_ISO_POLY, CRC32_ISO_SEED); - - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Length of the data buffer to hash. - /// Byte array of the hash value. - /// CRC polynomial - /// CRC seed - public static string Data(byte[] data, uint len, out byte[] hash, uint polynomial, uint seed) - { - bool useIso = polynomial == CRC32_ISO_POLY && seed == CRC32_ISO_SEED; - bool useNative = Native.IsSupported; - IntPtr nativeContext = IntPtr.Zero; - - if(useNative && useIso) - { - nativeContext = crc32_init(); - useNative = nativeContext != IntPtr.Zero; - } - - uint localHashInt = seed; - - uint[][] localTable = GenerateTable(polynomial); - - Step(ref localHashInt, localTable, data, len, useIso, useNative, nativeContext); - - localHashInt ^= seed; - - if(useNative && useIso) - { - crc32_final(nativeContext, ref localHashInt); - crc32_free(nativeContext); - } - - hash = BigEndianBitConverter.GetBytes(localHashInt); - - var crc32Output = new StringBuilder(); - - foreach(byte h in hash) - crc32Output.Append(h.ToString("x2")); - - return crc32Output.ToString(); - } - - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Byte array of the hash value. - public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); + previousCrc = crc; } + + /// Gets the hash of a file + /// File path. + public static byte[] File(string filename) + { + File(filename, out byte[] hash); + + return hash; + } + + /// Gets the hash of a file in hexadecimal and as a byte array. + /// File path. + /// Byte array of the hash value. + public static string File(string filename, out byte[] hash) => + File(filename, out hash, CRC32_ISO_POLY, CRC32_ISO_SEED); + + /// Gets the hash of a file in hexadecimal and as a byte array. + /// File path. + /// Byte array of the hash value. + /// CRC polynomial + /// CRC seed + public static string File(string filename, out byte[] hash, uint polynomial, uint seed) + { + bool useIso = polynomial == CRC32_ISO_POLY && seed == CRC32_ISO_SEED; + bool useNative = Native.IsSupported; + IntPtr nativeContext = IntPtr.Zero; + + if(useNative && useIso) + { + nativeContext = crc32_init(); + useNative = nativeContext != IntPtr.Zero; + } + + var fileStream = new FileStream(filename, FileMode.Open); + + uint localHashInt = seed; + + uint[][] localTable = GenerateTable(polynomial); + + byte[] buffer = new byte[65536]; + int read = fileStream.Read(buffer, 0, 65536); + + while(read > 0) + { + Step(ref localHashInt, localTable, buffer, (uint)read, useIso, useNative, nativeContext); + + read = fileStream.Read(buffer, 0, 65536); + } + + localHashInt ^= seed; + + if(useNative && useIso) + { + crc32_final(nativeContext, ref localHashInt); + crc32_free(nativeContext); + } + + hash = BigEndianBitConverter.GetBytes(localHashInt); + + var crc32Output = new StringBuilder(); + + foreach(byte h in hash) + crc32Output.Append(h.ToString("x2")); + + fileStream.Close(); + + return crc32Output.ToString(); + } + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Length of the data buffer to hash. + /// Byte array of the hash value. + public static string Data(byte[] data, uint len, out byte[] hash) => + Data(data, len, out hash, CRC32_ISO_POLY, CRC32_ISO_SEED); + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Length of the data buffer to hash. + /// Byte array of the hash value. + /// CRC polynomial + /// CRC seed + public static string Data(byte[] data, uint len, out byte[] hash, uint polynomial, uint seed) + { + bool useIso = polynomial == CRC32_ISO_POLY && seed == CRC32_ISO_SEED; + bool useNative = Native.IsSupported; + IntPtr nativeContext = IntPtr.Zero; + + if(useNative && useIso) + { + nativeContext = crc32_init(); + useNative = nativeContext != IntPtr.Zero; + } + + uint localHashInt = seed; + + uint[][] localTable = GenerateTable(polynomial); + + Step(ref localHashInt, localTable, data, len, useIso, useNative, nativeContext); + + localHashInt ^= seed; + + if(useNative && useIso) + { + crc32_final(nativeContext, ref localHashInt); + crc32_free(nativeContext); + } + + hash = BigEndianBitConverter.GetBytes(localHashInt); + + var crc32Output = new StringBuilder(); + + foreach(byte h in hash) + crc32Output.Append(h.ToString("x2")); + + return crc32Output.ToString(); + } + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Byte array of the hash value. + public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); } \ No newline at end of file diff --git a/Aaru6.Checksums/CRC64/clmul.cs b/Aaru6.Checksums/CRC64/clmul.cs index bacae0d..48f920a 100644 --- a/Aaru6.Checksums/CRC64/clmul.cs +++ b/Aaru6.Checksums/CRC64/clmul.cs @@ -26,86 +26,85 @@ using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace Aaru6.Checksums.CRC64 +namespace Aaru6.Checksums.CRC64; + +internal static class Clmul { - internal static class Clmul + static readonly byte[] _shuffleMasks = { - static readonly byte[] _shuffleMasks = + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x8f, 0x8e, + 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80 + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void ShiftRight128(Vector128 initial, uint n, out Vector128 outLeft, + out Vector128 outRight) + { + uint maskPos = 16 - n; + + Vector128 maskA = Vector128.Create(_shuffleMasks[maskPos], _shuffleMasks[maskPos + 1], + _shuffleMasks[maskPos + 2], _shuffleMasks[maskPos + 3], + _shuffleMasks[maskPos + 4], _shuffleMasks[maskPos + 5], + _shuffleMasks[maskPos + 6], _shuffleMasks[maskPos + 7], + _shuffleMasks[maskPos + 8], _shuffleMasks[maskPos + 9], + _shuffleMasks[maskPos + 10], _shuffleMasks[maskPos + 11], + _shuffleMasks[maskPos + 12], _shuffleMasks[maskPos + 13], + _shuffleMasks[maskPos + 14], _shuffleMasks[maskPos + 15]); + + Vector128 maskB = Sse2.Xor(maskA, Sse2.CompareEqual(Vector128.Zero, Vector128.Zero)); + + outLeft = Ssse3.Shuffle(initial.AsByte(), maskB).AsUInt64(); + outRight = Ssse3.Shuffle(initial.AsByte(), maskA).AsUInt64(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static Vector128 Fold(Vector128 input, Vector128 foldConstants) => + Sse2.Xor(Pclmulqdq.CarrylessMultiply(input, foldConstants, 0x00), + Pclmulqdq.CarrylessMultiply(input, foldConstants, 0x11)); + + internal static ulong Step(ulong crc, byte[] data, uint length) + { + int bufPos = 16; + const ulong k1 = 0xe05dd497ca393ae4; + const ulong k2 = 0xdabe95afc7875f40; + const ulong mu = 0x9c3e466c172963d5; + const ulong pol = 0x92d8af2baf0e1e85; + Vector128 foldConstants1 = Vector128.Create(k1, k2); + Vector128 foldConstants2 = Vector128.Create(mu, pol); + Vector128 initialCrc = Vector128.Create(~crc, 0); + length -= 16; + + // Initial CRC can simply be added to data + ShiftRight128(initialCrc, 0, out Vector128 crc0, out Vector128 crc1); + + Vector128 accumulator = + Sse2.Xor(Fold(Sse2.Xor(crc0, Vector128.Create(BitConverter.ToUInt64(data, 0), BitConverter.ToUInt64(data, 8))), foldConstants1), + crc1); + + while(length >= 32) { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x8f, 0x8e, - 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80 - }; + accumulator = + Fold(Sse2.Xor(Vector128.Create(BitConverter.ToUInt64(data, bufPos), BitConverter.ToUInt64(data, bufPos + 8)), accumulator), + foldConstants1); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void ShiftRight128(Vector128 initial, uint n, out Vector128 outLeft, - out Vector128 outRight) - { - uint maskPos = 16 - n; - - Vector128 maskA = Vector128.Create(_shuffleMasks[maskPos], _shuffleMasks[maskPos + 1], - _shuffleMasks[maskPos + 2], _shuffleMasks[maskPos + 3], - _shuffleMasks[maskPos + 4], _shuffleMasks[maskPos + 5], - _shuffleMasks[maskPos + 6], _shuffleMasks[maskPos + 7], - _shuffleMasks[maskPos + 8], _shuffleMasks[maskPos + 9], - _shuffleMasks[maskPos + 10], _shuffleMasks[maskPos + 11], - _shuffleMasks[maskPos + 12], _shuffleMasks[maskPos + 13], - _shuffleMasks[maskPos + 14], _shuffleMasks[maskPos + 15]); - - Vector128 maskB = Sse2.Xor(maskA, Sse2.CompareEqual(Vector128.Zero, Vector128.Zero)); - - outLeft = Ssse3.Shuffle(initial.AsByte(), maskB).AsUInt64(); - outRight = Ssse3.Shuffle(initial.AsByte(), maskA).AsUInt64(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static Vector128 Fold(Vector128 input, Vector128 foldConstants) => - Sse2.Xor(Pclmulqdq.CarrylessMultiply(input, foldConstants, 0x00), - Pclmulqdq.CarrylessMultiply(input, foldConstants, 0x11)); - - internal static ulong Step(ulong crc, byte[] data, uint length) - { - int bufPos = 16; - const ulong k1 = 0xe05dd497ca393ae4; - const ulong k2 = 0xdabe95afc7875f40; - const ulong mu = 0x9c3e466c172963d5; - const ulong pol = 0x92d8af2baf0e1e85; - Vector128 foldConstants1 = Vector128.Create(k1, k2); - Vector128 foldConstants2 = Vector128.Create(mu, pol); - Vector128 initialCrc = Vector128.Create(~crc, 0); length -= 16; - - // Initial CRC can simply be added to data - ShiftRight128(initialCrc, 0, out Vector128 crc0, out Vector128 crc1); - - Vector128 accumulator = - Sse2.Xor(Fold(Sse2.Xor(crc0, Vector128.Create(BitConverter.ToUInt64(data, 0), BitConverter.ToUInt64(data, 8))), foldConstants1), - crc1); - - while(length >= 32) - { - accumulator = - Fold(Sse2.Xor(Vector128.Create(BitConverter.ToUInt64(data, bufPos), BitConverter.ToUInt64(data, bufPos + 8)), accumulator), - foldConstants1); - - length -= 16; - bufPos += 16; - } - - Vector128 p = Sse2.Xor(accumulator, - Vector128.Create(BitConverter.ToUInt64(data, bufPos), - BitConverter.ToUInt64(data, bufPos + 8))); - - Vector128 r = Sse2.Xor(Pclmulqdq.CarrylessMultiply(p, foldConstants1, 0x10), - Sse2.ShiftRightLogical128BitLane(p, 8)); - - // Final Barrett reduction - Vector128 t1 = Pclmulqdq.CarrylessMultiply(r, foldConstants2, 0x00); - - Vector128 t2 = - Sse2.Xor(Sse2.Xor(Pclmulqdq.CarrylessMultiply(t1, foldConstants2, 0x10), Sse2.ShiftLeftLogical128BitLane(t1, 8)), - r); - - return ~(((ulong)Sse41.Extract(t2.AsUInt32(), 3) << 32) | Sse41.Extract(t2.AsUInt32(), 2)); + bufPos += 16; } + + Vector128 p = Sse2.Xor(accumulator, + Vector128.Create(BitConverter.ToUInt64(data, bufPos), + BitConverter.ToUInt64(data, bufPos + 8))); + + Vector128 r = Sse2.Xor(Pclmulqdq.CarrylessMultiply(p, foldConstants1, 0x10), + Sse2.ShiftRightLogical128BitLane(p, 8)); + + // Final Barrett reduction + Vector128 t1 = Pclmulqdq.CarrylessMultiply(r, foldConstants2, 0x00); + + Vector128 t2 = + Sse2.Xor(Sse2.Xor(Pclmulqdq.CarrylessMultiply(t1, foldConstants2, 0x10), Sse2.ShiftLeftLogical128BitLane(t1, 8)), + r); + + return ~(((ulong)Sse41.Extract(t2.AsUInt32(), 3) << 32) | Sse41.Extract(t2.AsUInt32(), 2)); } } \ No newline at end of file diff --git a/Aaru6.Checksums/CRC64Context.cs b/Aaru6.Checksums/CRC64Context.cs index f09fe93..56a3c6b 100644 --- a/Aaru6.Checksums/CRC64Context.cs +++ b/Aaru6.Checksums/CRC64Context.cs @@ -39,546 +39,545 @@ using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; using Aaru6.Checksums.CRC64; -namespace Aaru6.Checksums +namespace Aaru6.Checksums; + +/// +/// Implements a CRC64 algorithm +public sealed class Crc64Context : IChecksum { - /// - /// Implements a CRC64 algorithm - public sealed class Crc64Context : IChecksum + /// ECMA CRC64 polynomial + public const ulong CRC64_ECMA_POLY = 0xC96C5795D7870F42; + /// ECMA CRC64 seed + public const ulong CRC64_ECMA_SEED = 0xFFFFFFFFFFFFFFFF; + + static readonly ulong[][] _ecmaCrc64Table = { - /// ECMA CRC64 polynomial - public const ulong CRC64_ECMA_POLY = 0xC96C5795D7870F42; - /// ECMA CRC64 seed - public const ulong CRC64_ECMA_SEED = 0xFFFFFFFFFFFFFFFF; - - static readonly ulong[][] _ecmaCrc64Table = + new ulong[] { - new ulong[] - { - 0x0000000000000000, 0xB32E4CBE03A75F6F, 0xF4843657A840A05B, 0x47AA7AE9ABE7FF34, 0x7BD0C384FF8F5E33, - 0xC8FE8F3AFC28015C, 0x8F54F5D357CFFE68, 0x3C7AB96D5468A107, 0xF7A18709FF1EBC66, 0x448FCBB7FCB9E309, - 0x0325B15E575E1C3D, 0xB00BFDE054F94352, 0x8C71448D0091E255, 0x3F5F08330336BD3A, 0x78F572DAA8D1420E, - 0xCBDB3E64AB761D61, 0x7D9BA13851336649, 0xCEB5ED8652943926, 0x891F976FF973C612, 0x3A31DBD1FAD4997D, - 0x064B62BCAEBC387A, 0xB5652E02AD1B6715, 0xF2CF54EB06FC9821, 0x41E11855055BC74E, 0x8A3A2631AE2DDA2F, - 0x39146A8FAD8A8540, 0x7EBE1066066D7A74, 0xCD905CD805CA251B, 0xF1EAE5B551A2841C, 0x42C4A90B5205DB73, - 0x056ED3E2F9E22447, 0xB6409F5CFA457B28, 0xFB374270A266CC92, 0x48190ECEA1C193FD, 0x0FB374270A266CC9, - 0xBC9D3899098133A6, 0x80E781F45DE992A1, 0x33C9CD4A5E4ECDCE, 0x7463B7A3F5A932FA, 0xC74DFB1DF60E6D95, - 0x0C96C5795D7870F4, 0xBFB889C75EDF2F9B, 0xF812F32EF538D0AF, 0x4B3CBF90F69F8FC0, 0x774606FDA2F72EC7, - 0xC4684A43A15071A8, 0x83C230AA0AB78E9C, 0x30EC7C140910D1F3, 0x86ACE348F355AADB, 0x3582AFF6F0F2F5B4, - 0x7228D51F5B150A80, 0xC10699A158B255EF, 0xFD7C20CC0CDAF4E8, 0x4E526C720F7DAB87, 0x09F8169BA49A54B3, - 0xBAD65A25A73D0BDC, 0x710D64410C4B16BD, 0xC22328FF0FEC49D2, 0x85895216A40BB6E6, 0x36A71EA8A7ACE989, - 0x0ADDA7C5F3C4488E, 0xB9F3EB7BF06317E1, 0xFE5991925B84E8D5, 0x4D77DD2C5823B7BA, 0x64B62BCAEBC387A1, - 0xD7986774E864D8CE, 0x90321D9D438327FA, 0x231C512340247895, 0x1F66E84E144CD992, 0xAC48A4F017EB86FD, - 0xEBE2DE19BC0C79C9, 0x58CC92A7BFAB26A6, 0x9317ACC314DD3BC7, 0x2039E07D177A64A8, 0x67939A94BC9D9B9C, - 0xD4BDD62ABF3AC4F3, 0xE8C76F47EB5265F4, 0x5BE923F9E8F53A9B, 0x1C4359104312C5AF, 0xAF6D15AE40B59AC0, - 0x192D8AF2BAF0E1E8, 0xAA03C64CB957BE87, 0xEDA9BCA512B041B3, 0x5E87F01B11171EDC, 0x62FD4976457FBFDB, - 0xD1D305C846D8E0B4, 0x96797F21ED3F1F80, 0x2557339FEE9840EF, 0xEE8C0DFB45EE5D8E, 0x5DA24145464902E1, - 0x1A083BACEDAEFDD5, 0xA9267712EE09A2BA, 0x955CCE7FBA6103BD, 0x267282C1B9C65CD2, 0x61D8F8281221A3E6, - 0xD2F6B4961186FC89, 0x9F8169BA49A54B33, 0x2CAF25044A02145C, 0x6B055FEDE1E5EB68, 0xD82B1353E242B407, - 0xE451AA3EB62A1500, 0x577FE680B58D4A6F, 0x10D59C691E6AB55B, 0xA3FBD0D71DCDEA34, 0x6820EEB3B6BBF755, - 0xDB0EA20DB51CA83A, 0x9CA4D8E41EFB570E, 0x2F8A945A1D5C0861, 0x13F02D374934A966, 0xA0DE61894A93F609, - 0xE7741B60E174093D, 0x545A57DEE2D35652, 0xE21AC88218962D7A, 0x5134843C1B317215, 0x169EFED5B0D68D21, - 0xA5B0B26BB371D24E, 0x99CA0B06E7197349, 0x2AE447B8E4BE2C26, 0x6D4E3D514F59D312, 0xDE6071EF4CFE8C7D, - 0x15BB4F8BE788911C, 0xA6950335E42FCE73, 0xE13F79DC4FC83147, 0x521135624C6F6E28, 0x6E6B8C0F1807CF2F, - 0xDD45C0B11BA09040, 0x9AEFBA58B0476F74, 0x29C1F6E6B3E0301B, 0xC96C5795D7870F42, 0x7A421B2BD420502D, - 0x3DE861C27FC7AF19, 0x8EC62D7C7C60F076, 0xB2BC941128085171, 0x0192D8AF2BAF0E1E, 0x4638A2468048F12A, - 0xF516EEF883EFAE45, 0x3ECDD09C2899B324, 0x8DE39C222B3EEC4B, 0xCA49E6CB80D9137F, 0x7967AA75837E4C10, - 0x451D1318D716ED17, 0xF6335FA6D4B1B278, 0xB199254F7F564D4C, 0x02B769F17CF11223, 0xB4F7F6AD86B4690B, - 0x07D9BA1385133664, 0x4073C0FA2EF4C950, 0xF35D8C442D53963F, 0xCF273529793B3738, 0x7C0979977A9C6857, - 0x3BA3037ED17B9763, 0x888D4FC0D2DCC80C, 0x435671A479AAD56D, 0xF0783D1A7A0D8A02, 0xB7D247F3D1EA7536, - 0x04FC0B4DD24D2A59, 0x3886B22086258B5E, 0x8BA8FE9E8582D431, 0xCC0284772E652B05, 0x7F2CC8C92DC2746A, - 0x325B15E575E1C3D0, 0x8175595B76469CBF, 0xC6DF23B2DDA1638B, 0x75F16F0CDE063CE4, 0x498BD6618A6E9DE3, - 0xFAA59ADF89C9C28C, 0xBD0FE036222E3DB8, 0x0E21AC88218962D7, 0xC5FA92EC8AFF7FB6, 0x76D4DE52895820D9, - 0x317EA4BB22BFDFED, 0x8250E80521188082, 0xBE2A516875702185, 0x0D041DD676D77EEA, 0x4AAE673FDD3081DE, - 0xF9802B81DE97DEB1, 0x4FC0B4DD24D2A599, 0xFCEEF8632775FAF6, 0xBB44828A8C9205C2, 0x086ACE348F355AAD, - 0x34107759DB5DFBAA, 0x873E3BE7D8FAA4C5, 0xC094410E731D5BF1, 0x73BA0DB070BA049E, 0xB86133D4DBCC19FF, - 0x0B4F7F6AD86B4690, 0x4CE50583738CB9A4, 0xFFCB493D702BE6CB, 0xC3B1F050244347CC, 0x709FBCEE27E418A3, - 0x3735C6078C03E797, 0x841B8AB98FA4B8F8, 0xADDA7C5F3C4488E3, 0x1EF430E13FE3D78C, 0x595E4A08940428B8, - 0xEA7006B697A377D7, 0xD60ABFDBC3CBD6D0, 0x6524F365C06C89BF, 0x228E898C6B8B768B, 0x91A0C532682C29E4, - 0x5A7BFB56C35A3485, 0xE955B7E8C0FD6BEA, 0xAEFFCD016B1A94DE, 0x1DD181BF68BDCBB1, 0x21AB38D23CD56AB6, - 0x9285746C3F7235D9, 0xD52F0E859495CAED, 0x6601423B97329582, 0xD041DD676D77EEAA, 0x636F91D96ED0B1C5, - 0x24C5EB30C5374EF1, 0x97EBA78EC690119E, 0xAB911EE392F8B099, 0x18BF525D915FEFF6, 0x5F1528B43AB810C2, - 0xEC3B640A391F4FAD, 0x27E05A6E926952CC, 0x94CE16D091CE0DA3, 0xD3646C393A29F297, 0x604A2087398EADF8, - 0x5C3099EA6DE60CFF, 0xEF1ED5546E415390, 0xA8B4AFBDC5A6ACA4, 0x1B9AE303C601F3CB, 0x56ED3E2F9E224471, - 0xE5C372919D851B1E, 0xA26908783662E42A, 0x114744C635C5BB45, 0x2D3DFDAB61AD1A42, 0x9E13B115620A452D, - 0xD9B9CBFCC9EDBA19, 0x6A978742CA4AE576, 0xA14CB926613CF817, 0x1262F598629BA778, 0x55C88F71C97C584C, - 0xE6E6C3CFCADB0723, 0xDA9C7AA29EB3A624, 0x69B2361C9D14F94B, 0x2E184CF536F3067F, 0x9D36004B35545910, - 0x2B769F17CF112238, 0x9858D3A9CCB67D57, 0xDFF2A94067518263, 0x6CDCE5FE64F6DD0C, 0x50A65C93309E7C0B, - 0xE388102D33392364, 0xA4226AC498DEDC50, 0x170C267A9B79833F, 0xDCD7181E300F9E5E, 0x6FF954A033A8C131, - 0x28532E49984F3E05, 0x9B7D62F79BE8616A, 0xA707DB9ACF80C06D, 0x14299724CC279F02, 0x5383EDCD67C06036, - 0xE0ADA17364673F59 - }, - new ulong[] - { - 0x0000000000000000, 0x54E979925CD0F10D, 0xA9D2F324B9A1E21A, 0xFD3B8AB6E5711317, 0xC17D4962DC4DDAB1, - 0x959430F0809D2BBC, 0x68AFBA4665EC38AB, 0x3C46C3D4393CC9A6, 0x10223DEE1795ABE7, 0x44CB447C4B455AEA, - 0xB9F0CECAAE3449FD, 0xED19B758F2E4B8F0, 0xD15F748CCBD87156, 0x85B60D1E9708805B, 0x788D87A87279934C, - 0x2C64FE3A2EA96241, 0x20447BDC2F2B57CE, 0x74AD024E73FBA6C3, 0x899688F8968AB5D4, 0xDD7FF16ACA5A44D9, - 0xE13932BEF3668D7F, 0xB5D04B2CAFB67C72, 0x48EBC19A4AC76F65, 0x1C02B80816179E68, 0x3066463238BEFC29, - 0x648F3FA0646E0D24, 0x99B4B516811F1E33, 0xCD5DCC84DDCFEF3E, 0xF11B0F50E4F32698, 0xA5F276C2B823D795, - 0x58C9FC745D52C482, 0x0C2085E60182358F, 0x4088F7B85E56AF9C, 0x14618E2A02865E91, 0xE95A049CE7F74D86, - 0xBDB37D0EBB27BC8B, 0x81F5BEDA821B752D, 0xD51CC748DECB8420, 0x28274DFE3BBA9737, 0x7CCE346C676A663A, - 0x50AACA5649C3047B, 0x0443B3C41513F576, 0xF9783972F062E661, 0xAD9140E0ACB2176C, 0x91D78334958EDECA, - 0xC53EFAA6C95E2FC7, 0x380570102C2F3CD0, 0x6CEC098270FFCDDD, 0x60CC8C64717DF852, 0x3425F5F62DAD095F, - 0xC91E7F40C8DC1A48, 0x9DF706D2940CEB45, 0xA1B1C506AD3022E3, 0xF558BC94F1E0D3EE, 0x086336221491C0F9, - 0x5C8A4FB0484131F4, 0x70EEB18A66E853B5, 0x2407C8183A38A2B8, 0xD93C42AEDF49B1AF, 0x8DD53B3C839940A2, - 0xB193F8E8BAA58904, 0xE57A817AE6757809, 0x18410BCC03046B1E, 0x4CA8725E5FD49A13, 0x8111EF70BCAD5F38, - 0xD5F896E2E07DAE35, 0x28C31C54050CBD22, 0x7C2A65C659DC4C2F, 0x406CA61260E08589, 0x1485DF803C307484, - 0xE9BE5536D9416793, 0xBD572CA48591969E, 0x9133D29EAB38F4DF, 0xC5DAAB0CF7E805D2, 0x38E121BA129916C5, - 0x6C0858284E49E7C8, 0x504E9BFC77752E6E, 0x04A7E26E2BA5DF63, 0xF99C68D8CED4CC74, 0xAD75114A92043D79, - 0xA15594AC938608F6, 0xF5BCED3ECF56F9FB, 0x088767882A27EAEC, 0x5C6E1E1A76F71BE1, 0x6028DDCE4FCBD247, - 0x34C1A45C131B234A, 0xC9FA2EEAF66A305D, 0x9D135778AABAC150, 0xB177A9428413A311, 0xE59ED0D0D8C3521C, - 0x18A55A663DB2410B, 0x4C4C23F46162B006, 0x700AE020585E79A0, 0x24E399B2048E88AD, 0xD9D81304E1FF9BBA, - 0x8D316A96BD2F6AB7, 0xC19918C8E2FBF0A4, 0x9570615ABE2B01A9, 0x684BEBEC5B5A12BE, 0x3CA2927E078AE3B3, - 0x00E451AA3EB62A15, 0x540D28386266DB18, 0xA936A28E8717C80F, 0xFDDFDB1CDBC73902, 0xD1BB2526F56E5B43, - 0x85525CB4A9BEAA4E, 0x7869D6024CCFB959, 0x2C80AF90101F4854, 0x10C66C44292381F2, 0x442F15D675F370FF, - 0xB9149F60908263E8, 0xEDFDE6F2CC5292E5, 0xE1DD6314CDD0A76A, 0xB5341A8691005667, 0x480F903074714570, - 0x1CE6E9A228A1B47D, 0x20A02A76119D7DDB, 0x744953E44D4D8CD6, 0x8972D952A83C9FC1, 0xDD9BA0C0F4EC6ECC, - 0xF1FF5EFADA450C8D, 0xA51627688695FD80, 0x582DADDE63E4EE97, 0x0CC4D44C3F341F9A, 0x308217980608D63C, - 0x646B6E0A5AD82731, 0x9950E4BCBFA93426, 0xCDB99D2EE379C52B, 0x90FB71CAD654A0F5, 0xC41208588A8451F8, - 0x392982EE6FF542EF, 0x6DC0FB7C3325B3E2, 0x518638A80A197A44, 0x056F413A56C98B49, 0xF854CB8CB3B8985E, - 0xACBDB21EEF686953, 0x80D94C24C1C10B12, 0xD43035B69D11FA1F, 0x290BBF007860E908, 0x7DE2C69224B01805, - 0x41A405461D8CD1A3, 0x154D7CD4415C20AE, 0xE876F662A42D33B9, 0xBC9F8FF0F8FDC2B4, 0xB0BF0A16F97FF73B, - 0xE4567384A5AF0636, 0x196DF93240DE1521, 0x4D8480A01C0EE42C, 0x71C2437425322D8A, 0x252B3AE679E2DC87, - 0xD810B0509C93CF90, 0x8CF9C9C2C0433E9D, 0xA09D37F8EEEA5CDC, 0xF4744E6AB23AADD1, 0x094FC4DC574BBEC6, - 0x5DA6BD4E0B9B4FCB, 0x61E07E9A32A7866D, 0x350907086E777760, 0xC8328DBE8B066477, 0x9CDBF42CD7D6957A, - 0xD073867288020F69, 0x849AFFE0D4D2FE64, 0x79A1755631A3ED73, 0x2D480CC46D731C7E, 0x110ECF10544FD5D8, - 0x45E7B682089F24D5, 0xB8DC3C34EDEE37C2, 0xEC3545A6B13EC6CF, 0xC051BB9C9F97A48E, 0x94B8C20EC3475583, - 0x698348B826364694, 0x3D6A312A7AE6B799, 0x012CF2FE43DA7E3F, 0x55C58B6C1F0A8F32, 0xA8FE01DAFA7B9C25, - 0xFC177848A6AB6D28, 0xF037FDAEA72958A7, 0xA4DE843CFBF9A9AA, 0x59E50E8A1E88BABD, 0x0D0C771842584BB0, - 0x314AB4CC7B648216, 0x65A3CD5E27B4731B, 0x989847E8C2C5600C, 0xCC713E7A9E159101, 0xE015C040B0BCF340, - 0xB4FCB9D2EC6C024D, 0x49C73364091D115A, 0x1D2E4AF655CDE057, 0x216889226CF129F1, 0x7581F0B03021D8FC, - 0x88BA7A06D550CBEB, 0xDC53039489803AE6, 0x11EA9EBA6AF9FFCD, 0x4503E72836290EC0, 0xB8386D9ED3581DD7, - 0xECD1140C8F88ECDA, 0xD097D7D8B6B4257C, 0x847EAE4AEA64D471, 0x794524FC0F15C766, 0x2DAC5D6E53C5366B, - 0x01C8A3547D6C542A, 0x5521DAC621BCA527, 0xA81A5070C4CDB630, 0xFCF329E2981D473D, 0xC0B5EA36A1218E9B, - 0x945C93A4FDF17F96, 0x6967191218806C81, 0x3D8E608044509D8C, 0x31AEE56645D2A803, 0x65479CF41902590E, - 0x987C1642FC734A19, 0xCC956FD0A0A3BB14, 0xF0D3AC04999F72B2, 0xA43AD596C54F83BF, 0x59015F20203E90A8, - 0x0DE826B27CEE61A5, 0x218CD888524703E4, 0x7565A11A0E97F2E9, 0x885E2BACEBE6E1FE, 0xDCB7523EB73610F3, - 0xE0F191EA8E0AD955, 0xB418E878D2DA2858, 0x492362CE37AB3B4F, 0x1DCA1B5C6B7BCA42, 0x5162690234AF5051, - 0x058B1090687FA15C, 0xF8B09A268D0EB24B, 0xAC59E3B4D1DE4346, 0x901F2060E8E28AE0, 0xC4F659F2B4327BED, - 0x39CDD344514368FA, 0x6D24AAD60D9399F7, 0x414054EC233AFBB6, 0x15A92D7E7FEA0ABB, 0xE892A7C89A9B19AC, - 0xBC7BDE5AC64BE8A1, 0x803D1D8EFF772107, 0xD4D4641CA3A7D00A, 0x29EFEEAA46D6C31D, 0x7D0697381A063210, - 0x712612DE1B84079F, 0x25CF6B4C4754F692, 0xD8F4E1FAA225E585, 0x8C1D9868FEF51488, 0xB05B5BBCC7C9DD2E, - 0xE4B2222E9B192C23, 0x1989A8987E683F34, 0x4D60D10A22B8CE39, 0x61042F300C11AC78, 0x35ED56A250C15D75, - 0xC8D6DC14B5B04E62, 0x9C3FA586E960BF6F, 0xA0796652D05C76C9, 0xF4901FC08C8C87C4, 0x09AB957669FD94D3, - 0x5D42ECE4352D65DE - }, - new ulong[] - { - 0x0000000000000000, 0x3F0BE14A916A6DCB, 0x7E17C29522D4DB96, 0x411C23DFB3BEB65D, 0xFC2F852A45A9B72C, - 0xC3246460D4C3DAE7, 0x823847BF677D6CBA, 0xBD33A6F5F6170171, 0x6A87A57F245D70DD, 0x558C4435B5371D16, - 0x149067EA0689AB4B, 0x2B9B86A097E3C680, 0x96A8205561F4C7F1, 0xA9A3C11FF09EAA3A, 0xE8BFE2C043201C67, - 0xD7B4038AD24A71AC, 0xD50F4AFE48BAE1BA, 0xEA04ABB4D9D08C71, 0xAB18886B6A6E3A2C, 0x94136921FB0457E7, - 0x2920CFD40D135696, 0x162B2E9E9C793B5D, 0x57370D412FC78D00, 0x683CEC0BBEADE0CB, 0xBF88EF816CE79167, - 0x80830ECBFD8DFCAC, 0xC19F2D144E334AF1, 0xFE94CC5EDF59273A, 0x43A76AAB294E264B, 0x7CAC8BE1B8244B80, - 0x3DB0A83E0B9AFDDD, 0x02BB49749AF09016, 0x38C63AD73E7BDDF1, 0x07CDDB9DAF11B03A, 0x46D1F8421CAF0667, - 0x79DA19088DC56BAC, 0xC4E9BFFD7BD26ADD, 0xFBE25EB7EAB80716, 0xBAFE7D685906B14B, 0x85F59C22C86CDC80, - 0x52419FA81A26AD2C, 0x6D4A7EE28B4CC0E7, 0x2C565D3D38F276BA, 0x135DBC77A9981B71, 0xAE6E1A825F8F1A00, - 0x9165FBC8CEE577CB, 0xD079D8177D5BC196, 0xEF72395DEC31AC5D, 0xEDC9702976C13C4B, 0xD2C29163E7AB5180, - 0x93DEB2BC5415E7DD, 0xACD553F6C57F8A16, 0x11E6F50333688B67, 0x2EED1449A202E6AC, 0x6FF1379611BC50F1, - 0x50FAD6DC80D63D3A, 0x874ED556529C4C96, 0xB845341CC3F6215D, 0xF95917C370489700, 0xC652F689E122FACB, - 0x7B61507C1735FBBA, 0x446AB136865F9671, 0x057692E935E1202C, 0x3A7D73A3A48B4DE7, 0x718C75AE7CF7BBE2, - 0x4E8794E4ED9DD629, 0x0F9BB73B5E236074, 0x30905671CF490DBF, 0x8DA3F084395E0CCE, 0xB2A811CEA8346105, - 0xF3B432111B8AD758, 0xCCBFD35B8AE0BA93, 0x1B0BD0D158AACB3F, 0x2400319BC9C0A6F4, 0x651C12447A7E10A9, - 0x5A17F30EEB147D62, 0xE72455FB1D037C13, 0xD82FB4B18C6911D8, 0x9933976E3FD7A785, 0xA6387624AEBDCA4E, - 0xA4833F50344D5A58, 0x9B88DE1AA5273793, 0xDA94FDC5169981CE, 0xE59F1C8F87F3EC05, 0x58ACBA7A71E4ED74, - 0x67A75B30E08E80BF, 0x26BB78EF533036E2, 0x19B099A5C25A5B29, 0xCE049A2F10102A85, 0xF10F7B65817A474E, - 0xB01358BA32C4F113, 0x8F18B9F0A3AE9CD8, 0x322B1F0555B99DA9, 0x0D20FE4FC4D3F062, 0x4C3CDD90776D463F, - 0x73373CDAE6072BF4, 0x494A4F79428C6613, 0x7641AE33D3E60BD8, 0x375D8DEC6058BD85, 0x08566CA6F132D04E, - 0xB565CA530725D13F, 0x8A6E2B19964FBCF4, 0xCB7208C625F10AA9, 0xF479E98CB49B6762, 0x23CDEA0666D116CE, - 0x1CC60B4CF7BB7B05, 0x5DDA28934405CD58, 0x62D1C9D9D56FA093, 0xDFE26F2C2378A1E2, 0xE0E98E66B212CC29, - 0xA1F5ADB901AC7A74, 0x9EFE4CF390C617BF, 0x9C4505870A3687A9, 0xA34EE4CD9B5CEA62, 0xE252C71228E25C3F, - 0xDD592658B98831F4, 0x606A80AD4F9F3085, 0x5F6161E7DEF55D4E, 0x1E7D42386D4BEB13, 0x2176A372FC2186D8, - 0xF6C2A0F82E6BF774, 0xC9C941B2BF019ABF, 0x88D5626D0CBF2CE2, 0xB7DE83279DD54129, 0x0AED25D26BC24058, - 0x35E6C498FAA82D93, 0x74FAE74749169BCE, 0x4BF1060DD87CF605, 0xE318EB5CF9EF77C4, 0xDC130A1668851A0F, - 0x9D0F29C9DB3BAC52, 0xA204C8834A51C199, 0x1F376E76BC46C0E8, 0x203C8F3C2D2CAD23, 0x6120ACE39E921B7E, - 0x5E2B4DA90FF876B5, 0x899F4E23DDB20719, 0xB694AF694CD86AD2, 0xF7888CB6FF66DC8F, 0xC8836DFC6E0CB144, - 0x75B0CB09981BB035, 0x4ABB2A430971DDFE, 0x0BA7099CBACF6BA3, 0x34ACE8D62BA50668, 0x3617A1A2B155967E, - 0x091C40E8203FFBB5, 0x4800633793814DE8, 0x770B827D02EB2023, 0xCA382488F4FC2152, 0xF533C5C265964C99, - 0xB42FE61DD628FAC4, 0x8B2407574742970F, 0x5C9004DD9508E6A3, 0x639BE59704628B68, 0x2287C648B7DC3D35, - 0x1D8C270226B650FE, 0xA0BF81F7D0A1518F, 0x9FB460BD41CB3C44, 0xDEA84362F2758A19, 0xE1A3A228631FE7D2, - 0xDBDED18BC794AA35, 0xE4D530C156FEC7FE, 0xA5C9131EE54071A3, 0x9AC2F254742A1C68, 0x27F154A1823D1D19, - 0x18FAB5EB135770D2, 0x59E69634A0E9C68F, 0x66ED777E3183AB44, 0xB15974F4E3C9DAE8, 0x8E5295BE72A3B723, - 0xCF4EB661C11D017E, 0xF045572B50776CB5, 0x4D76F1DEA6606DC4, 0x727D1094370A000F, 0x3361334B84B4B652, - 0x0C6AD20115DEDB99, 0x0ED19B758F2E4B8F, 0x31DA7A3F1E442644, 0x70C659E0ADFA9019, 0x4FCDB8AA3C90FDD2, - 0xF2FE1E5FCA87FCA3, 0xCDF5FF155BED9168, 0x8CE9DCCAE8532735, 0xB3E23D8079394AFE, 0x64563E0AAB733B52, - 0x5B5DDF403A195699, 0x1A41FC9F89A7E0C4, 0x254A1DD518CD8D0F, 0x9879BB20EEDA8C7E, 0xA7725A6A7FB0E1B5, - 0xE66E79B5CC0E57E8, 0xD96598FF5D643A23, 0x92949EF28518CC26, 0xAD9F7FB81472A1ED, 0xEC835C67A7CC17B0, - 0xD388BD2D36A67A7B, 0x6EBB1BD8C0B17B0A, 0x51B0FA9251DB16C1, 0x10ACD94DE265A09C, 0x2FA73807730FCD57, - 0xF8133B8DA145BCFB, 0xC718DAC7302FD130, 0x8604F9188391676D, 0xB90F185212FB0AA6, 0x043CBEA7E4EC0BD7, - 0x3B375FED7586661C, 0x7A2B7C32C638D041, 0x45209D785752BD8A, 0x479BD40CCDA22D9C, 0x789035465CC84057, - 0x398C1699EF76F60A, 0x0687F7D37E1C9BC1, 0xBBB45126880B9AB0, 0x84BFB06C1961F77B, 0xC5A393B3AADF4126, - 0xFAA872F93BB52CED, 0x2D1C7173E9FF5D41, 0x121790397895308A, 0x530BB3E6CB2B86D7, 0x6C0052AC5A41EB1C, - 0xD133F459AC56EA6D, 0xEE3815133D3C87A6, 0xAF2436CC8E8231FB, 0x902FD7861FE85C30, 0xAA52A425BB6311D7, - 0x9559456F2A097C1C, 0xD44566B099B7CA41, 0xEB4E87FA08DDA78A, 0x567D210FFECAA6FB, 0x6976C0456FA0CB30, - 0x286AE39ADC1E7D6D, 0x176102D04D7410A6, 0xC0D5015A9F3E610A, 0xFFDEE0100E540CC1, 0xBEC2C3CFBDEABA9C, - 0x81C922852C80D757, 0x3CFA8470DA97D626, 0x03F1653A4BFDBBED, 0x42ED46E5F8430DB0, 0x7DE6A7AF6929607B, - 0x7F5DEEDBF3D9F06D, 0x40560F9162B39DA6, 0x014A2C4ED10D2BFB, 0x3E41CD0440674630, 0x83726BF1B6704741, - 0xBC798ABB271A2A8A, 0xFD65A96494A49CD7, 0xC26E482E05CEF11C, 0x15DA4BA4D78480B0, 0x2AD1AAEE46EEED7B, - 0x6BCD8931F5505B26, 0x54C6687B643A36ED, 0xE9F5CE8E922D379C, 0xD6FE2FC403475A57, 0x97E20C1BB0F9EC0A, - 0xA8E9ED51219381C1 - }, - new ulong[] - { - 0x0000000000000000, 0x1DEE8A5E222CA1DC, 0x3BDD14BC445943B8, 0x26339EE26675E264, 0x77BA297888B28770, - 0x6A54A326AA9E26AC, 0x4C673DC4CCEBC4C8, 0x5189B79AEEC76514, 0xEF7452F111650EE0, 0xF29AD8AF3349AF3C, - 0xD4A9464D553C4D58, 0xC947CC137710EC84, 0x98CE7B8999D78990, 0x8520F1D7BBFB284C, 0xA3136F35DD8ECA28, - 0xBEFDE56BFFA26BF4, 0x4C300AC98DC40345, 0x51DE8097AFE8A299, 0x77ED1E75C99D40FD, 0x6A03942BEBB1E121, - 0x3B8A23B105768435, 0x2664A9EF275A25E9, 0x0057370D412FC78D, 0x1DB9BD5363036651, 0xA34458389CA10DA5, - 0xBEAAD266BE8DAC79, 0x98994C84D8F84E1D, 0x8577C6DAFAD4EFC1, 0xD4FE714014138AD5, 0xC910FB1E363F2B09, - 0xEF2365FC504AC96D, 0xF2CDEFA2726668B1, 0x986015931B88068A, 0x858E9FCD39A4A756, 0xA3BD012F5FD14532, - 0xBE538B717DFDE4EE, 0xEFDA3CEB933A81FA, 0xF234B6B5B1162026, 0xD4072857D763C242, 0xC9E9A209F54F639E, - 0x771447620AED086A, 0x6AFACD3C28C1A9B6, 0x4CC953DE4EB44BD2, 0x5127D9806C98EA0E, 0x00AE6E1A825F8F1A, - 0x1D40E444A0732EC6, 0x3B737AA6C606CCA2, 0x269DF0F8E42A6D7E, 0xD4501F5A964C05CF, 0xC9BE9504B460A413, - 0xEF8D0BE6D2154677, 0xF26381B8F039E7AB, 0xA3EA36221EFE82BF, 0xBE04BC7C3CD22363, 0x9837229E5AA7C107, - 0x85D9A8C0788B60DB, 0x3B244DAB87290B2F, 0x26CAC7F5A505AAF3, 0x00F95917C3704897, 0x1D17D349E15CE94B, - 0x4C9E64D30F9B8C5F, 0x5170EE8D2DB72D83, 0x7743706F4BC2CFE7, 0x6AADFA3169EE6E3B, 0xA218840D981E1391, - 0xBFF60E53BA32B24D, 0x99C590B1DC475029, 0x842B1AEFFE6BF1F5, 0xD5A2AD7510AC94E1, 0xC84C272B3280353D, - 0xEE7FB9C954F5D759, 0xF391339776D97685, 0x4D6CD6FC897B1D71, 0x50825CA2AB57BCAD, 0x76B1C240CD225EC9, - 0x6B5F481EEF0EFF15, 0x3AD6FF8401C99A01, 0x273875DA23E53BDD, 0x010BEB384590D9B9, 0x1CE5616667BC7865, - 0xEE288EC415DA10D4, 0xF3C6049A37F6B108, 0xD5F59A785183536C, 0xC81B102673AFF2B0, 0x9992A7BC9D6897A4, - 0x847C2DE2BF443678, 0xA24FB300D931D41C, 0xBFA1395EFB1D75C0, 0x015CDC3504BF1E34, 0x1CB2566B2693BFE8, - 0x3A81C88940E65D8C, 0x276F42D762CAFC50, 0x76E6F54D8C0D9944, 0x6B087F13AE213898, 0x4D3BE1F1C854DAFC, - 0x50D56BAFEA787B20, 0x3A78919E8396151B, 0x27961BC0A1BAB4C7, 0x01A58522C7CF56A3, 0x1C4B0F7CE5E3F77F, - 0x4DC2B8E60B24926B, 0x502C32B8290833B7, 0x761FAC5A4F7DD1D3, 0x6BF126046D51700F, 0xD50CC36F92F31BFB, - 0xC8E24931B0DFBA27, 0xEED1D7D3D6AA5843, 0xF33F5D8DF486F99F, 0xA2B6EA171A419C8B, 0xBF586049386D3D57, - 0x996BFEAB5E18DF33, 0x848574F57C347EEF, 0x76489B570E52165E, 0x6BA611092C7EB782, 0x4D958FEB4A0B55E6, - 0x507B05B56827F43A, 0x01F2B22F86E0912E, 0x1C1C3871A4CC30F2, 0x3A2FA693C2B9D296, 0x27C12CCDE095734A, - 0x993CC9A61F3718BE, 0x84D243F83D1BB962, 0xA2E1DD1A5B6E5B06, 0xBF0F57447942FADA, 0xEE86E0DE97859FCE, - 0xF3686A80B5A93E12, 0xD55BF462D3DCDC76, 0xC8B57E3CF1F07DAA, 0xD6E9A7309F3239A7, 0xCB072D6EBD1E987B, - 0xED34B38CDB6B7A1F, 0xF0DA39D2F947DBC3, 0xA1538E481780BED7, 0xBCBD041635AC1F0B, 0x9A8E9AF453D9FD6F, - 0x876010AA71F55CB3, 0x399DF5C18E573747, 0x24737F9FAC7B969B, 0x0240E17DCA0E74FF, 0x1FAE6B23E822D523, - 0x4E27DCB906E5B037, 0x53C956E724C911EB, 0x75FAC80542BCF38F, 0x6814425B60905253, 0x9AD9ADF912F63AE2, - 0x873727A730DA9B3E, 0xA104B94556AF795A, 0xBCEA331B7483D886, 0xED6384819A44BD92, 0xF08D0EDFB8681C4E, - 0xD6BE903DDE1DFE2A, 0xCB501A63FC315FF6, 0x75ADFF0803933402, 0x6843755621BF95DE, 0x4E70EBB447CA77BA, - 0x539E61EA65E6D666, 0x0217D6708B21B372, 0x1FF95C2EA90D12AE, 0x39CAC2CCCF78F0CA, 0x24244892ED545116, - 0x4E89B2A384BA3F2D, 0x536738FDA6969EF1, 0x7554A61FC0E37C95, 0x68BA2C41E2CFDD49, 0x39339BDB0C08B85D, - 0x24DD11852E241981, 0x02EE8F674851FBE5, 0x1F0005396A7D5A39, 0xA1FDE05295DF31CD, 0xBC136A0CB7F39011, - 0x9A20F4EED1867275, 0x87CE7EB0F3AAD3A9, 0xD647C92A1D6DB6BD, 0xCBA943743F411761, 0xED9ADD965934F505, - 0xF07457C87B1854D9, 0x02B9B86A097E3C68, 0x1F5732342B529DB4, 0x3964ACD64D277FD0, 0x248A26886F0BDE0C, - 0x7503911281CCBB18, 0x68ED1B4CA3E01AC4, 0x4EDE85AEC595F8A0, 0x53300FF0E7B9597C, 0xEDCDEA9B181B3288, - 0xF02360C53A379354, 0xD610FE275C427130, 0xCBFE74797E6ED0EC, 0x9A77C3E390A9B5F8, 0x879949BDB2851424, - 0xA1AAD75FD4F0F640, 0xBC445D01F6DC579C, 0x74F1233D072C2A36, 0x691FA96325008BEA, 0x4F2C37814375698E, - 0x52C2BDDF6159C852, 0x034B0A458F9EAD46, 0x1EA5801BADB20C9A, 0x38961EF9CBC7EEFE, 0x257894A7E9EB4F22, - 0x9B8571CC164924D6, 0x866BFB923465850A, 0xA05865705210676E, 0xBDB6EF2E703CC6B2, 0xEC3F58B49EFBA3A6, - 0xF1D1D2EABCD7027A, 0xD7E24C08DAA2E01E, 0xCA0CC656F88E41C2, 0x38C129F48AE82973, 0x252FA3AAA8C488AF, - 0x031C3D48CEB16ACB, 0x1EF2B716EC9DCB17, 0x4F7B008C025AAE03, 0x52958AD220760FDF, 0x74A614304603EDBB, - 0x69489E6E642F4C67, 0xD7B57B059B8D2793, 0xCA5BF15BB9A1864F, 0xEC686FB9DFD4642B, 0xF186E5E7FDF8C5F7, - 0xA00F527D133FA0E3, 0xBDE1D8233113013F, 0x9BD246C15766E35B, 0x863CCC9F754A4287, 0xEC9136AE1CA42CBC, - 0xF17FBCF03E888D60, 0xD74C221258FD6F04, 0xCAA2A84C7AD1CED8, 0x9B2B1FD69416ABCC, 0x86C59588B63A0A10, - 0xA0F60B6AD04FE874, 0xBD188134F26349A8, 0x03E5645F0DC1225C, 0x1E0BEE012FED8380, 0x383870E3499861E4, - 0x25D6FABD6BB4C038, 0x745F4D278573A52C, 0x69B1C779A75F04F0, 0x4F82599BC12AE694, 0x526CD3C5E3064748, - 0xA0A13C6791602FF9, 0xBD4FB639B34C8E25, 0x9B7C28DBD5396C41, 0x8692A285F715CD9D, 0xD71B151F19D2A889, - 0xCAF59F413BFE0955, 0xECC601A35D8BEB31, 0xF1288BFD7FA74AED, 0x4FD56E9680052119, 0x523BE4C8A22980C5, - 0x74087A2AC45C62A1, 0x69E6F074E670C37D, 0x386F47EE08B7A669, 0x2581CDB02A9B07B5, 0x03B253524CEEE5D1, - 0x1E5CD90C6EC2440D - } - }; - - readonly ulong _finalSeed; - readonly IntPtr _nativeContext; - readonly ulong[][] _table; - readonly bool _useEcma; - readonly bool _useNative; - ulong _hashInt; - - /// Initializes the CRC64 table and seed as CRC64-ECMA - public Crc64Context() + 0x0000000000000000, 0xB32E4CBE03A75F6F, 0xF4843657A840A05B, 0x47AA7AE9ABE7FF34, 0x7BD0C384FF8F5E33, + 0xC8FE8F3AFC28015C, 0x8F54F5D357CFFE68, 0x3C7AB96D5468A107, 0xF7A18709FF1EBC66, 0x448FCBB7FCB9E309, + 0x0325B15E575E1C3D, 0xB00BFDE054F94352, 0x8C71448D0091E255, 0x3F5F08330336BD3A, 0x78F572DAA8D1420E, + 0xCBDB3E64AB761D61, 0x7D9BA13851336649, 0xCEB5ED8652943926, 0x891F976FF973C612, 0x3A31DBD1FAD4997D, + 0x064B62BCAEBC387A, 0xB5652E02AD1B6715, 0xF2CF54EB06FC9821, 0x41E11855055BC74E, 0x8A3A2631AE2DDA2F, + 0x39146A8FAD8A8540, 0x7EBE1066066D7A74, 0xCD905CD805CA251B, 0xF1EAE5B551A2841C, 0x42C4A90B5205DB73, + 0x056ED3E2F9E22447, 0xB6409F5CFA457B28, 0xFB374270A266CC92, 0x48190ECEA1C193FD, 0x0FB374270A266CC9, + 0xBC9D3899098133A6, 0x80E781F45DE992A1, 0x33C9CD4A5E4ECDCE, 0x7463B7A3F5A932FA, 0xC74DFB1DF60E6D95, + 0x0C96C5795D7870F4, 0xBFB889C75EDF2F9B, 0xF812F32EF538D0AF, 0x4B3CBF90F69F8FC0, 0x774606FDA2F72EC7, + 0xC4684A43A15071A8, 0x83C230AA0AB78E9C, 0x30EC7C140910D1F3, 0x86ACE348F355AADB, 0x3582AFF6F0F2F5B4, + 0x7228D51F5B150A80, 0xC10699A158B255EF, 0xFD7C20CC0CDAF4E8, 0x4E526C720F7DAB87, 0x09F8169BA49A54B3, + 0xBAD65A25A73D0BDC, 0x710D64410C4B16BD, 0xC22328FF0FEC49D2, 0x85895216A40BB6E6, 0x36A71EA8A7ACE989, + 0x0ADDA7C5F3C4488E, 0xB9F3EB7BF06317E1, 0xFE5991925B84E8D5, 0x4D77DD2C5823B7BA, 0x64B62BCAEBC387A1, + 0xD7986774E864D8CE, 0x90321D9D438327FA, 0x231C512340247895, 0x1F66E84E144CD992, 0xAC48A4F017EB86FD, + 0xEBE2DE19BC0C79C9, 0x58CC92A7BFAB26A6, 0x9317ACC314DD3BC7, 0x2039E07D177A64A8, 0x67939A94BC9D9B9C, + 0xD4BDD62ABF3AC4F3, 0xE8C76F47EB5265F4, 0x5BE923F9E8F53A9B, 0x1C4359104312C5AF, 0xAF6D15AE40B59AC0, + 0x192D8AF2BAF0E1E8, 0xAA03C64CB957BE87, 0xEDA9BCA512B041B3, 0x5E87F01B11171EDC, 0x62FD4976457FBFDB, + 0xD1D305C846D8E0B4, 0x96797F21ED3F1F80, 0x2557339FEE9840EF, 0xEE8C0DFB45EE5D8E, 0x5DA24145464902E1, + 0x1A083BACEDAEFDD5, 0xA9267712EE09A2BA, 0x955CCE7FBA6103BD, 0x267282C1B9C65CD2, 0x61D8F8281221A3E6, + 0xD2F6B4961186FC89, 0x9F8169BA49A54B33, 0x2CAF25044A02145C, 0x6B055FEDE1E5EB68, 0xD82B1353E242B407, + 0xE451AA3EB62A1500, 0x577FE680B58D4A6F, 0x10D59C691E6AB55B, 0xA3FBD0D71DCDEA34, 0x6820EEB3B6BBF755, + 0xDB0EA20DB51CA83A, 0x9CA4D8E41EFB570E, 0x2F8A945A1D5C0861, 0x13F02D374934A966, 0xA0DE61894A93F609, + 0xE7741B60E174093D, 0x545A57DEE2D35652, 0xE21AC88218962D7A, 0x5134843C1B317215, 0x169EFED5B0D68D21, + 0xA5B0B26BB371D24E, 0x99CA0B06E7197349, 0x2AE447B8E4BE2C26, 0x6D4E3D514F59D312, 0xDE6071EF4CFE8C7D, + 0x15BB4F8BE788911C, 0xA6950335E42FCE73, 0xE13F79DC4FC83147, 0x521135624C6F6E28, 0x6E6B8C0F1807CF2F, + 0xDD45C0B11BA09040, 0x9AEFBA58B0476F74, 0x29C1F6E6B3E0301B, 0xC96C5795D7870F42, 0x7A421B2BD420502D, + 0x3DE861C27FC7AF19, 0x8EC62D7C7C60F076, 0xB2BC941128085171, 0x0192D8AF2BAF0E1E, 0x4638A2468048F12A, + 0xF516EEF883EFAE45, 0x3ECDD09C2899B324, 0x8DE39C222B3EEC4B, 0xCA49E6CB80D9137F, 0x7967AA75837E4C10, + 0x451D1318D716ED17, 0xF6335FA6D4B1B278, 0xB199254F7F564D4C, 0x02B769F17CF11223, 0xB4F7F6AD86B4690B, + 0x07D9BA1385133664, 0x4073C0FA2EF4C950, 0xF35D8C442D53963F, 0xCF273529793B3738, 0x7C0979977A9C6857, + 0x3BA3037ED17B9763, 0x888D4FC0D2DCC80C, 0x435671A479AAD56D, 0xF0783D1A7A0D8A02, 0xB7D247F3D1EA7536, + 0x04FC0B4DD24D2A59, 0x3886B22086258B5E, 0x8BA8FE9E8582D431, 0xCC0284772E652B05, 0x7F2CC8C92DC2746A, + 0x325B15E575E1C3D0, 0x8175595B76469CBF, 0xC6DF23B2DDA1638B, 0x75F16F0CDE063CE4, 0x498BD6618A6E9DE3, + 0xFAA59ADF89C9C28C, 0xBD0FE036222E3DB8, 0x0E21AC88218962D7, 0xC5FA92EC8AFF7FB6, 0x76D4DE52895820D9, + 0x317EA4BB22BFDFED, 0x8250E80521188082, 0xBE2A516875702185, 0x0D041DD676D77EEA, 0x4AAE673FDD3081DE, + 0xF9802B81DE97DEB1, 0x4FC0B4DD24D2A599, 0xFCEEF8632775FAF6, 0xBB44828A8C9205C2, 0x086ACE348F355AAD, + 0x34107759DB5DFBAA, 0x873E3BE7D8FAA4C5, 0xC094410E731D5BF1, 0x73BA0DB070BA049E, 0xB86133D4DBCC19FF, + 0x0B4F7F6AD86B4690, 0x4CE50583738CB9A4, 0xFFCB493D702BE6CB, 0xC3B1F050244347CC, 0x709FBCEE27E418A3, + 0x3735C6078C03E797, 0x841B8AB98FA4B8F8, 0xADDA7C5F3C4488E3, 0x1EF430E13FE3D78C, 0x595E4A08940428B8, + 0xEA7006B697A377D7, 0xD60ABFDBC3CBD6D0, 0x6524F365C06C89BF, 0x228E898C6B8B768B, 0x91A0C532682C29E4, + 0x5A7BFB56C35A3485, 0xE955B7E8C0FD6BEA, 0xAEFFCD016B1A94DE, 0x1DD181BF68BDCBB1, 0x21AB38D23CD56AB6, + 0x9285746C3F7235D9, 0xD52F0E859495CAED, 0x6601423B97329582, 0xD041DD676D77EEAA, 0x636F91D96ED0B1C5, + 0x24C5EB30C5374EF1, 0x97EBA78EC690119E, 0xAB911EE392F8B099, 0x18BF525D915FEFF6, 0x5F1528B43AB810C2, + 0xEC3B640A391F4FAD, 0x27E05A6E926952CC, 0x94CE16D091CE0DA3, 0xD3646C393A29F297, 0x604A2087398EADF8, + 0x5C3099EA6DE60CFF, 0xEF1ED5546E415390, 0xA8B4AFBDC5A6ACA4, 0x1B9AE303C601F3CB, 0x56ED3E2F9E224471, + 0xE5C372919D851B1E, 0xA26908783662E42A, 0x114744C635C5BB45, 0x2D3DFDAB61AD1A42, 0x9E13B115620A452D, + 0xD9B9CBFCC9EDBA19, 0x6A978742CA4AE576, 0xA14CB926613CF817, 0x1262F598629BA778, 0x55C88F71C97C584C, + 0xE6E6C3CFCADB0723, 0xDA9C7AA29EB3A624, 0x69B2361C9D14F94B, 0x2E184CF536F3067F, 0x9D36004B35545910, + 0x2B769F17CF112238, 0x9858D3A9CCB67D57, 0xDFF2A94067518263, 0x6CDCE5FE64F6DD0C, 0x50A65C93309E7C0B, + 0xE388102D33392364, 0xA4226AC498DEDC50, 0x170C267A9B79833F, 0xDCD7181E300F9E5E, 0x6FF954A033A8C131, + 0x28532E49984F3E05, 0x9B7D62F79BE8616A, 0xA707DB9ACF80C06D, 0x14299724CC279F02, 0x5383EDCD67C06036, + 0xE0ADA17364673F59 + }, + new ulong[] { - _hashInt = CRC64_ECMA_SEED; - _table = _ecmaCrc64Table; - _finalSeed = CRC64_ECMA_SEED; - _useEcma = true; + 0x0000000000000000, 0x54E979925CD0F10D, 0xA9D2F324B9A1E21A, 0xFD3B8AB6E5711317, 0xC17D4962DC4DDAB1, + 0x959430F0809D2BBC, 0x68AFBA4665EC38AB, 0x3C46C3D4393CC9A6, 0x10223DEE1795ABE7, 0x44CB447C4B455AEA, + 0xB9F0CECAAE3449FD, 0xED19B758F2E4B8F0, 0xD15F748CCBD87156, 0x85B60D1E9708805B, 0x788D87A87279934C, + 0x2C64FE3A2EA96241, 0x20447BDC2F2B57CE, 0x74AD024E73FBA6C3, 0x899688F8968AB5D4, 0xDD7FF16ACA5A44D9, + 0xE13932BEF3668D7F, 0xB5D04B2CAFB67C72, 0x48EBC19A4AC76F65, 0x1C02B80816179E68, 0x3066463238BEFC29, + 0x648F3FA0646E0D24, 0x99B4B516811F1E33, 0xCD5DCC84DDCFEF3E, 0xF11B0F50E4F32698, 0xA5F276C2B823D795, + 0x58C9FC745D52C482, 0x0C2085E60182358F, 0x4088F7B85E56AF9C, 0x14618E2A02865E91, 0xE95A049CE7F74D86, + 0xBDB37D0EBB27BC8B, 0x81F5BEDA821B752D, 0xD51CC748DECB8420, 0x28274DFE3BBA9737, 0x7CCE346C676A663A, + 0x50AACA5649C3047B, 0x0443B3C41513F576, 0xF9783972F062E661, 0xAD9140E0ACB2176C, 0x91D78334958EDECA, + 0xC53EFAA6C95E2FC7, 0x380570102C2F3CD0, 0x6CEC098270FFCDDD, 0x60CC8C64717DF852, 0x3425F5F62DAD095F, + 0xC91E7F40C8DC1A48, 0x9DF706D2940CEB45, 0xA1B1C506AD3022E3, 0xF558BC94F1E0D3EE, 0x086336221491C0F9, + 0x5C8A4FB0484131F4, 0x70EEB18A66E853B5, 0x2407C8183A38A2B8, 0xD93C42AEDF49B1AF, 0x8DD53B3C839940A2, + 0xB193F8E8BAA58904, 0xE57A817AE6757809, 0x18410BCC03046B1E, 0x4CA8725E5FD49A13, 0x8111EF70BCAD5F38, + 0xD5F896E2E07DAE35, 0x28C31C54050CBD22, 0x7C2A65C659DC4C2F, 0x406CA61260E08589, 0x1485DF803C307484, + 0xE9BE5536D9416793, 0xBD572CA48591969E, 0x9133D29EAB38F4DF, 0xC5DAAB0CF7E805D2, 0x38E121BA129916C5, + 0x6C0858284E49E7C8, 0x504E9BFC77752E6E, 0x04A7E26E2BA5DF63, 0xF99C68D8CED4CC74, 0xAD75114A92043D79, + 0xA15594AC938608F6, 0xF5BCED3ECF56F9FB, 0x088767882A27EAEC, 0x5C6E1E1A76F71BE1, 0x6028DDCE4FCBD247, + 0x34C1A45C131B234A, 0xC9FA2EEAF66A305D, 0x9D135778AABAC150, 0xB177A9428413A311, 0xE59ED0D0D8C3521C, + 0x18A55A663DB2410B, 0x4C4C23F46162B006, 0x700AE020585E79A0, 0x24E399B2048E88AD, 0xD9D81304E1FF9BBA, + 0x8D316A96BD2F6AB7, 0xC19918C8E2FBF0A4, 0x9570615ABE2B01A9, 0x684BEBEC5B5A12BE, 0x3CA2927E078AE3B3, + 0x00E451AA3EB62A15, 0x540D28386266DB18, 0xA936A28E8717C80F, 0xFDDFDB1CDBC73902, 0xD1BB2526F56E5B43, + 0x85525CB4A9BEAA4E, 0x7869D6024CCFB959, 0x2C80AF90101F4854, 0x10C66C44292381F2, 0x442F15D675F370FF, + 0xB9149F60908263E8, 0xEDFDE6F2CC5292E5, 0xE1DD6314CDD0A76A, 0xB5341A8691005667, 0x480F903074714570, + 0x1CE6E9A228A1B47D, 0x20A02A76119D7DDB, 0x744953E44D4D8CD6, 0x8972D952A83C9FC1, 0xDD9BA0C0F4EC6ECC, + 0xF1FF5EFADA450C8D, 0xA51627688695FD80, 0x582DADDE63E4EE97, 0x0CC4D44C3F341F9A, 0x308217980608D63C, + 0x646B6E0A5AD82731, 0x9950E4BCBFA93426, 0xCDB99D2EE379C52B, 0x90FB71CAD654A0F5, 0xC41208588A8451F8, + 0x392982EE6FF542EF, 0x6DC0FB7C3325B3E2, 0x518638A80A197A44, 0x056F413A56C98B49, 0xF854CB8CB3B8985E, + 0xACBDB21EEF686953, 0x80D94C24C1C10B12, 0xD43035B69D11FA1F, 0x290BBF007860E908, 0x7DE2C69224B01805, + 0x41A405461D8CD1A3, 0x154D7CD4415C20AE, 0xE876F662A42D33B9, 0xBC9F8FF0F8FDC2B4, 0xB0BF0A16F97FF73B, + 0xE4567384A5AF0636, 0x196DF93240DE1521, 0x4D8480A01C0EE42C, 0x71C2437425322D8A, 0x252B3AE679E2DC87, + 0xD810B0509C93CF90, 0x8CF9C9C2C0433E9D, 0xA09D37F8EEEA5CDC, 0xF4744E6AB23AADD1, 0x094FC4DC574BBEC6, + 0x5DA6BD4E0B9B4FCB, 0x61E07E9A32A7866D, 0x350907086E777760, 0xC8328DBE8B066477, 0x9CDBF42CD7D6957A, + 0xD073867288020F69, 0x849AFFE0D4D2FE64, 0x79A1755631A3ED73, 0x2D480CC46D731C7E, 0x110ECF10544FD5D8, + 0x45E7B682089F24D5, 0xB8DC3C34EDEE37C2, 0xEC3545A6B13EC6CF, 0xC051BB9C9F97A48E, 0x94B8C20EC3475583, + 0x698348B826364694, 0x3D6A312A7AE6B799, 0x012CF2FE43DA7E3F, 0x55C58B6C1F0A8F32, 0xA8FE01DAFA7B9C25, + 0xFC177848A6AB6D28, 0xF037FDAEA72958A7, 0xA4DE843CFBF9A9AA, 0x59E50E8A1E88BABD, 0x0D0C771842584BB0, + 0x314AB4CC7B648216, 0x65A3CD5E27B4731B, 0x989847E8C2C5600C, 0xCC713E7A9E159101, 0xE015C040B0BCF340, + 0xB4FCB9D2EC6C024D, 0x49C73364091D115A, 0x1D2E4AF655CDE057, 0x216889226CF129F1, 0x7581F0B03021D8FC, + 0x88BA7A06D550CBEB, 0xDC53039489803AE6, 0x11EA9EBA6AF9FFCD, 0x4503E72836290EC0, 0xB8386D9ED3581DD7, + 0xECD1140C8F88ECDA, 0xD097D7D8B6B4257C, 0x847EAE4AEA64D471, 0x794524FC0F15C766, 0x2DAC5D6E53C5366B, + 0x01C8A3547D6C542A, 0x5521DAC621BCA527, 0xA81A5070C4CDB630, 0xFCF329E2981D473D, 0xC0B5EA36A1218E9B, + 0x945C93A4FDF17F96, 0x6967191218806C81, 0x3D8E608044509D8C, 0x31AEE56645D2A803, 0x65479CF41902590E, + 0x987C1642FC734A19, 0xCC956FD0A0A3BB14, 0xF0D3AC04999F72B2, 0xA43AD596C54F83BF, 0x59015F20203E90A8, + 0x0DE826B27CEE61A5, 0x218CD888524703E4, 0x7565A11A0E97F2E9, 0x885E2BACEBE6E1FE, 0xDCB7523EB73610F3, + 0xE0F191EA8E0AD955, 0xB418E878D2DA2858, 0x492362CE37AB3B4F, 0x1DCA1B5C6B7BCA42, 0x5162690234AF5051, + 0x058B1090687FA15C, 0xF8B09A268D0EB24B, 0xAC59E3B4D1DE4346, 0x901F2060E8E28AE0, 0xC4F659F2B4327BED, + 0x39CDD344514368FA, 0x6D24AAD60D9399F7, 0x414054EC233AFBB6, 0x15A92D7E7FEA0ABB, 0xE892A7C89A9B19AC, + 0xBC7BDE5AC64BE8A1, 0x803D1D8EFF772107, 0xD4D4641CA3A7D00A, 0x29EFEEAA46D6C31D, 0x7D0697381A063210, + 0x712612DE1B84079F, 0x25CF6B4C4754F692, 0xD8F4E1FAA225E585, 0x8C1D9868FEF51488, 0xB05B5BBCC7C9DD2E, + 0xE4B2222E9B192C23, 0x1989A8987E683F34, 0x4D60D10A22B8CE39, 0x61042F300C11AC78, 0x35ED56A250C15D75, + 0xC8D6DC14B5B04E62, 0x9C3FA586E960BF6F, 0xA0796652D05C76C9, 0xF4901FC08C8C87C4, 0x09AB957669FD94D3, + 0x5D42ECE4352D65DE + }, + new ulong[] + { + 0x0000000000000000, 0x3F0BE14A916A6DCB, 0x7E17C29522D4DB96, 0x411C23DFB3BEB65D, 0xFC2F852A45A9B72C, + 0xC3246460D4C3DAE7, 0x823847BF677D6CBA, 0xBD33A6F5F6170171, 0x6A87A57F245D70DD, 0x558C4435B5371D16, + 0x149067EA0689AB4B, 0x2B9B86A097E3C680, 0x96A8205561F4C7F1, 0xA9A3C11FF09EAA3A, 0xE8BFE2C043201C67, + 0xD7B4038AD24A71AC, 0xD50F4AFE48BAE1BA, 0xEA04ABB4D9D08C71, 0xAB18886B6A6E3A2C, 0x94136921FB0457E7, + 0x2920CFD40D135696, 0x162B2E9E9C793B5D, 0x57370D412FC78D00, 0x683CEC0BBEADE0CB, 0xBF88EF816CE79167, + 0x80830ECBFD8DFCAC, 0xC19F2D144E334AF1, 0xFE94CC5EDF59273A, 0x43A76AAB294E264B, 0x7CAC8BE1B8244B80, + 0x3DB0A83E0B9AFDDD, 0x02BB49749AF09016, 0x38C63AD73E7BDDF1, 0x07CDDB9DAF11B03A, 0x46D1F8421CAF0667, + 0x79DA19088DC56BAC, 0xC4E9BFFD7BD26ADD, 0xFBE25EB7EAB80716, 0xBAFE7D685906B14B, 0x85F59C22C86CDC80, + 0x52419FA81A26AD2C, 0x6D4A7EE28B4CC0E7, 0x2C565D3D38F276BA, 0x135DBC77A9981B71, 0xAE6E1A825F8F1A00, + 0x9165FBC8CEE577CB, 0xD079D8177D5BC196, 0xEF72395DEC31AC5D, 0xEDC9702976C13C4B, 0xD2C29163E7AB5180, + 0x93DEB2BC5415E7DD, 0xACD553F6C57F8A16, 0x11E6F50333688B67, 0x2EED1449A202E6AC, 0x6FF1379611BC50F1, + 0x50FAD6DC80D63D3A, 0x874ED556529C4C96, 0xB845341CC3F6215D, 0xF95917C370489700, 0xC652F689E122FACB, + 0x7B61507C1735FBBA, 0x446AB136865F9671, 0x057692E935E1202C, 0x3A7D73A3A48B4DE7, 0x718C75AE7CF7BBE2, + 0x4E8794E4ED9DD629, 0x0F9BB73B5E236074, 0x30905671CF490DBF, 0x8DA3F084395E0CCE, 0xB2A811CEA8346105, + 0xF3B432111B8AD758, 0xCCBFD35B8AE0BA93, 0x1B0BD0D158AACB3F, 0x2400319BC9C0A6F4, 0x651C12447A7E10A9, + 0x5A17F30EEB147D62, 0xE72455FB1D037C13, 0xD82FB4B18C6911D8, 0x9933976E3FD7A785, 0xA6387624AEBDCA4E, + 0xA4833F50344D5A58, 0x9B88DE1AA5273793, 0xDA94FDC5169981CE, 0xE59F1C8F87F3EC05, 0x58ACBA7A71E4ED74, + 0x67A75B30E08E80BF, 0x26BB78EF533036E2, 0x19B099A5C25A5B29, 0xCE049A2F10102A85, 0xF10F7B65817A474E, + 0xB01358BA32C4F113, 0x8F18B9F0A3AE9CD8, 0x322B1F0555B99DA9, 0x0D20FE4FC4D3F062, 0x4C3CDD90776D463F, + 0x73373CDAE6072BF4, 0x494A4F79428C6613, 0x7641AE33D3E60BD8, 0x375D8DEC6058BD85, 0x08566CA6F132D04E, + 0xB565CA530725D13F, 0x8A6E2B19964FBCF4, 0xCB7208C625F10AA9, 0xF479E98CB49B6762, 0x23CDEA0666D116CE, + 0x1CC60B4CF7BB7B05, 0x5DDA28934405CD58, 0x62D1C9D9D56FA093, 0xDFE26F2C2378A1E2, 0xE0E98E66B212CC29, + 0xA1F5ADB901AC7A74, 0x9EFE4CF390C617BF, 0x9C4505870A3687A9, 0xA34EE4CD9B5CEA62, 0xE252C71228E25C3F, + 0xDD592658B98831F4, 0x606A80AD4F9F3085, 0x5F6161E7DEF55D4E, 0x1E7D42386D4BEB13, 0x2176A372FC2186D8, + 0xF6C2A0F82E6BF774, 0xC9C941B2BF019ABF, 0x88D5626D0CBF2CE2, 0xB7DE83279DD54129, 0x0AED25D26BC24058, + 0x35E6C498FAA82D93, 0x74FAE74749169BCE, 0x4BF1060DD87CF605, 0xE318EB5CF9EF77C4, 0xDC130A1668851A0F, + 0x9D0F29C9DB3BAC52, 0xA204C8834A51C199, 0x1F376E76BC46C0E8, 0x203C8F3C2D2CAD23, 0x6120ACE39E921B7E, + 0x5E2B4DA90FF876B5, 0x899F4E23DDB20719, 0xB694AF694CD86AD2, 0xF7888CB6FF66DC8F, 0xC8836DFC6E0CB144, + 0x75B0CB09981BB035, 0x4ABB2A430971DDFE, 0x0BA7099CBACF6BA3, 0x34ACE8D62BA50668, 0x3617A1A2B155967E, + 0x091C40E8203FFBB5, 0x4800633793814DE8, 0x770B827D02EB2023, 0xCA382488F4FC2152, 0xF533C5C265964C99, + 0xB42FE61DD628FAC4, 0x8B2407574742970F, 0x5C9004DD9508E6A3, 0x639BE59704628B68, 0x2287C648B7DC3D35, + 0x1D8C270226B650FE, 0xA0BF81F7D0A1518F, 0x9FB460BD41CB3C44, 0xDEA84362F2758A19, 0xE1A3A228631FE7D2, + 0xDBDED18BC794AA35, 0xE4D530C156FEC7FE, 0xA5C9131EE54071A3, 0x9AC2F254742A1C68, 0x27F154A1823D1D19, + 0x18FAB5EB135770D2, 0x59E69634A0E9C68F, 0x66ED777E3183AB44, 0xB15974F4E3C9DAE8, 0x8E5295BE72A3B723, + 0xCF4EB661C11D017E, 0xF045572B50776CB5, 0x4D76F1DEA6606DC4, 0x727D1094370A000F, 0x3361334B84B4B652, + 0x0C6AD20115DEDB99, 0x0ED19B758F2E4B8F, 0x31DA7A3F1E442644, 0x70C659E0ADFA9019, 0x4FCDB8AA3C90FDD2, + 0xF2FE1E5FCA87FCA3, 0xCDF5FF155BED9168, 0x8CE9DCCAE8532735, 0xB3E23D8079394AFE, 0x64563E0AAB733B52, + 0x5B5DDF403A195699, 0x1A41FC9F89A7E0C4, 0x254A1DD518CD8D0F, 0x9879BB20EEDA8C7E, 0xA7725A6A7FB0E1B5, + 0xE66E79B5CC0E57E8, 0xD96598FF5D643A23, 0x92949EF28518CC26, 0xAD9F7FB81472A1ED, 0xEC835C67A7CC17B0, + 0xD388BD2D36A67A7B, 0x6EBB1BD8C0B17B0A, 0x51B0FA9251DB16C1, 0x10ACD94DE265A09C, 0x2FA73807730FCD57, + 0xF8133B8DA145BCFB, 0xC718DAC7302FD130, 0x8604F9188391676D, 0xB90F185212FB0AA6, 0x043CBEA7E4EC0BD7, + 0x3B375FED7586661C, 0x7A2B7C32C638D041, 0x45209D785752BD8A, 0x479BD40CCDA22D9C, 0x789035465CC84057, + 0x398C1699EF76F60A, 0x0687F7D37E1C9BC1, 0xBBB45126880B9AB0, 0x84BFB06C1961F77B, 0xC5A393B3AADF4126, + 0xFAA872F93BB52CED, 0x2D1C7173E9FF5D41, 0x121790397895308A, 0x530BB3E6CB2B86D7, 0x6C0052AC5A41EB1C, + 0xD133F459AC56EA6D, 0xEE3815133D3C87A6, 0xAF2436CC8E8231FB, 0x902FD7861FE85C30, 0xAA52A425BB6311D7, + 0x9559456F2A097C1C, 0xD44566B099B7CA41, 0xEB4E87FA08DDA78A, 0x567D210FFECAA6FB, 0x6976C0456FA0CB30, + 0x286AE39ADC1E7D6D, 0x176102D04D7410A6, 0xC0D5015A9F3E610A, 0xFFDEE0100E540CC1, 0xBEC2C3CFBDEABA9C, + 0x81C922852C80D757, 0x3CFA8470DA97D626, 0x03F1653A4BFDBBED, 0x42ED46E5F8430DB0, 0x7DE6A7AF6929607B, + 0x7F5DEEDBF3D9F06D, 0x40560F9162B39DA6, 0x014A2C4ED10D2BFB, 0x3E41CD0440674630, 0x83726BF1B6704741, + 0xBC798ABB271A2A8A, 0xFD65A96494A49CD7, 0xC26E482E05CEF11C, 0x15DA4BA4D78480B0, 0x2AD1AAEE46EEED7B, + 0x6BCD8931F5505B26, 0x54C6687B643A36ED, 0xE9F5CE8E922D379C, 0xD6FE2FC403475A57, 0x97E20C1BB0F9EC0A, + 0xA8E9ED51219381C1 + }, + new ulong[] + { + 0x0000000000000000, 0x1DEE8A5E222CA1DC, 0x3BDD14BC445943B8, 0x26339EE26675E264, 0x77BA297888B28770, + 0x6A54A326AA9E26AC, 0x4C673DC4CCEBC4C8, 0x5189B79AEEC76514, 0xEF7452F111650EE0, 0xF29AD8AF3349AF3C, + 0xD4A9464D553C4D58, 0xC947CC137710EC84, 0x98CE7B8999D78990, 0x8520F1D7BBFB284C, 0xA3136F35DD8ECA28, + 0xBEFDE56BFFA26BF4, 0x4C300AC98DC40345, 0x51DE8097AFE8A299, 0x77ED1E75C99D40FD, 0x6A03942BEBB1E121, + 0x3B8A23B105768435, 0x2664A9EF275A25E9, 0x0057370D412FC78D, 0x1DB9BD5363036651, 0xA34458389CA10DA5, + 0xBEAAD266BE8DAC79, 0x98994C84D8F84E1D, 0x8577C6DAFAD4EFC1, 0xD4FE714014138AD5, 0xC910FB1E363F2B09, + 0xEF2365FC504AC96D, 0xF2CDEFA2726668B1, 0x986015931B88068A, 0x858E9FCD39A4A756, 0xA3BD012F5FD14532, + 0xBE538B717DFDE4EE, 0xEFDA3CEB933A81FA, 0xF234B6B5B1162026, 0xD4072857D763C242, 0xC9E9A209F54F639E, + 0x771447620AED086A, 0x6AFACD3C28C1A9B6, 0x4CC953DE4EB44BD2, 0x5127D9806C98EA0E, 0x00AE6E1A825F8F1A, + 0x1D40E444A0732EC6, 0x3B737AA6C606CCA2, 0x269DF0F8E42A6D7E, 0xD4501F5A964C05CF, 0xC9BE9504B460A413, + 0xEF8D0BE6D2154677, 0xF26381B8F039E7AB, 0xA3EA36221EFE82BF, 0xBE04BC7C3CD22363, 0x9837229E5AA7C107, + 0x85D9A8C0788B60DB, 0x3B244DAB87290B2F, 0x26CAC7F5A505AAF3, 0x00F95917C3704897, 0x1D17D349E15CE94B, + 0x4C9E64D30F9B8C5F, 0x5170EE8D2DB72D83, 0x7743706F4BC2CFE7, 0x6AADFA3169EE6E3B, 0xA218840D981E1391, + 0xBFF60E53BA32B24D, 0x99C590B1DC475029, 0x842B1AEFFE6BF1F5, 0xD5A2AD7510AC94E1, 0xC84C272B3280353D, + 0xEE7FB9C954F5D759, 0xF391339776D97685, 0x4D6CD6FC897B1D71, 0x50825CA2AB57BCAD, 0x76B1C240CD225EC9, + 0x6B5F481EEF0EFF15, 0x3AD6FF8401C99A01, 0x273875DA23E53BDD, 0x010BEB384590D9B9, 0x1CE5616667BC7865, + 0xEE288EC415DA10D4, 0xF3C6049A37F6B108, 0xD5F59A785183536C, 0xC81B102673AFF2B0, 0x9992A7BC9D6897A4, + 0x847C2DE2BF443678, 0xA24FB300D931D41C, 0xBFA1395EFB1D75C0, 0x015CDC3504BF1E34, 0x1CB2566B2693BFE8, + 0x3A81C88940E65D8C, 0x276F42D762CAFC50, 0x76E6F54D8C0D9944, 0x6B087F13AE213898, 0x4D3BE1F1C854DAFC, + 0x50D56BAFEA787B20, 0x3A78919E8396151B, 0x27961BC0A1BAB4C7, 0x01A58522C7CF56A3, 0x1C4B0F7CE5E3F77F, + 0x4DC2B8E60B24926B, 0x502C32B8290833B7, 0x761FAC5A4F7DD1D3, 0x6BF126046D51700F, 0xD50CC36F92F31BFB, + 0xC8E24931B0DFBA27, 0xEED1D7D3D6AA5843, 0xF33F5D8DF486F99F, 0xA2B6EA171A419C8B, 0xBF586049386D3D57, + 0x996BFEAB5E18DF33, 0x848574F57C347EEF, 0x76489B570E52165E, 0x6BA611092C7EB782, 0x4D958FEB4A0B55E6, + 0x507B05B56827F43A, 0x01F2B22F86E0912E, 0x1C1C3871A4CC30F2, 0x3A2FA693C2B9D296, 0x27C12CCDE095734A, + 0x993CC9A61F3718BE, 0x84D243F83D1BB962, 0xA2E1DD1A5B6E5B06, 0xBF0F57447942FADA, 0xEE86E0DE97859FCE, + 0xF3686A80B5A93E12, 0xD55BF462D3DCDC76, 0xC8B57E3CF1F07DAA, 0xD6E9A7309F3239A7, 0xCB072D6EBD1E987B, + 0xED34B38CDB6B7A1F, 0xF0DA39D2F947DBC3, 0xA1538E481780BED7, 0xBCBD041635AC1F0B, 0x9A8E9AF453D9FD6F, + 0x876010AA71F55CB3, 0x399DF5C18E573747, 0x24737F9FAC7B969B, 0x0240E17DCA0E74FF, 0x1FAE6B23E822D523, + 0x4E27DCB906E5B037, 0x53C956E724C911EB, 0x75FAC80542BCF38F, 0x6814425B60905253, 0x9AD9ADF912F63AE2, + 0x873727A730DA9B3E, 0xA104B94556AF795A, 0xBCEA331B7483D886, 0xED6384819A44BD92, 0xF08D0EDFB8681C4E, + 0xD6BE903DDE1DFE2A, 0xCB501A63FC315FF6, 0x75ADFF0803933402, 0x6843755621BF95DE, 0x4E70EBB447CA77BA, + 0x539E61EA65E6D666, 0x0217D6708B21B372, 0x1FF95C2EA90D12AE, 0x39CAC2CCCF78F0CA, 0x24244892ED545116, + 0x4E89B2A384BA3F2D, 0x536738FDA6969EF1, 0x7554A61FC0E37C95, 0x68BA2C41E2CFDD49, 0x39339BDB0C08B85D, + 0x24DD11852E241981, 0x02EE8F674851FBE5, 0x1F0005396A7D5A39, 0xA1FDE05295DF31CD, 0xBC136A0CB7F39011, + 0x9A20F4EED1867275, 0x87CE7EB0F3AAD3A9, 0xD647C92A1D6DB6BD, 0xCBA943743F411761, 0xED9ADD965934F505, + 0xF07457C87B1854D9, 0x02B9B86A097E3C68, 0x1F5732342B529DB4, 0x3964ACD64D277FD0, 0x248A26886F0BDE0C, + 0x7503911281CCBB18, 0x68ED1B4CA3E01AC4, 0x4EDE85AEC595F8A0, 0x53300FF0E7B9597C, 0xEDCDEA9B181B3288, + 0xF02360C53A379354, 0xD610FE275C427130, 0xCBFE74797E6ED0EC, 0x9A77C3E390A9B5F8, 0x879949BDB2851424, + 0xA1AAD75FD4F0F640, 0xBC445D01F6DC579C, 0x74F1233D072C2A36, 0x691FA96325008BEA, 0x4F2C37814375698E, + 0x52C2BDDF6159C852, 0x034B0A458F9EAD46, 0x1EA5801BADB20C9A, 0x38961EF9CBC7EEFE, 0x257894A7E9EB4F22, + 0x9B8571CC164924D6, 0x866BFB923465850A, 0xA05865705210676E, 0xBDB6EF2E703CC6B2, 0xEC3F58B49EFBA3A6, + 0xF1D1D2EABCD7027A, 0xD7E24C08DAA2E01E, 0xCA0CC656F88E41C2, 0x38C129F48AE82973, 0x252FA3AAA8C488AF, + 0x031C3D48CEB16ACB, 0x1EF2B716EC9DCB17, 0x4F7B008C025AAE03, 0x52958AD220760FDF, 0x74A614304603EDBB, + 0x69489E6E642F4C67, 0xD7B57B059B8D2793, 0xCA5BF15BB9A1864F, 0xEC686FB9DFD4642B, 0xF186E5E7FDF8C5F7, + 0xA00F527D133FA0E3, 0xBDE1D8233113013F, 0x9BD246C15766E35B, 0x863CCC9F754A4287, 0xEC9136AE1CA42CBC, + 0xF17FBCF03E888D60, 0xD74C221258FD6F04, 0xCAA2A84C7AD1CED8, 0x9B2B1FD69416ABCC, 0x86C59588B63A0A10, + 0xA0F60B6AD04FE874, 0xBD188134F26349A8, 0x03E5645F0DC1225C, 0x1E0BEE012FED8380, 0x383870E3499861E4, + 0x25D6FABD6BB4C038, 0x745F4D278573A52C, 0x69B1C779A75F04F0, 0x4F82599BC12AE694, 0x526CD3C5E3064748, + 0xA0A13C6791602FF9, 0xBD4FB639B34C8E25, 0x9B7C28DBD5396C41, 0x8692A285F715CD9D, 0xD71B151F19D2A889, + 0xCAF59F413BFE0955, 0xECC601A35D8BEB31, 0xF1288BFD7FA74AED, 0x4FD56E9680052119, 0x523BE4C8A22980C5, + 0x74087A2AC45C62A1, 0x69E6F074E670C37D, 0x386F47EE08B7A669, 0x2581CDB02A9B07B5, 0x03B253524CEEE5D1, + 0x1E5CD90C6EC2440D + } + }; - if(!Native.IsSupported) - return; + readonly ulong _finalSeed; + readonly IntPtr _nativeContext; + readonly ulong[][] _table; + readonly bool _useEcma; + readonly bool _useNative; + ulong _hashInt; + /// Initializes the CRC64 table and seed as CRC64-ECMA + public Crc64Context() + { + _hashInt = CRC64_ECMA_SEED; + _table = _ecmaCrc64Table; + _finalSeed = CRC64_ECMA_SEED; + _useEcma = true; + + if(!Native.IsSupported) + return; + + _nativeContext = crc64_init(); + _useNative = _nativeContext != IntPtr.Zero; + } + + /// Initializes the CRC16 table with a custom polynomial and seed + public Crc64Context(ulong polynomial, ulong seed) + { + _hashInt = seed; + _finalSeed = seed; + _useEcma = polynomial == CRC64_ECMA_POLY && seed == CRC64_ECMA_SEED; + + if(Native.IsSupported && _useEcma) + { _nativeContext = crc64_init(); _useNative = _nativeContext != IntPtr.Zero; } + else + _table = GenerateTable(polynomial); + } - /// Initializes the CRC16 table with a custom polynomial and seed - public Crc64Context(ulong polynomial, ulong seed) + /// + /// Updates the hash with data. + /// Data buffer. + /// Length of buffer to hash. + public void Update(byte[] data, uint len) => + Step(ref _hashInt, _table, data, len, _useEcma, _useNative, _nativeContext); + + /// + /// Updates the hash with data. + /// Data buffer. + public void Update(byte[] data) => Update(data, (uint)data.Length); + + /// + /// Returns a byte array of the hash value. + public byte[] Final() + { + ulong crc = _hashInt ^ _finalSeed; + + if(!_useNative || + !_useEcma) + return BigEndianBitConverter.GetBytes(crc); + + crc64_final(_nativeContext, ref crc); + crc64_free(_nativeContext); + + return BigEndianBitConverter.GetBytes(crc); + } + + /// + /// Returns a hexadecimal representation of the hash value. + public string End() + { + ulong crc = _hashInt ^ _finalSeed; + + var crc64Output = new StringBuilder(); + + if(_useNative && _useEcma) { - _hashInt = seed; - _finalSeed = seed; - _useEcma = polynomial == CRC64_ECMA_POLY && seed == CRC64_ECMA_SEED; - - if(Native.IsSupported && _useEcma) - { - _nativeContext = crc64_init(); - _useNative = _nativeContext != IntPtr.Zero; - } - else - _table = GenerateTable(polynomial); - } - - /// - /// Updates the hash with data. - /// Data buffer. - /// Length of buffer to hash. - public void Update(byte[] data, uint len) => - Step(ref _hashInt, _table, data, len, _useEcma, _useNative, _nativeContext); - - /// - /// Updates the hash with data. - /// Data buffer. - public void Update(byte[] data) => Update(data, (uint)data.Length); - - /// - /// Returns a byte array of the hash value. - public byte[] Final() - { - ulong crc = _hashInt ^ _finalSeed; - - if(!_useNative || - !_useEcma) - return BigEndianBitConverter.GetBytes(crc); - crc64_final(_nativeContext, ref crc); crc64_free(_nativeContext); - - return BigEndianBitConverter.GetBytes(crc); } - /// - /// Returns a hexadecimal representation of the hash value. - public string End() - { - ulong crc = _hashInt ^ _finalSeed; + for(int i = 0; i < BigEndianBitConverter.GetBytes(crc).Length; i++) + crc64Output.Append(BigEndianBitConverter.GetBytes(crc)[i].ToString("x2")); - var crc64Output = new StringBuilder(); - - if(_useNative && _useEcma) - { - crc64_final(_nativeContext, ref crc); - crc64_free(_nativeContext); - } - - for(int i = 0; i < BigEndianBitConverter.GetBytes(crc).Length; i++) - crc64Output.Append(BigEndianBitConverter.GetBytes(crc)[i].ToString("x2")); - - return crc64Output.ToString(); - } - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern IntPtr crc64_init(); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int crc64_update(IntPtr ctx, byte[] data, uint len); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int crc64_final(IntPtr ctx, ref ulong crc); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern void crc64_free(IntPtr ctx); - - static ulong[][] GenerateTable(ulong polynomial) - { - ulong[][] table = new ulong[8][]; - - for(int i = 0; i < 8; i++) - table[i] = new ulong[256]; - - for(int i = 0; i < 256; i++) - { - ulong entry = (ulong)i; - - for(int j = 0; j < 8; j++) - if((entry & 1) == 1) - entry = (entry >> 1) ^ polynomial; - else - entry >>= 1; - - table[0][i] = entry; - } - - for(int slice = 1; slice < 4; slice++) - for(int i = 0; i < 256; i++) - table[slice][i] = (table[slice - 1][i] >> 8) ^ table[0][table[slice - 1][i] & 0xFF]; - - return table; - } - - static void Step(ref ulong previousCrc, ulong[][] table, byte[] data, uint len, bool useEcma, bool useNative, - IntPtr nativeContext) - { - if(useNative && useEcma) - { - crc64_update(nativeContext, data, len); - - return; - } - - int dataOff = 0; - - if(useEcma && - Pclmulqdq.IsSupported && - Sse41.IsSupported && - Ssse3.IsSupported && - Sse2.IsSupported) - { - // Only works in blocks of 32 bytes - uint blocks = len / 32; - - if(blocks > 0) - { - previousCrc = ~Clmul.Step(~previousCrc, data, blocks * 32); - - dataOff = (int)(blocks * 32); - len -= blocks * 32; - } - - if(len == 0) - return; - } - - // Unroll according to Intel slicing by uint8_t - // http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf - // http://sourceforge.net/projects/slicing-by-8/ - - ulong crc = previousCrc; - - if(len > 4) - { - long limit = dataOff + (len & ~(uint)3); - len &= 3; - - while(dataOff < limit) - { - uint tmp = (uint)(crc ^ BitConverter.ToUInt32(data, dataOff)); - dataOff += 4; - - crc = table[3][tmp & 0xFF] ^ table[2][(tmp >> 8) & 0xFF] ^ (crc >> 32) ^ - table[1][(tmp >> 16) & 0xFF] ^ table[0][tmp >> 24]; - } - } - - while(len-- != 0) - crc = table[0][data[dataOff++] ^ (crc & 0xFF)] ^ (crc >> 8); - - previousCrc = crc; - } - - /// Gets the hash of a file - /// File path. - public static byte[] File(string filename) - { - File(filename, out byte[] localHash); - - return localHash; - } - - /// Gets the hash of a file in hexadecimal and as a byte array. - /// File path. - /// Byte array of the hash value. - public static string File(string filename, out byte[] hash) => - File(filename, out hash, CRC64_ECMA_POLY, CRC64_ECMA_SEED); - - /// Gets the hash of a file in hexadecimal and as a byte array. - /// File path. - /// Byte array of the hash value. - /// CRC polynomial - /// CRC seed - public static string File(string filename, out byte[] hash, ulong polynomial, ulong seed) - { - bool useEcma = polynomial == CRC64_ECMA_POLY && seed == CRC64_ECMA_SEED; - bool useNative = Native.IsSupported; - IntPtr nativeContext = IntPtr.Zero; - - if(useNative && useEcma) - { - nativeContext = crc64_init(); - useNative = nativeContext != IntPtr.Zero; - } - - var fileStream = new FileStream(filename, FileMode.Open); - - ulong localHashInt = seed; - - ulong[][] localTable = GenerateTable(polynomial); - - byte[] buffer = new byte[65536]; - int read = fileStream.Read(buffer, 0, 65536); - - while(read > 0) - { - Step(ref localHashInt, localTable, buffer, (uint)read, useEcma, useNative, nativeContext); - - read = fileStream.Read(buffer, 0, 65536); - } - - localHashInt ^= seed; - - if(useNative && useEcma) - { - crc64_final(nativeContext, ref localHashInt); - crc64_free(nativeContext); - } - - hash = BigEndianBitConverter.GetBytes(localHashInt); - - var crc64Output = new StringBuilder(); - - foreach(byte h in hash) - crc64Output.Append(h.ToString("x2")); - - fileStream.Close(); - - return crc64Output.ToString(); - } - - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Length of the data buffer to hash. - /// Byte array of the hash value. - public static string Data(byte[] data, uint len, out byte[] hash) => - Data(data, len, out hash, CRC64_ECMA_POLY, CRC64_ECMA_SEED); - - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Length of the data buffer to hash. - /// Byte array of the hash value. - /// CRC polynomial - /// CRC seed - public static string Data(byte[] data, uint len, out byte[] hash, ulong polynomial, ulong seed) - { - bool useEcma = polynomial == CRC64_ECMA_POLY && seed == CRC64_ECMA_SEED; - bool useNative = Native.IsSupported; - IntPtr nativeContext = IntPtr.Zero; - - if(useNative && useEcma) - { - nativeContext = crc64_init(); - useNative = nativeContext != IntPtr.Zero; - } - - ulong localHashInt = seed; - - ulong[][] localTable = GenerateTable(polynomial); - - Step(ref localHashInt, localTable, data, len, useEcma, useNative, nativeContext); - - localHashInt ^= seed; - - if(useNative && useEcma) - { - crc64_final(nativeContext, ref localHashInt); - crc64_free(nativeContext); - } - - hash = BigEndianBitConverter.GetBytes(localHashInt); - - var crc64Output = new StringBuilder(); - - foreach(byte h in hash) - crc64Output.Append(h.ToString("x2")); - - return crc64Output.ToString(); - } - - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Byte array of the hash value. - public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); + return crc64Output.ToString(); } + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern IntPtr crc64_init(); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int crc64_update(IntPtr ctx, byte[] data, uint len); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int crc64_final(IntPtr ctx, ref ulong crc); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern void crc64_free(IntPtr ctx); + + static ulong[][] GenerateTable(ulong polynomial) + { + ulong[][] table = new ulong[8][]; + + for(int i = 0; i < 8; i++) + table[i] = new ulong[256]; + + for(int i = 0; i < 256; i++) + { + ulong entry = (ulong)i; + + for(int j = 0; j < 8; j++) + if((entry & 1) == 1) + entry = (entry >> 1) ^ polynomial; + else + entry >>= 1; + + table[0][i] = entry; + } + + for(int slice = 1; slice < 4; slice++) + for(int i = 0; i < 256; i++) + table[slice][i] = (table[slice - 1][i] >> 8) ^ table[0][table[slice - 1][i] & 0xFF]; + + return table; + } + + static void Step(ref ulong previousCrc, ulong[][] table, byte[] data, uint len, bool useEcma, bool useNative, + IntPtr nativeContext) + { + if(useNative && useEcma) + { + crc64_update(nativeContext, data, len); + + return; + } + + int dataOff = 0; + + if(useEcma && + Pclmulqdq.IsSupported && + Sse41.IsSupported && + Ssse3.IsSupported && + Sse2.IsSupported) + { + // Only works in blocks of 32 bytes + uint blocks = len / 32; + + if(blocks > 0) + { + previousCrc = ~Clmul.Step(~previousCrc, data, blocks * 32); + + dataOff = (int)(blocks * 32); + len -= blocks * 32; + } + + if(len == 0) + return; + } + + // Unroll according to Intel slicing by uint8_t + // http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf + // http://sourceforge.net/projects/slicing-by-8/ + + ulong crc = previousCrc; + + if(len > 4) + { + long limit = dataOff + (len & ~(uint)3); + len &= 3; + + while(dataOff < limit) + { + uint tmp = (uint)(crc ^ BitConverter.ToUInt32(data, dataOff)); + dataOff += 4; + + crc = table[3][tmp & 0xFF] ^ table[2][(tmp >> 8) & 0xFF] ^ (crc >> 32) ^ table[1][(tmp >> 16) & 0xFF] ^ + table[0][tmp >> 24]; + } + } + + while(len-- != 0) + crc = table[0][data[dataOff++] ^ (crc & 0xFF)] ^ (crc >> 8); + + previousCrc = crc; + } + + /// Gets the hash of a file + /// File path. + public static byte[] File(string filename) + { + File(filename, out byte[] localHash); + + return localHash; + } + + /// Gets the hash of a file in hexadecimal and as a byte array. + /// File path. + /// Byte array of the hash value. + public static string File(string filename, out byte[] hash) => + File(filename, out hash, CRC64_ECMA_POLY, CRC64_ECMA_SEED); + + /// Gets the hash of a file in hexadecimal and as a byte array. + /// File path. + /// Byte array of the hash value. + /// CRC polynomial + /// CRC seed + public static string File(string filename, out byte[] hash, ulong polynomial, ulong seed) + { + bool useEcma = polynomial == CRC64_ECMA_POLY && seed == CRC64_ECMA_SEED; + bool useNative = Native.IsSupported; + IntPtr nativeContext = IntPtr.Zero; + + if(useNative && useEcma) + { + nativeContext = crc64_init(); + useNative = nativeContext != IntPtr.Zero; + } + + var fileStream = new FileStream(filename, FileMode.Open); + + ulong localHashInt = seed; + + ulong[][] localTable = GenerateTable(polynomial); + + byte[] buffer = new byte[65536]; + int read = fileStream.Read(buffer, 0, 65536); + + while(read > 0) + { + Step(ref localHashInt, localTable, buffer, (uint)read, useEcma, useNative, nativeContext); + + read = fileStream.Read(buffer, 0, 65536); + } + + localHashInt ^= seed; + + if(useNative && useEcma) + { + crc64_final(nativeContext, ref localHashInt); + crc64_free(nativeContext); + } + + hash = BigEndianBitConverter.GetBytes(localHashInt); + + var crc64Output = new StringBuilder(); + + foreach(byte h in hash) + crc64Output.Append(h.ToString("x2")); + + fileStream.Close(); + + return crc64Output.ToString(); + } + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Length of the data buffer to hash. + /// Byte array of the hash value. + public static string Data(byte[] data, uint len, out byte[] hash) => + Data(data, len, out hash, CRC64_ECMA_POLY, CRC64_ECMA_SEED); + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Length of the data buffer to hash. + /// Byte array of the hash value. + /// CRC polynomial + /// CRC seed + public static string Data(byte[] data, uint len, out byte[] hash, ulong polynomial, ulong seed) + { + bool useEcma = polynomial == CRC64_ECMA_POLY && seed == CRC64_ECMA_SEED; + bool useNative = Native.IsSupported; + IntPtr nativeContext = IntPtr.Zero; + + if(useNative && useEcma) + { + nativeContext = crc64_init(); + useNative = nativeContext != IntPtr.Zero; + } + + ulong localHashInt = seed; + + ulong[][] localTable = GenerateTable(polynomial); + + Step(ref localHashInt, localTable, data, len, useEcma, useNative, nativeContext); + + localHashInt ^= seed; + + if(useNative && useEcma) + { + crc64_final(nativeContext, ref localHashInt); + crc64_free(nativeContext); + } + + hash = BigEndianBitConverter.GetBytes(localHashInt); + + var crc64Output = new StringBuilder(); + + foreach(byte h in hash) + crc64Output.Append(h.ToString("x2")); + + return crc64Output.ToString(); + } + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Byte array of the hash value. + public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); } \ No newline at end of file diff --git a/Aaru6.Checksums/FletcherContext.cs b/Aaru6.Checksums/FletcherContext.cs index ed2a15a..242349f 100644 --- a/Aaru6.Checksums/FletcherContext.cs +++ b/Aaru6.Checksums/FletcherContext.cs @@ -39,677 +39,676 @@ using System.Text; using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; -namespace Aaru6.Checksums +namespace Aaru6.Checksums; + +/// Implements the Fletcher-32 algorithm +public sealed class Fletcher32Context : IChecksum { - /// Implements the Fletcher-32 algorithm - public sealed class Fletcher32Context : IChecksum + const ushort FLETCHER_MODULE = 0xFFFF; + const uint NMAX = 5552; + readonly IntPtr _nativeContext; + readonly bool _useNative; + ushort _sum1, _sum2; + + /// Initializes the Fletcher-32 sums + public Fletcher32Context() { - const ushort FLETCHER_MODULE = 0xFFFF; - const uint NMAX = 5552; - readonly IntPtr _nativeContext; - ushort _sum1, _sum2; - readonly bool _useNative; + _sum1 = 0xFFFF; + _sum2 = 0xFFFF; - /// Initializes the Fletcher-32 sums - public Fletcher32Context() - { - _sum1 = 0xFFFF; - _sum2 = 0xFFFF; + if(!Native.IsSupported) + return; - if(!Native.IsSupported) - return; - - _nativeContext = fletcher32_init(); - _useNative = _nativeContext != IntPtr.Zero; - } - - /// - /// Updates the hash with data. - /// Data buffer. - /// Length of buffer to hash. - public void Update(byte[] data, uint len) => Step(ref _sum1, ref _sum2, data, len, _useNative, _nativeContext); - - /// - /// Updates the hash with data. - /// Data buffer. - public void Update(byte[] data) => Update(data, (uint)data.Length); - - /// - /// Returns a byte array of the hash value. - public byte[] Final() - { - uint finalSum = (uint)((_sum2 << 16) | _sum1); - - if(!_useNative) - return BigEndianBitConverter.GetBytes(finalSum); - - fletcher32_final(_nativeContext, ref finalSum); - fletcher32_free(_nativeContext); - - return BigEndianBitConverter.GetBytes(finalSum); - } - - /// - /// Returns a hexadecimal representation of the hash value. - public string End() - { - uint finalSum = (uint)((_sum2 << 16) | _sum1); - - if(_useNative) - { - fletcher32_final(_nativeContext, ref finalSum); - fletcher32_free(_nativeContext); - } - - var fletcherOutput = new StringBuilder(); - - for(int i = 0; i < BigEndianBitConverter.GetBytes(finalSum).Length; i++) - fletcherOutput.Append(BigEndianBitConverter.GetBytes(finalSum)[i].ToString("x2")); - - return fletcherOutput.ToString(); - } - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern IntPtr fletcher32_init(); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int fletcher32_update(IntPtr ctx, byte[] data, uint len); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int fletcher32_final(IntPtr ctx, ref uint crc); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern void fletcher32_free(IntPtr ctx); - - static void Step(ref ushort previousSum1, ref ushort previousSum2, byte[] data, uint len, bool useNative, - IntPtr nativeContext) - { - if(useNative) - { - fletcher32_update(nativeContext, data, len); - - return; - } - - uint sum1 = previousSum1; - uint sum2 = previousSum2; - uint n; - int dataOff = 0; - - /* in case user likes doing a byte at a time, keep it fast */ - if(len == 1) - { - sum1 += data[dataOff]; - - if(sum1 >= FLETCHER_MODULE) - sum1 -= FLETCHER_MODULE; - - sum2 += sum1; - - if(sum2 >= FLETCHER_MODULE) - sum2 -= FLETCHER_MODULE; - - previousSum1 = (ushort)(sum1 & 0xFFFF); - previousSum2 = (ushort)(sum2 & 0xFFFF); - - return; - } - - /* in case short lengths are provided, keep it somewhat fast */ - if(len < 16) - { - while(len-- > 0) - { - sum1 += data[dataOff++]; - sum2 += sum1; - } - - if(sum1 >= FLETCHER_MODULE) - sum1 -= FLETCHER_MODULE; - - sum2 %= FLETCHER_MODULE; /* only added so many FLETCHER_MODULE's */ - previousSum1 = (ushort)(sum1 & 0xFFFF); - previousSum2 = (ushort)(sum2 & 0xFFFF); - - return; - } - - /* do length NMAX blocks -- requires just one modulo operation */ - while(len >= NMAX) - { - len -= NMAX; - n = NMAX / 16; /* NMAX is divisible by 16 */ - - do - { - sum1 += data[dataOff]; - sum2 += sum1; - sum1 += data[dataOff + 1]; - sum2 += sum1; - sum1 += data[dataOff + 2]; - sum2 += sum1; - sum1 += data[dataOff + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 4]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4 + 2 + 1]; - sum2 += sum1; - - /* 16 sums unrolled */ - dataOff += 16; - } while(--n != 0); - - sum1 %= FLETCHER_MODULE; - sum2 %= FLETCHER_MODULE; - } - - /* do remaining bytes (less than NMAX, still just one modulo) */ - if(len != 0) - { - /* avoid modulos if none remaining */ - while(len >= 16) - { - len -= 16; - sum1 += data[dataOff]; - sum2 += sum1; - sum1 += data[dataOff + 1]; - sum2 += sum1; - sum1 += data[dataOff + 2]; - sum2 += sum1; - sum1 += data[dataOff + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 4]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 4 + 2 + 1]; - sum2 += sum1; - - dataOff += 16; - } - - while(len-- != 0) - { - sum1 += data[dataOff++]; - sum2 += sum1; - } - - sum1 %= FLETCHER_MODULE; - sum2 %= FLETCHER_MODULE; - } - - previousSum1 = (ushort)(sum1 & 0xFFFF); - previousSum2 = (ushort)(sum2 & 0xFFFF); - } - - /// Gets the hash of a file - /// File path. - public static byte[] File(string filename) - { - File(filename, out byte[] hash); - - return hash; - } - - /// Gets the hash of a file in hexadecimal and as a byte array. - /// File path. - /// Byte array of the hash value. - public static string File(string filename, out byte[] hash) - { - bool useNative = Native.IsSupported; - IntPtr nativeContext = IntPtr.Zero; - - if(useNative) - { - nativeContext = fletcher32_init(); - - if(nativeContext == IntPtr.Zero) - useNative = false; - } - - var fileStream = new FileStream(filename, FileMode.Open); - - ushort localSum1 = 0xFFFF; - ushort localSum2 = 0xFFFF; - - byte[] buffer = new byte[65536]; - int read = fileStream.Read(buffer, 0, 65536); - - while(read > 0) - { - Step(ref localSum1, ref localSum2, buffer, (uint)read, useNative, nativeContext); - - read = fileStream.Read(buffer, 0, 65536); - } - - uint finalSum = (uint)((localSum2 << 16) | localSum1); - - if(useNative) - { - fletcher32_final(nativeContext, ref finalSum); - fletcher32_free(nativeContext); - } - - hash = BigEndianBitConverter.GetBytes(finalSum); - - var fletcherOutput = new StringBuilder(); - - foreach(byte h in hash) - fletcherOutput.Append(h.ToString("x2")); - - fileStream.Close(); - - return fletcherOutput.ToString(); - } - - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Length of the data buffer to hash. - /// Byte array of the hash value. - public static string Data(byte[] data, uint len, out byte[] hash) - { - bool useNative = Native.IsSupported; - IntPtr nativeContext = IntPtr.Zero; - - if(useNative) - { - nativeContext = fletcher32_init(); - - if(nativeContext == IntPtr.Zero) - useNative = false; - } - - ushort localSum1 = 0xFFFF; - ushort localSum2 = 0xFFFF; - - Step(ref localSum1, ref localSum2, data, len, useNative, nativeContext); - - uint finalSum = (uint)((localSum2 << 16) | localSum1); - - if(useNative) - { - fletcher32_final(nativeContext, ref finalSum); - fletcher32_free(nativeContext); - } - - hash = BigEndianBitConverter.GetBytes(finalSum); - - var adlerOutput = new StringBuilder(); - - foreach(byte h in hash) - adlerOutput.Append(h.ToString("x2")); - - return adlerOutput.ToString(); - } - - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Byte array of the hash value. - public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); + _nativeContext = fletcher32_init(); + _useNative = _nativeContext != IntPtr.Zero; } /// - /// Implements the Fletcher-16 algorithm - public sealed class Fletcher16Context : IChecksum + /// Updates the hash with data. + /// Data buffer. + /// Length of buffer to hash. + public void Update(byte[] data, uint len) => Step(ref _sum1, ref _sum2, data, len, _useNative, _nativeContext); + + /// + /// Updates the hash with data. + /// Data buffer. + public void Update(byte[] data) => Update(data, (uint)data.Length); + + /// + /// Returns a byte array of the hash value. + public byte[] Final() { - const byte FLETCHER_MODULE = 0xFF; - const byte NMAX = 22; - - readonly IntPtr _nativeContext; - readonly bool _useNative; - byte _sum1, _sum2; - - /// Initializes the Fletcher-16 sums - public Fletcher16Context() - { - _sum1 = 0xFF; - _sum2 = 0xFF; - - if(!Native.IsSupported) - return; - - _nativeContext = fletcher16_init(); - _useNative = _nativeContext != IntPtr.Zero; - } - - /// - /// Updates the hash with data. - /// Data buffer. - /// Length of buffer to hash. - public void Update(byte[] data, uint len) => Step(ref _sum1, ref _sum2, data, len, _useNative, _nativeContext); - - /// - /// Updates the hash with data. - /// Data buffer. - public void Update(byte[] data) => Update(data, (uint)data.Length); - - /// - /// Returns a byte array of the hash value. - public byte[] Final() - { - ushort finalSum = (ushort)((_sum2 << 8) | _sum1); - - if(!_useNative) - return BigEndianBitConverter.GetBytes(finalSum); - - fletcher16_final(_nativeContext, ref finalSum); - fletcher16_free(_nativeContext); + uint finalSum = (uint)((_sum2 << 16) | _sum1); + if(!_useNative) return BigEndianBitConverter.GetBytes(finalSum); + + fletcher32_final(_nativeContext, ref finalSum); + fletcher32_free(_nativeContext); + + return BigEndianBitConverter.GetBytes(finalSum); + } + + /// + /// Returns a hexadecimal representation of the hash value. + public string End() + { + uint finalSum = (uint)((_sum2 << 16) | _sum1); + + if(_useNative) + { + fletcher32_final(_nativeContext, ref finalSum); + fletcher32_free(_nativeContext); } - /// - /// Returns a hexadecimal representation of the hash value. - public string End() + var fletcherOutput = new StringBuilder(); + + for(int i = 0; i < BigEndianBitConverter.GetBytes(finalSum).Length; i++) + fletcherOutput.Append(BigEndianBitConverter.GetBytes(finalSum)[i].ToString("x2")); + + return fletcherOutput.ToString(); + } + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern IntPtr fletcher32_init(); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int fletcher32_update(IntPtr ctx, byte[] data, uint len); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int fletcher32_final(IntPtr ctx, ref uint crc); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern void fletcher32_free(IntPtr ctx); + + static void Step(ref ushort previousSum1, ref ushort previousSum2, byte[] data, uint len, bool useNative, + IntPtr nativeContext) + { + if(useNative) { - ushort finalSum = (ushort)((_sum2 << 8) | _sum1); + fletcher32_update(nativeContext, data, len); - if(_useNative) - { - fletcher16_final(_nativeContext, ref finalSum); - fletcher16_free(_nativeContext); - } - - var fletcherOutput = new StringBuilder(); - - for(int i = 0; i < BigEndianBitConverter.GetBytes(finalSum).Length; i++) - fletcherOutput.Append(BigEndianBitConverter.GetBytes(finalSum)[i].ToString("x2")); - - return fletcherOutput.ToString(); + return; } - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern IntPtr fletcher16_init(); + uint sum1 = previousSum1; + uint sum2 = previousSum2; + uint n; + int dataOff = 0; - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int fletcher16_update(IntPtr ctx, byte[] data, uint len); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int fletcher16_final(IntPtr ctx, ref ushort checksum); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern void fletcher16_free(IntPtr ctx); - - static void Step(ref byte previousSum1, ref byte previousSum2, byte[] data, uint len, bool useNative, - IntPtr nativeContext) + /* in case user likes doing a byte at a time, keep it fast */ + if(len == 1) { - if(useNative) - { - fletcher16_update(nativeContext, data, len); + sum1 += data[dataOff]; - return; + if(sum1 >= FLETCHER_MODULE) + sum1 -= FLETCHER_MODULE; + + sum2 += sum1; + + if(sum2 >= FLETCHER_MODULE) + sum2 -= FLETCHER_MODULE; + + previousSum1 = (ushort)(sum1 & 0xFFFF); + previousSum2 = (ushort)(sum2 & 0xFFFF); + + return; + } + + /* in case short lengths are provided, keep it somewhat fast */ + if(len < 16) + { + while(len-- > 0) + { + sum1 += data[dataOff++]; + sum2 += sum1; } - uint sum1 = previousSum1; - uint sum2 = previousSum2; - uint n; - int dataOff = 0; + if(sum1 >= FLETCHER_MODULE) + sum1 -= FLETCHER_MODULE; - /* in case user likes doing a byte at a time, keep it fast */ - if(len == 1) + sum2 %= FLETCHER_MODULE; /* only added so many FLETCHER_MODULE's */ + previousSum1 = (ushort)(sum1 & 0xFFFF); + previousSum2 = (ushort)(sum2 & 0xFFFF); + + return; + } + + /* do length NMAX blocks -- requires just one modulo operation */ + while(len >= NMAX) + { + len -= NMAX; + n = NMAX / 16; /* NMAX is divisible by 16 */ + + do { sum1 += data[dataOff]; - - if(sum1 >= FLETCHER_MODULE) - sum1 -= FLETCHER_MODULE; - + sum2 += sum1; + sum1 += data[dataOff + 1]; + sum2 += sum1; + sum1 += data[dataOff + 2]; + sum2 += sum1; + sum1 += data[dataOff + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 4]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4 + 2 + 1]; sum2 += sum1; - if(sum2 >= FLETCHER_MODULE) - sum2 -= FLETCHER_MODULE; + /* 16 sums unrolled */ + dataOff += 16; + } while(--n != 0); - previousSum1 = (byte)(sum1 & 0xFF); - previousSum2 = (byte)(sum2 & 0xFF); + sum1 %= FLETCHER_MODULE; + sum2 %= FLETCHER_MODULE; + } - return; - } - - /* in case short lengths are provided, keep it somewhat fast */ - if(len < 11) + /* do remaining bytes (less than NMAX, still just one modulo) */ + if(len != 0) + { + /* avoid modulos if none remaining */ + while(len >= 16) { - while(len-- > 0) - { - sum1 += data[dataOff++]; - sum2 += sum1; - } + len -= 16; + sum1 += data[dataOff]; + sum2 += sum1; + sum1 += data[dataOff + 1]; + sum2 += sum1; + sum1 += data[dataOff + 2]; + sum2 += sum1; + sum1 += data[dataOff + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 4]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 4 + 2 + 1]; + sum2 += sum1; - if(sum1 >= FLETCHER_MODULE) - sum1 -= FLETCHER_MODULE; - - sum2 %= FLETCHER_MODULE; /* only added so many FLETCHER_MODULE's */ - previousSum1 = (byte)(sum1 & 0xFF); - previousSum2 = (byte)(sum2 & 0xFF); - - return; + dataOff += 16; } - /* do length NMAX blocks -- requires just one modulo operation */ - while(len >= NMAX) + while(len-- != 0) { - len -= NMAX; - n = NMAX / 11; /* NMAX is divisible by 16 */ - - do - { - sum1 += data[dataOff]; - sum2 += sum1; - sum1 += data[dataOff + 1]; - sum2 += sum1; - sum1 += data[dataOff + 2]; - sum2 += sum1; - sum1 += data[dataOff + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 4]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 2]; - sum2 += sum1; - - /* 11 sums unrolled */ - dataOff += 11; - } while(--n != 0); - - sum1 %= FLETCHER_MODULE; - sum2 %= FLETCHER_MODULE; + sum1 += data[dataOff++]; + sum2 += sum1; } - /* do remaining bytes (less than NMAX, still just one modulo) */ - if(len != 0) - { - /* avoid modulos if none remaining */ - while(len >= 11) - { - len -= 11; - sum1 += data[dataOff]; - sum2 += sum1; - sum1 += data[dataOff + 1]; - sum2 += sum1; - sum1 += data[dataOff + 2]; - sum2 += sum1; - sum1 += data[dataOff + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 4]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 2]; - sum2 += sum1; - sum1 += data[dataOff + 4 + 2 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 1]; - sum2 += sum1; - sum1 += data[dataOff + 8 + 2]; - sum2 += sum1; + sum1 %= FLETCHER_MODULE; + sum2 %= FLETCHER_MODULE; + } - dataOff += 11; - } + previousSum1 = (ushort)(sum1 & 0xFFFF); + previousSum2 = (ushort)(sum2 & 0xFFFF); + } - while(len-- != 0) - { - sum1 += data[dataOff++]; - sum2 += sum1; - } + /// Gets the hash of a file + /// File path. + public static byte[] File(string filename) + { + File(filename, out byte[] hash); - sum1 %= FLETCHER_MODULE; - sum2 %= FLETCHER_MODULE; - } + return hash; + } + + /// Gets the hash of a file in hexadecimal and as a byte array. + /// File path. + /// Byte array of the hash value. + public static string File(string filename, out byte[] hash) + { + bool useNative = Native.IsSupported; + IntPtr nativeContext = IntPtr.Zero; + + if(useNative) + { + nativeContext = fletcher32_init(); + + if(nativeContext == IntPtr.Zero) + useNative = false; + } + + var fileStream = new FileStream(filename, FileMode.Open); + + ushort localSum1 = 0xFFFF; + ushort localSum2 = 0xFFFF; + + byte[] buffer = new byte[65536]; + int read = fileStream.Read(buffer, 0, 65536); + + while(read > 0) + { + Step(ref localSum1, ref localSum2, buffer, (uint)read, useNative, nativeContext); + + read = fileStream.Read(buffer, 0, 65536); + } + + uint finalSum = (uint)((localSum2 << 16) | localSum1); + + if(useNative) + { + fletcher32_final(nativeContext, ref finalSum); + fletcher32_free(nativeContext); + } + + hash = BigEndianBitConverter.GetBytes(finalSum); + + var fletcherOutput = new StringBuilder(); + + foreach(byte h in hash) + fletcherOutput.Append(h.ToString("x2")); + + fileStream.Close(); + + return fletcherOutput.ToString(); + } + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Length of the data buffer to hash. + /// Byte array of the hash value. + public static string Data(byte[] data, uint len, out byte[] hash) + { + bool useNative = Native.IsSupported; + IntPtr nativeContext = IntPtr.Zero; + + if(useNative) + { + nativeContext = fletcher32_init(); + + if(nativeContext == IntPtr.Zero) + useNative = false; + } + + ushort localSum1 = 0xFFFF; + ushort localSum2 = 0xFFFF; + + Step(ref localSum1, ref localSum2, data, len, useNative, nativeContext); + + uint finalSum = (uint)((localSum2 << 16) | localSum1); + + if(useNative) + { + fletcher32_final(nativeContext, ref finalSum); + fletcher32_free(nativeContext); + } + + hash = BigEndianBitConverter.GetBytes(finalSum); + + var adlerOutput = new StringBuilder(); + + foreach(byte h in hash) + adlerOutput.Append(h.ToString("x2")); + + return adlerOutput.ToString(); + } + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Byte array of the hash value. + public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); +} + +/// +/// Implements the Fletcher-16 algorithm +public sealed class Fletcher16Context : IChecksum +{ + const byte FLETCHER_MODULE = 0xFF; + const byte NMAX = 22; + + readonly IntPtr _nativeContext; + readonly bool _useNative; + byte _sum1, _sum2; + + /// Initializes the Fletcher-16 sums + public Fletcher16Context() + { + _sum1 = 0xFF; + _sum2 = 0xFF; + + if(!Native.IsSupported) + return; + + _nativeContext = fletcher16_init(); + _useNative = _nativeContext != IntPtr.Zero; + } + + /// + /// Updates the hash with data. + /// Data buffer. + /// Length of buffer to hash. + public void Update(byte[] data, uint len) => Step(ref _sum1, ref _sum2, data, len, _useNative, _nativeContext); + + /// + /// Updates the hash with data. + /// Data buffer. + public void Update(byte[] data) => Update(data, (uint)data.Length); + + /// + /// Returns a byte array of the hash value. + public byte[] Final() + { + ushort finalSum = (ushort)((_sum2 << 8) | _sum1); + + if(!_useNative) + return BigEndianBitConverter.GetBytes(finalSum); + + fletcher16_final(_nativeContext, ref finalSum); + fletcher16_free(_nativeContext); + + return BigEndianBitConverter.GetBytes(finalSum); + } + + /// + /// Returns a hexadecimal representation of the hash value. + public string End() + { + ushort finalSum = (ushort)((_sum2 << 8) | _sum1); + + if(_useNative) + { + fletcher16_final(_nativeContext, ref finalSum); + fletcher16_free(_nativeContext); + } + + var fletcherOutput = new StringBuilder(); + + for(int i = 0; i < BigEndianBitConverter.GetBytes(finalSum).Length; i++) + fletcherOutput.Append(BigEndianBitConverter.GetBytes(finalSum)[i].ToString("x2")); + + return fletcherOutput.ToString(); + } + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern IntPtr fletcher16_init(); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int fletcher16_update(IntPtr ctx, byte[] data, uint len); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int fletcher16_final(IntPtr ctx, ref ushort checksum); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern void fletcher16_free(IntPtr ctx); + + static void Step(ref byte previousSum1, ref byte previousSum2, byte[] data, uint len, bool useNative, + IntPtr nativeContext) + { + if(useNative) + { + fletcher16_update(nativeContext, data, len); + + return; + } + + uint sum1 = previousSum1; + uint sum2 = previousSum2; + uint n; + int dataOff = 0; + + /* in case user likes doing a byte at a time, keep it fast */ + if(len == 1) + { + sum1 += data[dataOff]; + + if(sum1 >= FLETCHER_MODULE) + sum1 -= FLETCHER_MODULE; + + sum2 += sum1; + + if(sum2 >= FLETCHER_MODULE) + sum2 -= FLETCHER_MODULE; previousSum1 = (byte)(sum1 & 0xFF); previousSum2 = (byte)(sum2 & 0xFF); + + return; } - /// Gets the hash of a file - /// File path. - public static byte[] File(string filename) + /* in case short lengths are provided, keep it somewhat fast */ + if(len < 11) { - File(filename, out byte[] hash); + while(len-- > 0) + { + sum1 += data[dataOff++]; + sum2 += sum1; + } - return hash; + if(sum1 >= FLETCHER_MODULE) + sum1 -= FLETCHER_MODULE; + + sum2 %= FLETCHER_MODULE; /* only added so many FLETCHER_MODULE's */ + previousSum1 = (byte)(sum1 & 0xFF); + previousSum2 = (byte)(sum2 & 0xFF); + + return; } - /// Gets the hash of a file in hexadecimal and as a byte array. - /// File path. - /// Byte array of the hash value. - public static string File(string filename, out byte[] hash) + /* do length NMAX blocks -- requires just one modulo operation */ + while(len >= NMAX) { - bool useNative = Native.IsSupported; - IntPtr nativeContext = IntPtr.Zero; + len -= NMAX; + n = NMAX / 11; /* NMAX is divisible by 16 */ - if(useNative) + do { - nativeContext = fletcher16_init(); + sum1 += data[dataOff]; + sum2 += sum1; + sum1 += data[dataOff + 1]; + sum2 += sum1; + sum1 += data[dataOff + 2]; + sum2 += sum1; + sum1 += data[dataOff + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 4]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 2]; + sum2 += sum1; - if(nativeContext == IntPtr.Zero) - useNative = false; - } + /* 11 sums unrolled */ + dataOff += 11; + } while(--n != 0); - var fileStream = new FileStream(filename, FileMode.Open); - - byte localSum1 = 0xFF; - byte localSum2 = 0xFF; - - byte[] buffer = new byte[65536]; - int read = fileStream.Read(buffer, 0, 65536); - - while(read > 0) - { - Step(ref localSum1, ref localSum2, buffer, (uint)read, useNative, nativeContext); - - read = fileStream.Read(buffer, 0, 65536); - } - - ushort finalSum = (ushort)((localSum2 << 8) | localSum1); - - if(useNative) - { - fletcher16_final(nativeContext, ref finalSum); - fletcher16_free(nativeContext); - } - - hash = BigEndianBitConverter.GetBytes(finalSum); - - var fletcherOutput = new StringBuilder(); - - foreach(byte h in hash) - fletcherOutput.Append(h.ToString("x2")); - - fileStream.Close(); - - return fletcherOutput.ToString(); + sum1 %= FLETCHER_MODULE; + sum2 %= FLETCHER_MODULE; } - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Length of the data buffer to hash. - /// Byte array of the hash value. - public static string Data(byte[] data, uint len, out byte[] hash) + /* do remaining bytes (less than NMAX, still just one modulo) */ + if(len != 0) { - bool useNative = Native.IsSupported; - IntPtr nativeContext = IntPtr.Zero; - - if(useNative) + /* avoid modulos if none remaining */ + while(len >= 11) { - nativeContext = fletcher16_init(); + len -= 11; + sum1 += data[dataOff]; + sum2 += sum1; + sum1 += data[dataOff + 1]; + sum2 += sum1; + sum1 += data[dataOff + 2]; + sum2 += sum1; + sum1 += data[dataOff + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 4]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 2]; + sum2 += sum1; + sum1 += data[dataOff + 4 + 2 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 1]; + sum2 += sum1; + sum1 += data[dataOff + 8 + 2]; + sum2 += sum1; - if(nativeContext == IntPtr.Zero) - useNative = false; + dataOff += 11; } - byte localSum1 = 0xFF; - byte localSum2 = 0xFF; - - Step(ref localSum1, ref localSum2, data, len, useNative, nativeContext); - - ushort finalSum = (ushort)((localSum2 << 8) | localSum1); - - if(useNative) + while(len-- != 0) { - fletcher16_final(nativeContext, ref finalSum); - fletcher16_free(nativeContext); + sum1 += data[dataOff++]; + sum2 += sum1; } - hash = BigEndianBitConverter.GetBytes(finalSum); - - var adlerOutput = new StringBuilder(); - - foreach(byte h in hash) - adlerOutput.Append(h.ToString("x2")); - - return adlerOutput.ToString(); + sum1 %= FLETCHER_MODULE; + sum2 %= FLETCHER_MODULE; } - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Byte array of the hash value. - public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); + previousSum1 = (byte)(sum1 & 0xFF); + previousSum2 = (byte)(sum2 & 0xFF); } + + /// Gets the hash of a file + /// File path. + public static byte[] File(string filename) + { + File(filename, out byte[] hash); + + return hash; + } + + /// Gets the hash of a file in hexadecimal and as a byte array. + /// File path. + /// Byte array of the hash value. + public static string File(string filename, out byte[] hash) + { + bool useNative = Native.IsSupported; + IntPtr nativeContext = IntPtr.Zero; + + if(useNative) + { + nativeContext = fletcher16_init(); + + if(nativeContext == IntPtr.Zero) + useNative = false; + } + + var fileStream = new FileStream(filename, FileMode.Open); + + byte localSum1 = 0xFF; + byte localSum2 = 0xFF; + + byte[] buffer = new byte[65536]; + int read = fileStream.Read(buffer, 0, 65536); + + while(read > 0) + { + Step(ref localSum1, ref localSum2, buffer, (uint)read, useNative, nativeContext); + + read = fileStream.Read(buffer, 0, 65536); + } + + ushort finalSum = (ushort)((localSum2 << 8) | localSum1); + + if(useNative) + { + fletcher16_final(nativeContext, ref finalSum); + fletcher16_free(nativeContext); + } + + hash = BigEndianBitConverter.GetBytes(finalSum); + + var fletcherOutput = new StringBuilder(); + + foreach(byte h in hash) + fletcherOutput.Append(h.ToString("x2")); + + fileStream.Close(); + + return fletcherOutput.ToString(); + } + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Length of the data buffer to hash. + /// Byte array of the hash value. + public static string Data(byte[] data, uint len, out byte[] hash) + { + bool useNative = Native.IsSupported; + IntPtr nativeContext = IntPtr.Zero; + + if(useNative) + { + nativeContext = fletcher16_init(); + + if(nativeContext == IntPtr.Zero) + useNative = false; + } + + byte localSum1 = 0xFF; + byte localSum2 = 0xFF; + + Step(ref localSum1, ref localSum2, data, len, useNative, nativeContext); + + ushort finalSum = (ushort)((localSum2 << 8) | localSum1); + + if(useNative) + { + fletcher16_final(nativeContext, ref finalSum); + fletcher16_free(nativeContext); + } + + hash = BigEndianBitConverter.GetBytes(finalSum); + + var adlerOutput = new StringBuilder(); + + foreach(byte h in hash) + adlerOutput.Append(h.ToString("x2")); + + return adlerOutput.ToString(); + } + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Byte array of the hash value. + public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); } \ No newline at end of file diff --git a/Aaru6.Checksums/Native.cs b/Aaru6.Checksums/Native.cs index d95d28f..5c10b8d 100644 --- a/Aaru6.Checksums/Native.cs +++ b/Aaru6.Checksums/Native.cs @@ -1,46 +1,45 @@ using System.Runtime.InteropServices; -namespace Aaru6.Checksums +namespace Aaru6.Checksums; + +public static class Native { - public static class Native + static bool _checked; + static bool _supported; + + public static bool ForceManaged { get; set; } + + public static bool IsSupported { - static bool _checked; - static bool _supported; - - public static bool ForceManaged { get; set; } - - public static bool IsSupported + get { - get - { - if(ForceManaged) - return false; - - if(_checked) - return _supported; - - ulong version; - _checked = true; - - try - { - version = get_acn_version(); - } - catch - { - _supported = false; - - return false; - } - - // TODO: Check version compatibility - _supported = version >= 0x06000000; + if(ForceManaged) + return false; + if(_checked) return _supported; - } - } - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern ulong get_acn_version(); + ulong version; + _checked = true; + + try + { + version = get_acn_version(); + } + catch + { + _supported = false; + + return false; + } + + // TODO: Check version compatibility + _supported = version >= 0x06000000; + + return _supported; + } } + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern ulong get_acn_version(); } \ No newline at end of file diff --git a/Aaru6.Checksums/SpamSumContext.cs b/Aaru6.Checksums/SpamSumContext.cs index 12a2f5f..0df63a0 100644 --- a/Aaru6.Checksums/SpamSumContext.cs +++ b/Aaru6.Checksums/SpamSumContext.cs @@ -44,280 +44,331 @@ using System.Runtime.CompilerServices; using System.Text; using Aaru.CommonTypes.Interfaces; -namespace Aaru6.Checksums +namespace Aaru6.Checksums; + +/// +/// Implements the SpamSum fuzzy hashing algorithm. +public sealed class SpamSumContext : IChecksum { - /// - /// Implements the SpamSum fuzzy hashing algorithm. - public sealed class SpamSumContext : IChecksum + const uint ROLLING_WINDOW = 7; + const uint MIN_BLOCKSIZE = 3; + const uint HASH_PRIME = 0x01000193; + const uint HASH_INIT = 0x28021967; + const uint NUM_BLOCKHASHES = 31; + const uint SPAMSUM_LENGTH = 64; + const uint FUZZY_MAX_RESULT = (2 * SPAMSUM_LENGTH) + 20; + + //"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + readonly byte[] _b64 = { - const uint ROLLING_WINDOW = 7; - const uint MIN_BLOCKSIZE = 3; - const uint HASH_PRIME = 0x01000193; - const uint HASH_INIT = 0x28021967; - const uint NUM_BLOCKHASHES = 31; - const uint SPAMSUM_LENGTH = 64; - const uint FUZZY_MAX_RESULT = (2 * SPAMSUM_LENGTH) + 20; + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, + 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, + 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, + 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F + }; - //"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - readonly byte[] _b64 = + FuzzyState _self; + + /// Initializes the SpamSum structures + public SpamSumContext() + { + _self = new FuzzyState { - 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, - 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, - 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, - 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F + Bh = new BlockhashContext[NUM_BLOCKHASHES] }; - FuzzyState _self; + for(int i = 0; i < NUM_BLOCKHASHES; i++) + _self.Bh[i].Digest = new byte[SPAMSUM_LENGTH]; - /// Initializes the SpamSum structures - public SpamSumContext() + _self.Bhstart = 0; + _self.Bhend = 1; + _self.Bh[0].H = HASH_INIT; + _self.Bh[0].Halfh = HASH_INIT; + _self.Bh[0].Digest[0] = 0; + _self.Bh[0].Halfdigest = 0; + _self.Bh[0].Dlen = 0; + _self.TotalSize = 0; + roll_init(); + } + + /// + /// Updates the hash with data. + /// Data buffer. + /// Length of buffer to hash. + public void Update(byte[] data, uint len) + { + _self.TotalSize += len; + + for(int i = 0; i < len; i++) + fuzzy_engine_step(data[i]); + } + + /// + /// Updates the hash with data. + /// Data buffer. + public void Update(byte[] data) => Update(data, (uint)data.Length); + + /// + /// Returns a byte array of the hash value. + public byte[] Final() => throw new NotImplementedException("SpamSum does not have a binary representation."); + + /// + /// Returns a base64 representation of the hash value. + public string End() + { + FuzzyDigest(out byte[] result); + + return CToString(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void roll_init() => _self.Roll = new RollState + { + Window = new byte[ROLLING_WINDOW] + }; + + /* + * a rolling hash, based on the Adler checksum. By using a rolling hash + * we can perform auto resynchronisation after inserts/deletes + + * internally, h1 is the sum of the bytes in the window and h2 + * is the sum of the bytes times the index + + * h3 is a shift/xor based rolling hash, and is mostly needed to ensure that + * we can cope with large blocksize values + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void roll_hash(byte c) + { + _self.Roll.H2 -= _self.Roll.H1; + _self.Roll.H2 += ROLLING_WINDOW * c; + + _self.Roll.H1 += c; + _self.Roll.H1 -= _self.Roll.Window[_self.Roll.N % ROLLING_WINDOW]; + + _self.Roll.Window[_self.Roll.N % ROLLING_WINDOW] = c; + _self.Roll.N++; + + /* The original spamsum AND'ed this value with 0xFFFFFFFF which + * in theory should have no effect. This AND has been removed + * for performance (jk) */ + _self.Roll.H3 <<= 5; + _self.Roll.H3 ^= c; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + uint roll_sum() => _self.Roll.H1 + _self.Roll.H2 + _self.Roll.H3; + + /* A simple non-rolling hash, based on the FNV hash. */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static uint sum_hash(byte c, uint h) => (h * HASH_PRIME) ^ c; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static uint SSDEEP_BS(uint index) => MIN_BLOCKSIZE << (int)index; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void fuzzy_try_fork_blockhash() + { + if(_self.Bhend >= NUM_BLOCKHASHES) + return; + + if(_self.Bhend == 0) // assert + throw new Exception("Assertion failed"); + + uint obh = _self.Bhend - 1; + uint nbh = _self.Bhend; + _self.Bh[nbh].H = _self.Bh[obh].H; + _self.Bh[nbh].Halfh = _self.Bh[obh].Halfh; + _self.Bh[nbh].Digest[0] = 0; + _self.Bh[nbh].Halfdigest = 0; + _self.Bh[nbh].Dlen = 0; + ++_self.Bhend; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void fuzzy_try_reduce_blockhash() + { + if(_self.Bhstart >= _self.Bhend) + throw new Exception("Assertion failed"); + + if(_self.Bhend - _self.Bhstart < 2) + /* Need at least two working hashes. */ + return; + + if((ulong)SSDEEP_BS(_self.Bhstart) * SPAMSUM_LENGTH >= _self.TotalSize) + /* Initial blocksize estimate would select this or a smaller + * blocksize. */ + return; + + if(_self.Bh[_self.Bhstart + 1].Dlen < SPAMSUM_LENGTH / 2) + /* Estimate adjustment would select this blocksize. */ + return; + + /* At this point we are clearly no longer interested in the + * start_blocksize. Get rid of it. */ + ++_self.Bhstart; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void fuzzy_engine_step(byte c) + { + uint i; + /* At each character we update the rolling hash and the normal hashes. + * When the rolling hash hits a reset value then we emit a normal hash + * as a element of the signature and reset the normal hash. */ + roll_hash(c); + ulong h = roll_sum(); + + for(i = _self.Bhstart; i < _self.Bhend; ++i) { - _self = new FuzzyState + _self.Bh[i].H = sum_hash(c, _self.Bh[i].H); + _self.Bh[i].Halfh = sum_hash(c, _self.Bh[i].Halfh); + } + + for(i = _self.Bhstart; i < _self.Bhend; ++i) + { + /* With growing blocksize almost no runs fail the next test. */ + if(h % SSDEEP_BS(i) != SSDEEP_BS(i) - 1) + /* Once this condition is false for one bs, it is + * automatically false for all further bs. I.e. if + * h === -1 (mod 2*bs) then h === -1 (mod bs). */ + break; + + /* We have hit a reset point. We now emit hashes which are + * based on all characters in the piece of the message between + * the last reset point and this one */ + if(0 == _self.Bh[i].Dlen) + fuzzy_try_fork_blockhash(); + + _self.Bh[i].Digest[_self.Bh[i].Dlen] = _b64[_self.Bh[i].H % 64]; + _self.Bh[i].Halfdigest = _b64[_self.Bh[i].Halfh % 64]; + + if(_self.Bh[i].Dlen < SPAMSUM_LENGTH - 1) { - Bh = new BlockhashContext[NUM_BLOCKHASHES] - }; + /* We can have a problem with the tail overflowing. The + * easiest way to cope with this is to only reset the + * normal hash if we have room for more characters in + * our signature. This has the effect of combining the + * last few pieces of the message into a single piece + * */ + _self.Bh[i].Digest[++_self.Bh[i].Dlen] = 0; + _self.Bh[i].H = HASH_INIT; - for(int i = 0; i < NUM_BLOCKHASHES; i++) - _self.Bh[i].Digest = new byte[SPAMSUM_LENGTH]; + if(_self.Bh[i].Dlen >= SPAMSUM_LENGTH / 2) + continue; - _self.Bhstart = 0; - _self.Bhend = 1; - _self.Bh[0].H = HASH_INIT; - _self.Bh[0].Halfh = HASH_INIT; - _self.Bh[0].Digest[0] = 0; - _self.Bh[0].Halfdigest = 0; - _self.Bh[0].Dlen = 0; - _self.TotalSize = 0; - roll_init(); - } - - /// - /// Updates the hash with data. - /// Data buffer. - /// Length of buffer to hash. - public void Update(byte[] data, uint len) - { - _self.TotalSize += len; - - for(int i = 0; i < len; i++) - fuzzy_engine_step(data[i]); - } - - /// - /// Updates the hash with data. - /// Data buffer. - public void Update(byte[] data) => Update(data, (uint)data.Length); - - /// - /// Returns a byte array of the hash value. - public byte[] Final() => throw new NotImplementedException("SpamSum does not have a binary representation."); - - /// - /// Returns a base64 representation of the hash value. - public string End() - { - FuzzyDigest(out byte[] result); - - return CToString(result); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void roll_init() => _self.Roll = new RollState - { - Window = new byte[ROLLING_WINDOW] - }; - - /* - * a rolling hash, based on the Adler checksum. By using a rolling hash - * we can perform auto resynchronisation after inserts/deletes - - * internally, h1 is the sum of the bytes in the window and h2 - * is the sum of the bytes times the index - - * h3 is a shift/xor based rolling hash, and is mostly needed to ensure that - * we can cope with large blocksize values - */ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void roll_hash(byte c) - { - _self.Roll.H2 -= _self.Roll.H1; - _self.Roll.H2 += ROLLING_WINDOW * c; - - _self.Roll.H1 += c; - _self.Roll.H1 -= _self.Roll.Window[_self.Roll.N % ROLLING_WINDOW]; - - _self.Roll.Window[_self.Roll.N % ROLLING_WINDOW] = c; - _self.Roll.N++; - - /* The original spamsum AND'ed this value with 0xFFFFFFFF which - * in theory should have no effect. This AND has been removed - * for performance (jk) */ - _self.Roll.H3 <<= 5; - _self.Roll.H3 ^= c; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - uint roll_sum() => _self.Roll.H1 + _self.Roll.H2 + _self.Roll.H3; - - /* A simple non-rolling hash, based on the FNV hash. */ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static uint sum_hash(byte c, uint h) => (h * HASH_PRIME) ^ c; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static uint SSDEEP_BS(uint index) => MIN_BLOCKSIZE << (int)index; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void fuzzy_try_fork_blockhash() - { - if(_self.Bhend >= NUM_BLOCKHASHES) - return; - - if(_self.Bhend == 0) // assert - throw new Exception("Assertion failed"); - - uint obh = _self.Bhend - 1; - uint nbh = _self.Bhend; - _self.Bh[nbh].H = _self.Bh[obh].H; - _self.Bh[nbh].Halfh = _self.Bh[obh].Halfh; - _self.Bh[nbh].Digest[0] = 0; - _self.Bh[nbh].Halfdigest = 0; - _self.Bh[nbh].Dlen = 0; - ++_self.Bhend; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void fuzzy_try_reduce_blockhash() - { - if(_self.Bhstart >= _self.Bhend) - throw new Exception("Assertion failed"); - - if(_self.Bhend - _self.Bhstart < 2) - /* Need at least two working hashes. */ - return; - - if((ulong)SSDEEP_BS(_self.Bhstart) * SPAMSUM_LENGTH >= _self.TotalSize) - /* Initial blocksize estimate would select this or a smaller - * blocksize. */ - return; - - if(_self.Bh[_self.Bhstart + 1].Dlen < SPAMSUM_LENGTH / 2) - /* Estimate adjustment would select this blocksize. */ - return; - - /* At this point we are clearly no longer interested in the - * start_blocksize. Get rid of it. */ - ++_self.Bhstart; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void fuzzy_engine_step(byte c) - { - uint i; - /* At each character we update the rolling hash and the normal hashes. - * When the rolling hash hits a reset value then we emit a normal hash - * as a element of the signature and reset the normal hash. */ - roll_hash(c); - ulong h = roll_sum(); - - for(i = _self.Bhstart; i < _self.Bhend; ++i) - { - _self.Bh[i].H = sum_hash(c, _self.Bh[i].H); - _self.Bh[i].Halfh = sum_hash(c, _self.Bh[i].Halfh); - } - - for(i = _self.Bhstart; i < _self.Bhend; ++i) - { - /* With growing blocksize almost no runs fail the next test. */ - if(h % SSDEEP_BS(i) != SSDEEP_BS(i) - 1) - /* Once this condition is false for one bs, it is - * automatically false for all further bs. I.e. if - * h === -1 (mod 2*bs) then h === -1 (mod bs). */ - break; - - /* We have hit a reset point. We now emit hashes which are - * based on all characters in the piece of the message between - * the last reset point and this one */ - if(0 == _self.Bh[i].Dlen) - fuzzy_try_fork_blockhash(); - - _self.Bh[i].Digest[_self.Bh[i].Dlen] = _b64[_self.Bh[i].H % 64]; - _self.Bh[i].Halfdigest = _b64[_self.Bh[i].Halfh % 64]; - - if(_self.Bh[i].Dlen < SPAMSUM_LENGTH - 1) - { - /* We can have a problem with the tail overflowing. The - * easiest way to cope with this is to only reset the - * normal hash if we have room for more characters in - * our signature. This has the effect of combining the - * last few pieces of the message into a single piece - * */ - _self.Bh[i].Digest[++_self.Bh[i].Dlen] = 0; - _self.Bh[i].H = HASH_INIT; - - if(_self.Bh[i].Dlen >= SPAMSUM_LENGTH / 2) - continue; - - _self.Bh[i].Halfh = HASH_INIT; - _self.Bh[i].Halfdigest = 0; - } - else - fuzzy_try_reduce_blockhash(); + _self.Bh[i].Halfh = HASH_INIT; + _self.Bh[i].Halfdigest = 0; } + else + fuzzy_try_reduce_blockhash(); } + } - // CLAUNIA: Flags seems to never be used in ssdeep, so I just removed it for code simplicity - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void FuzzyDigest(out byte[] result) + // CLAUNIA: Flags seems to never be used in ssdeep, so I just removed it for code simplicity + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void FuzzyDigest(out byte[] result) + { + var sb = new StringBuilder(); + uint bi = _self.Bhstart; + uint h = roll_sum(); + int remain = (int)(FUZZY_MAX_RESULT - 1); /* Exclude terminating '\0'. */ + result = new byte[FUZZY_MAX_RESULT]; + + /* Verify that our elimination was not overeager. */ + if(!(bi == 0 || (ulong)SSDEEP_BS(bi) / 2 * SPAMSUM_LENGTH < _self.TotalSize)) + throw new Exception("Assertion failed"); + + int resultOff; + + /* Initial blocksize guess. */ + while((ulong)SSDEEP_BS(bi) * SPAMSUM_LENGTH < _self.TotalSize) { - var sb = new StringBuilder(); - uint bi = _self.Bhstart; - uint h = roll_sum(); - int remain = (int)(FUZZY_MAX_RESULT - 1); /* Exclude terminating '\0'. */ - result = new byte[FUZZY_MAX_RESULT]; + ++bi; - /* Verify that our elimination was not overeager. */ - if(!(bi == 0 || (ulong)SSDEEP_BS(bi) / 2 * SPAMSUM_LENGTH < _self.TotalSize)) - throw new Exception("Assertion failed"); - - int resultOff; - - /* Initial blocksize guess. */ - while((ulong)SSDEEP_BS(bi) * SPAMSUM_LENGTH < _self.TotalSize) - { - ++bi; - - if(bi >= NUM_BLOCKHASHES) - throw new OverflowException("The input exceeds data types."); - } - - /* Adapt blocksize guess to actual digest length. */ - while(bi >= _self.Bhend) - --bi; - - while(bi > _self.Bhstart && - _self.Bh[bi].Dlen < SPAMSUM_LENGTH / 2) - --bi; - - if(bi > 0 && - _self.Bh[bi].Dlen < SPAMSUM_LENGTH / 2) - throw new Exception("Assertion failed"); - - sb.AppendFormat("{0}:", SSDEEP_BS(bi)); - int i = Encoding.ASCII.GetBytes(sb.ToString()).Length; - - if(i <= 0) - /* Maybe snprintf has set errno here? */ + if(bi >= NUM_BLOCKHASHES) throw new OverflowException("The input exceeds data types."); + } - if(i >= remain) + /* Adapt blocksize guess to actual digest length. */ + while(bi >= _self.Bhend) + --bi; + + while(bi > _self.Bhstart && + _self.Bh[bi].Dlen < SPAMSUM_LENGTH / 2) + --bi; + + if(bi > 0 && + _self.Bh[bi].Dlen < SPAMSUM_LENGTH / 2) + throw new Exception("Assertion failed"); + + sb.AppendFormat("{0}:", SSDEEP_BS(bi)); + int i = Encoding.ASCII.GetBytes(sb.ToString()).Length; + + if(i <= 0) + /* Maybe snprintf has set errno here? */ + throw new OverflowException("The input exceeds data types."); + + if(i >= remain) + throw new Exception("Assertion failed"); + + remain -= i; + + Array.Copy(Encoding.ASCII.GetBytes(sb.ToString()), 0, result, 0, i); + + resultOff = i; + + i = (int)_self.Bh[bi].Dlen; + + if(i > remain) + throw new Exception("Assertion failed"); + + Array.Copy(_self.Bh[bi].Digest, 0, result, resultOff, i); + resultOff += i; + remain -= i; + + if(h != 0) + { + if(remain <= 0) throw new Exception("Assertion failed"); - remain -= i; + result[resultOff] = _b64[_self.Bh[bi].H % 64]; - Array.Copy(Encoding.ASCII.GetBytes(sb.ToString()), 0, result, 0, i); + if(i < 3 || + result[resultOff] != result[resultOff - 1] || + result[resultOff] != result[resultOff - 2] || + result[resultOff] != result[resultOff - 3]) + { + ++resultOff; + --remain; + } + } + else if(_self.Bh[bi].Digest[i] != 0) + { + if(remain <= 0) + throw new Exception("Assertion failed"); - resultOff = i; + result[resultOff] = _self.Bh[bi].Digest[i]; + if(i < 3 || + result[resultOff] != result[resultOff - 1] || + result[resultOff] != result[resultOff - 2] || + result[resultOff] != result[resultOff - 3]) + { + ++resultOff; + --remain; + } + } + + if(remain <= 0) + throw new Exception("Assertion failed"); + + result[resultOff++] = 0x3A; // ':' + --remain; + + if(bi < _self.Bhend - 1) + { + ++bi; i = (int)_self.Bh[bi].Dlen; if(i > remain) @@ -332,7 +383,8 @@ namespace Aaru6.Checksums if(remain <= 0) throw new Exception("Assertion failed"); - result[resultOff] = _b64[_self.Bh[bi].H % 64]; + h = _self.Bh[bi].Halfh; + result[resultOff] = _b64[h % 64]; if(i < 3 || result[resultOff] != result[resultOff - 1] || @@ -343,48 +395,16 @@ namespace Aaru6.Checksums --remain; } } - else if(_self.Bh[bi].Digest[i] != 0) + else { - if(remain <= 0) - throw new Exception("Assertion failed"); + i = _self.Bh[bi].Halfdigest; - result[resultOff] = _self.Bh[bi].Digest[i]; - - if(i < 3 || - result[resultOff] != result[resultOff - 1] || - result[resultOff] != result[resultOff - 2] || - result[resultOff] != result[resultOff - 3]) - { - ++resultOff; - --remain; - } - } - - if(remain <= 0) - throw new Exception("Assertion failed"); - - result[resultOff++] = 0x3A; // ':' - --remain; - - if(bi < _self.Bhend - 1) - { - ++bi; - i = (int)_self.Bh[bi].Dlen; - - if(i > remain) - throw new Exception("Assertion failed"); - - Array.Copy(_self.Bh[bi].Digest, 0, result, resultOff, i); - resultOff += i; - remain -= i; - - if(h != 0) + if(i != 0) { if(remain <= 0) throw new Exception("Assertion failed"); - h = _self.Bh[bi].Halfh; - result[resultOff] = _b64[h % 64]; + result[resultOff] = (byte)i; if(i < 3 || result[resultOff] != result[resultOff - 1] || @@ -395,133 +415,112 @@ namespace Aaru6.Checksums --remain; } } - else - { - i = _self.Bh[bi].Halfdigest; - - if(i != 0) - { - if(remain <= 0) - throw new Exception("Assertion failed"); - - result[resultOff] = (byte)i; - - if(i < 3 || - result[resultOff] != result[resultOff - 1] || - result[resultOff] != result[resultOff - 2] || - result[resultOff] != result[resultOff - 3]) - { - ++resultOff; - --remain; - } - } - } } - else if(h != 0) - { - if(_self.Bh[bi].Dlen != 0) - throw new Exception("Assertion failed"); - - if(remain <= 0) - throw new Exception("Assertion failed"); - - result[resultOff++] = _b64[_self.Bh[bi].H % 64]; - /* No need to bother with FUZZY_FLAG_ELIMSEQ, because this - * digest has length 1. */ - --remain; - } - - result[resultOff] = 0; } - - /// Gets the hash of a file - /// File path. - public static byte[] File(string filename) => - throw new NotImplementedException("SpamSum does not have a binary representation."); - - /// Gets the hash of a file in hexadecimal and as a byte array. - /// File path. - /// Byte array of the hash value. - public static string File(string filename, out byte[] hash) => - throw new NotImplementedException("Not yet implemented."); - - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// Length of the data buffer to hash. - /// null - /// Base64 representation of SpamSum $blocksize:$hash:$hash - public static string Data(byte[] data, uint len, out byte[] hash) + else if(h != 0) { - var fuzzyContext = new SpamSumContext(); + if(_self.Bh[bi].Dlen != 0) + throw new Exception("Assertion failed"); - fuzzyContext.Update(data, len); + if(remain <= 0) + throw new Exception("Assertion failed"); - hash = null; - - return fuzzyContext.End(); + result[resultOff++] = _b64[_self.Bh[bi].H % 64]; + /* No need to bother with FUZZY_FLAG_ELIMSEQ, because this + * digest has length 1. */ + --remain; } - /// Gets the hash of the specified data buffer. - /// Data buffer. - /// null - /// Base64 representation of SpamSum $blocksize:$hash:$hash - public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); + result[resultOff] = 0; + } - // Converts an ASCII null-terminated string to .NET string - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static string CToString(byte[] cString) + /// Gets the hash of a file + /// File path. + public static byte[] File(string filename) => + throw new NotImplementedException("SpamSum does not have a binary representation."); + + /// Gets the hash of a file in hexadecimal and as a byte array. + /// File path. + /// Byte array of the hash value. + public static string File(string filename, out byte[] hash) => + throw new NotImplementedException("Not yet implemented."); + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// Length of the data buffer to hash. + /// null + /// Base64 representation of SpamSum $blocksize:$hash:$hash + public static string Data(byte[] data, uint len, out byte[] hash) + { + var fuzzyContext = new SpamSumContext(); + + fuzzyContext.Update(data, len); + + hash = null; + + return fuzzyContext.End(); + } + + /// Gets the hash of the specified data buffer. + /// Data buffer. + /// null + /// Base64 representation of SpamSum $blocksize:$hash:$hash + public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash); + + // Converts an ASCII null-terminated string to .NET string + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string CToString(byte[] cString) + { + int count = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + // LINQ is six times slower + foreach(byte c in cString) { - int count = 0; + if(c == 0) + break; - // ReSharper disable once LoopCanBeConvertedToQuery - // LINQ is six times slower - foreach(byte c in cString) - { - if(c == 0) - break; - - count++; - } - - return Encoding.ASCII.GetString(cString, 0, count); + count++; } - struct RollState - { - public byte[] Window; + return Encoding.ASCII.GetString(cString, 0, count); + } - // ROLLING_WINDOW - public uint H1; - public uint H2; - public uint H3; - public uint N; - } + struct RollState + { + public byte[] Window; - /* A blockhash contains a signature state for a specific (implicit) blocksize. - * The blocksize is given by SSDEEP_BS(index). The h and halfh members are the - * FNV hashes, where halfh stops to be reset after digest is SPAMSUM_LENGTH/2 - * long. The halfh hash is needed be able to truncate digest for the second - * output hash to stay compatible with ssdeep output. */ - struct BlockhashContext - { - public uint H; - public uint Halfh; - public byte[] Digest; + // ROLLING_WINDOW + public uint H1; + public uint H2; + public uint H3; + public uint N; + } - // SPAMSUM_LENGTH - public byte Halfdigest; - public uint Dlen; - } + /* A blockhash contains a signature state for a specific (implicit) blocksize. + * The blocksize is given by SSDEEP_BS(index). The h and halfh members are the + * FNV hashes, where halfh stops to be reset after digest is SPAMSUM_LENGTH/2 + * long. The halfh hash is needed be able to truncate digest for the second + * output hash to stay compatible with ssdeep output. */ + struct BlockhashContext + { + public uint H; + public uint Halfh; + public byte[] Digest; - struct FuzzyState - { - public uint Bhstart; - public uint Bhend; - public BlockhashContext[] Bh; + // SPAMSUM_LENGTH + public byte Halfdigest; + public uint Dlen; + } - //NUM_BLOCKHASHES - public ulong TotalSize; - public RollState Roll; - } + struct FuzzyState + { + public uint Bhstart; + public uint Bhend; + public BlockhashContext[] Bh; + + //NUM_BLOCKHASHES + public ulong TotalSize; + public RollState Roll; } } \ No newline at end of file diff --git a/Aaru6.Compression/ADC.cs b/Aaru6.Compression/ADC.cs index ed0eca8..e3a3f70 100644 --- a/Aaru6.Compression/ADC.cs +++ b/Aaru6.Compression/ADC.cs @@ -24,141 +24,139 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -using System; using System.Runtime.CompilerServices; -namespace Aaru6.Compression +namespace Aaru6.Compression; + +/// Implements the Apple version of RLE +public class ADC { - /// Implements the Apple version of RLE - public class ADC + const int PLAIN = 1; + const int TWO_BYTE = 2; + const int THREE_BYTE = 3; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int GetChunkType(byte byt) => (byt & 0x80) == 0x80 + ? PLAIN + : (byt & 0x40) == 0x40 + ? THREE_BYTE + : TWO_BYTE; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int GetChunkSize(byte byt) => GetChunkType(byt) switch { - const int PLAIN = 1; - const int TWO_BYTE = 2; - const int THREE_BYTE = 3; + PLAIN => (byt & 0x7F) + 1, + TWO_BYTE => ((byt & 0x3F) >> 2) + 3, + THREE_BYTE => (byt & 0x3F) + 4, + _ => -1 + }; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int GetChunkType(byte byt) => (byt & 0x80) == 0x80 - ? PLAIN - : (byt & 0x40) == 0x40 - ? THREE_BYTE - : TWO_BYTE; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int GetOffset(ReadOnlySpan chunk) => GetChunkType(chunk[0]) switch + { + PLAIN => 0, + TWO_BYTE => ((chunk[0] & 0x03) << 8) + chunk[1], + THREE_BYTE => (chunk[1] << 8) + chunk[2], + _ => -1 + }; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int GetChunkSize(byte byt) => GetChunkType(byt) switch + /// Decompresses a byte buffer that's compressed with ADC + /// Compressed buffer + /// Buffer to hold decompressed data + /// How many bytes are stored on + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static int DecodeBuffer(byte[] source, byte[] destination) + { + int inputPosition = 0; + int chunkSize; + int offset; + int chunkType; + int outPosition = 0; + Span temp = stackalloc byte[3]; + + while(inputPosition < source.Length) { - PLAIN => (byt & 0x7F) + 1, - TWO_BYTE => ((byt & 0x3F) >> 2) + 3, - THREE_BYTE => (byt & 0x3F) + 4, - _ => -1 - }; + byte readByte = source[inputPosition++]; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int GetOffset(ReadOnlySpan chunk) => GetChunkType(chunk[0]) switch - { - PLAIN => 0, - TWO_BYTE => ((chunk[0] & 0x03) << 8) + chunk[1], - THREE_BYTE => (chunk[1] << 8) + chunk[2], - _ => -1 - }; + chunkType = GetChunkType(readByte); - /// Decompresses a byte buffer that's compressed with ADC - /// Compressed buffer - /// Buffer to hold decompressed data - /// How many bytes are stored on - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static int DecodeBuffer(byte[] source, byte[] destination) - { - int inputPosition = 0; - int chunkSize; - int offset; - int chunkType; - int outPosition = 0; - Span temp = stackalloc byte[3]; - - while(inputPosition < source.Length) + switch(chunkType) { - byte readByte = source[inputPosition++]; + case PLAIN: + chunkSize = GetChunkSize(readByte); - chunkType = GetChunkType(readByte); + if(outPosition + chunkSize > destination.Length) + goto finished; - switch(chunkType) - { - case PLAIN: - chunkSize = GetChunkSize(readByte); + Array.Copy(source, inputPosition, destination, outPosition, chunkSize); + outPosition += chunkSize; + inputPosition += chunkSize; - if(outPosition + chunkSize > destination.Length) - goto finished; + break; + case TWO_BYTE: + chunkSize = GetChunkSize(readByte); + temp[0] = readByte; + temp[1] = source[inputPosition++]; + offset = GetOffset(temp); - Array.Copy(source, inputPosition, destination, outPosition, chunkSize); - outPosition += chunkSize; - inputPosition += chunkSize; + if(outPosition + chunkSize > destination.Length) + goto finished; - break; - case TWO_BYTE: - chunkSize = GetChunkSize(readByte); - temp[0] = readByte; - temp[1] = source[inputPosition++]; - offset = GetOffset(temp); + if(offset == 0) + { + byte lastByte = destination[outPosition - 1]; - if(outPosition + chunkSize > destination.Length) - goto finished; - - if(offset == 0) + for(int i = 0; i < chunkSize; i++) { - byte lastByte = destination[outPosition - 1]; - - for(int i = 0; i < chunkSize; i++) - { - destination[outPosition] = lastByte; - outPosition++; - } + destination[outPosition] = lastByte; + outPosition++; } - else + } + else + { + for(int i = 0; i < chunkSize; i++) { - for(int i = 0; i < chunkSize; i++) - { - destination[outPosition] = destination[outPosition - offset - 1]; - outPosition++; - } + destination[outPosition] = destination[outPosition - offset - 1]; + outPosition++; } + } - break; - case THREE_BYTE: - chunkSize = GetChunkSize(readByte); - temp[0] = readByte; - temp[1] = source[inputPosition++]; - temp[2] = source[inputPosition++]; - offset = GetOffset(temp); + break; + case THREE_BYTE: + chunkSize = GetChunkSize(readByte); + temp[0] = readByte; + temp[1] = source[inputPosition++]; + temp[2] = source[inputPosition++]; + offset = GetOffset(temp); - if(outPosition + chunkSize > destination.Length) - goto finished; + if(outPosition + chunkSize > destination.Length) + goto finished; - if(offset == 0) + if(offset == 0) + { + byte lastByte = destination[outPosition - 1]; + + for(int i = 0; i < chunkSize; i++) { - byte lastByte = destination[outPosition - 1]; - - for(int i = 0; i < chunkSize; i++) - { - destination[outPosition] = lastByte; - outPosition++; - } + destination[outPosition] = lastByte; + outPosition++; } - else + } + else + { + for(int i = 0; i < chunkSize; i++) { - for(int i = 0; i < chunkSize; i++) - { - destination[outPosition] = destination[outPosition - offset - 1]; - outPosition++; - } + destination[outPosition] = destination[outPosition - offset - 1]; + outPosition++; } + } - break; - } + break; } - - finished: - - return outPosition; } + + finished: + + return outPosition; } } \ No newline at end of file diff --git a/Aaru6.Compression/AppleRle.cs b/Aaru6.Compression/AppleRle.cs index f572e55..eeaeffb 100644 --- a/Aaru6.Compression/AppleRle.cs +++ b/Aaru6.Compression/AppleRle.cs @@ -31,94 +31,93 @@ // Copyright © 2018-2019 David Ryskalczyk // ****************************************************************************/ -namespace Aaru6.Compression +namespace Aaru6.Compression; + +/// Implements the Apple version of RLE +public class AppleRle { - /// Implements the Apple version of RLE - public class AppleRle + const uint DART_CHUNK = 20960; + + /// Decodes a buffer compressed with Apple RLE + /// Encoded buffer + /// Buffer where to write the decoded data + /// The number of decoded bytes + public static int DecodeBuffer(byte[] source, byte[] destination) { - const uint DART_CHUNK = 20960; + int count = 0; + bool nextA = true; // true if A, false if B + byte repeatedByteA = 0, repeatedByteB = 0; + bool repeatMode = false; // true if we're repeating, false if we're just copying + int inPosition = 0, outPosition = 0; - /// Decodes a buffer compressed with Apple RLE - /// Encoded buffer - /// Buffer where to write the decoded data - /// The number of decoded bytes - public static int DecodeBuffer(byte[] source, byte[] destination) + while(inPosition <= source.Length && + outPosition <= destination.Length) { - int count = 0; - bool nextA = true; // true if A, false if B - byte repeatedByteA = 0, repeatedByteB = 0; - bool repeatMode = false; // true if we're repeating, false if we're just copying - int inPosition = 0, outPosition = 0; - - while(inPosition <= source.Length && - outPosition <= destination.Length) + switch(repeatMode) { - switch(repeatMode) + case true when count > 0: { - case true when count > 0: + count--; + + if(nextA) { - count--; - - if(nextA) - { - nextA = false; - - destination[outPosition++] = repeatedByteA; - - continue; - } - - nextA = true; - - destination[outPosition++] = repeatedByteB; - - continue; - } - case false when count > 0: - count--; - - destination[outPosition++] = source[inPosition++]; - - continue; - } - - if(inPosition == source.Length) - break; - - while(true) - { - byte b1 = source[inPosition++]; - byte b2 = source[inPosition++]; - short s = (short)((b1 << 8) | b2); - - if(s == 0 || - s >= DART_CHUNK || - s <= -DART_CHUNK) - continue; - - if(s < 0) - { - repeatMode = true; - repeatedByteA = source[inPosition++]; - repeatedByteB = source[inPosition++]; - count = (-s * 2) - 1; - nextA = false; + nextA = false; destination[outPosition++] = repeatedByteA; - break; + continue; } - repeatMode = false; - count = (s * 2) - 1; + nextA = true; + + destination[outPosition++] = repeatedByteB; + + continue; + } + case false when count > 0: + count--; destination[outPosition++] = source[inPosition++]; - break; - } + continue; } - return outPosition; + if(inPosition == source.Length) + break; + + while(true) + { + byte b1 = source[inPosition++]; + byte b2 = source[inPosition++]; + short s = (short)((b1 << 8) | b2); + + if(s == 0 || + s >= DART_CHUNK || + s <= -DART_CHUNK) + continue; + + if(s < 0) + { + repeatMode = true; + repeatedByteA = source[inPosition++]; + repeatedByteB = source[inPosition++]; + count = (-s * 2) - 1; + nextA = false; + + destination[outPosition++] = repeatedByteA; + + break; + } + + repeatMode = false; + count = (s * 2) - 1; + + destination[outPosition++] = source[inPosition++]; + + break; + } } + + return outPosition; } } \ No newline at end of file diff --git a/Aaru6.Compression/TeleDiskLzh.cs b/Aaru6.Compression/TeleDiskLzh.cs index 1fb6f7f..b8a13c7 100644 --- a/Aaru6.Compression/TeleDiskLzh.cs +++ b/Aaru6.Compression/TeleDiskLzh.cs @@ -42,413 +42,409 @@ // Copyright © 1988 Kenji RIKITAKE // ****************************************************************************/ -using System; -using System.IO; +namespace Aaru6.Compression; -namespace Aaru6.Compression +/* + * Based on Japanese version 29-NOV-1988 + * LZSS coded by Haruhiko OKUMURA + * Adaptive Huffman Coding coded by Haruyasu YOSHIZAKI + * Edited and translated to English by Kenji RIKITAKE + */ +/// Implements the TeleDisk version of LZH +public class TeleDiskLzh { + const int BUFSZ = 512; + + /* LZSS Parameters */ + + const int N = 4096; /* Size of string buffer */ + const int F = 60; /* Size of look-ahead buffer */ + const int THRESHOLD = 2; + + /* Huffman coding parameters */ + + const int N_CHAR = 256 - THRESHOLD + F; + /* character code (= 0..N_CHAR-1) */ + const int T = (N_CHAR * 2) - 1; /* Size of table */ + const int ROOT = T - 1; /* root position */ + const int MAX_FREQ = 0x8000; + /* - * Based on Japanese version 29-NOV-1988 - * LZSS coded by Haruhiko OKUMURA - * Adaptive Huffman Coding coded by Haruyasu YOSHIZAKI - * Edited and translated to English by Kenji RIKITAKE + * Tables for encoding/decoding upper 6 bits of + * sliding dictionary pointer */ - /// Implements the TeleDisk version of LZH - public class TeleDiskLzh + + /* decoder table */ + readonly byte[] _dCode = { - const int BUFSZ = 512; + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0E, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F, 0x10, 0x10, + 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, + 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1A, + 0x1B, 0x1B, 0x1C, 0x1C, 0x1D, 0x1D, 0x1E, 0x1E, 0x1F, 0x1F, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, + 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, 0x2C, 0x2C, + 0x2D, 0x2D, 0x2E, 0x2E, 0x2F, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, + 0x3C, 0x3D, 0x3E, 0x3F + }; - /* LZSS Parameters */ + readonly byte[] _dLen = + { + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08 + }; + readonly ushort[] _freq = new ushort[T + 1]; /* cumulative freq table */ - const int N = 4096; /* Size of string buffer */ - const int F = 60; /* Size of look-ahead buffer */ - const int THRESHOLD = 2; + readonly Stream _inStream; - /* Huffman coding parameters */ + /* + * pointing parent nodes. + * area [T..(T + N_CHAR - 1)] are pointers for leaves + */ + readonly short[] _prnt = new short[T + N_CHAR]; - const int N_CHAR = 256 - THRESHOLD + F; - /* character code (= 0..N_CHAR-1) */ - const int T = (N_CHAR * 2) - 1; /* Size of table */ - const int ROOT = T - 1; /* root position */ - const int MAX_FREQ = 0x8000; + /* pointing children nodes (son[], son[] + 1)*/ + readonly short[] _son = new short[T]; + readonly byte[] _textBuf = new byte[N + F - 1]; - /* - * Tables for encoding/decoding upper 6 bits of - * sliding dictionary pointer - */ + ushort _getbuf; + byte _getlen; - /* decoder table */ - readonly byte[] _dCode = - { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, - 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0E, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F, 0x10, 0x10, - 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, - 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1A, - 0x1B, 0x1B, 0x1C, 0x1C, 0x1D, 0x1D, 0x1E, 0x1E, 0x1F, 0x1F, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, - 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, 0x2C, 0x2C, - 0x2D, 0x2D, 0x2E, 0x2E, 0x2F, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, - 0x3C, 0x3D, 0x3E, 0x3F - }; + Tdlzhuf _tdctl; - readonly byte[] _dLen = - { - 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, - 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, - 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, - 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, - 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, - 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08 - }; - readonly ushort[] _freq = new ushort[T + 1]; /* cumulative freq table */ + /// Implements the TeleDisk LZH algorithm over the specified stream. + /// Stream with compressed data. + public TeleDiskLzh(Stream dataStream) + { + int i; + _getbuf = 0; + _getlen = 0; + _tdctl = new Tdlzhuf(); + _tdctl.Ibufcnt = _tdctl.Ibufndx = 0; // input buffer is empty + _tdctl.Bufcnt = 0; + StartHuff(); - readonly Stream _inStream; + for(i = 0; i < N - F; i++) + _textBuf[i] = 0x20; - /* - * pointing parent nodes. - * area [T..(T + N_CHAR - 1)] are pointers for leaves - */ - readonly short[] _prnt = new short[T + N_CHAR]; + _tdctl.R = N - F; + _inStream = dataStream; + } - /* pointing children nodes (son[], son[] + 1)*/ - readonly short[] _son = new short[T]; - readonly byte[] _textBuf = new byte[N + F - 1]; + /* DeCompression - ushort _getbuf; - byte _getlen; + split out initialization code to init_Decode() - Tdlzhuf _tdctl; + */ - /// Implements the TeleDisk LZH algorithm over the specified stream. - /// Stream with compressed data. - public TeleDiskLzh(Stream dataStream) - { - int i; - _getbuf = 0; - _getlen = 0; - _tdctl = new Tdlzhuf(); - _tdctl.Ibufcnt = _tdctl.Ibufndx = 0; // input buffer is empty - _tdctl.Bufcnt = 0; - StartHuff(); + /// Decompresses data + /// Buffer to write the decompressed data to + /// Number of bytes to decompress + /// Number of decompressed bytes + public int Decode(out byte[] buf, int len) /* Decoding/Uncompressing */ + { + short c; + buf = new byte[len]; + int count; // was an unsigned long, seems unnecessary - for(i = 0; i < N - F; i++) - _textBuf[i] = 0x20; + for(count = 0; count < len;) + if(_tdctl.Bufcnt == 0) + { + if((c = DecodeChar()) < 0) + return count; // fatal error - _tdctl.R = N - F; - _inStream = dataStream; - } - - /* DeCompression - - split out initialization code to init_Decode() - - */ - - /// Decompresses data - /// Buffer to write the decompressed data to - /// Number of bytes to decompress - /// Number of decompressed bytes - public int Decode(out byte[] buf, int len) /* Decoding/Uncompressing */ - { - short c; - buf = new byte[len]; - int count; // was an unsigned long, seems unnecessary - - for(count = 0; count < len;) - if(_tdctl.Bufcnt == 0) + if(c < 256) { - if((c = DecodeChar()) < 0) - return count; // fatal error - - if(c < 256) - { - buf[count] = (byte)c; - _textBuf[_tdctl.R++] = (byte)c; - _tdctl.R &= N - 1; - count++; - } - else - { - short pos; - - if((pos = DecodePosition()) < 0) - return count; // fatal error - - _tdctl.Bufpos = (ushort)((_tdctl.R - pos - 1) & (N - 1)); - _tdctl.Bufcnt = (ushort)(c - 255 + THRESHOLD); - _tdctl.Bufndx = 0; - } + buf[count] = (byte)c; + _textBuf[_tdctl.R++] = (byte)c; + _tdctl.R &= N - 1; + count++; } else { - // still chars from last string - while(_tdctl.Bufndx < _tdctl.Bufcnt && - count < len) - { - c = _textBuf[(_tdctl.Bufpos + _tdctl.Bufndx) & (N - 1)]; - buf[count] = (byte)c; - _tdctl.Bufndx++; - _textBuf[_tdctl.R++] = (byte)c; - _tdctl.R &= N - 1; - count++; - } + short pos; - // reset bufcnt after copy string from text_buf[] - if(_tdctl.Bufndx >= _tdctl.Bufcnt) - _tdctl.Bufndx = _tdctl.Bufcnt = 0; + if((pos = DecodePosition()) < 0) + return count; // fatal error + + _tdctl.Bufpos = (ushort)((_tdctl.R - pos - 1) & (N - 1)); + _tdctl.Bufcnt = (ushort)(c - 255 + THRESHOLD); + _tdctl.Bufndx = 0; + } + } + else + { + // still chars from last string + while(_tdctl.Bufndx < _tdctl.Bufcnt && + count < len) + { + c = _textBuf[(_tdctl.Bufpos + _tdctl.Bufndx) & (N - 1)]; + buf[count] = (byte)c; + _tdctl.Bufndx++; + _textBuf[_tdctl.R++] = (byte)c; + _tdctl.R &= N - 1; + count++; } - return count; // count == len, success - } - - long DataRead(out byte[] buf, long size) - { - if(size > _inStream.Length - _inStream.Position) - size = _inStream.Length - _inStream.Position; - - buf = new byte[size]; - _inStream.Read(buf, 0, (int)size); - - return size; - } - - int NextWord() - { - if(_tdctl.Ibufndx >= _tdctl.Ibufcnt) - { - _tdctl.Ibufndx = 0; - _tdctl.Ibufcnt = (ushort)DataRead(out _tdctl.Inbuf, BUFSZ); - - if(_tdctl.Ibufcnt <= 0) - return -1; + // reset bufcnt after copy string from text_buf[] + if(_tdctl.Bufndx >= _tdctl.Bufcnt) + _tdctl.Bufndx = _tdctl.Bufcnt = 0; } - while(_getlen <= 8) - { - // typically reads a word at a time - _getbuf |= (ushort)(_tdctl.Inbuf[_tdctl.Ibufndx++] << (8 - _getlen)); - _getlen += 8; - } + return count; // count == len, success + } - return 0; - } + long DataRead(out byte[] buf, long size) + { + if(size > _inStream.Length - _inStream.Position) + size = _inStream.Length - _inStream.Position; - int GetBit() /* get one bit */ + buf = new byte[size]; + _inStream.Read(buf, 0, (int)size); + + return size; + } + + int NextWord() + { + if(_tdctl.Ibufndx >= _tdctl.Ibufcnt) { - if(NextWord() < 0) + _tdctl.Ibufndx = 0; + _tdctl.Ibufcnt = (ushort)DataRead(out _tdctl.Inbuf, BUFSZ); + + if(_tdctl.Ibufcnt <= 0) return -1; - - short i = (short)_getbuf; - _getbuf <<= 1; - _getlen--; - - return i < 0 ? 1 : 0; } - int GetByte() /* get a byte */ + while(_getlen <= 8) { - if(NextWord() != 0) - return -1; - - ushort i = _getbuf; - _getbuf <<= 8; - _getlen -= 8; - i = (ushort)(i >> 8); - - return i; + // typically reads a word at a time + _getbuf |= (ushort)(_tdctl.Inbuf[_tdctl.Ibufndx++] << (8 - _getlen)); + _getlen += 8; } - /* initialize freq tree */ + return 0; + } - void StartHuff() + int GetBit() /* get one bit */ + { + if(NextWord() < 0) + return -1; + + short i = (short)_getbuf; + _getbuf <<= 1; + _getlen--; + + return i < 0 ? 1 : 0; + } + + int GetByte() /* get a byte */ + { + if(NextWord() != 0) + return -1; + + ushort i = _getbuf; + _getbuf <<= 8; + _getlen -= 8; + i = (ushort)(i >> 8); + + return i; + } + + /* initialize freq tree */ + + void StartHuff() + { + int i; + + for(i = 0; i < N_CHAR; i++) { - int i; + _freq[i] = 1; + _son[i] = (short)(i + T); + _prnt[i + T] = (short)i; + } - for(i = 0; i < N_CHAR; i++) + i = 0; + int j = N_CHAR; + + while(j <= ROOT) + { + _freq[j] = (ushort)(_freq[i] + _freq[i + 1]); + _son[j] = (short)i; + _prnt[i] = _prnt[i + 1] = (short)j; + i += 2; + j++; + } + + _freq[T] = 0xffff; + _prnt[ROOT] = 0; + } + + /* reconstruct freq tree */ + + void Reconst() + { + short i, k; + + /* halven cumulative freq for leaf nodes */ + short j = 0; + + for(i = 0; i < T; i++) + if(_son[i] >= T) { - _freq[i] = 1; - _son[i] = (short)(i + T); - _prnt[i + T] = (short)i; - } - - i = 0; - int j = N_CHAR; - - while(j <= ROOT) - { - _freq[j] = (ushort)(_freq[i] + _freq[i + 1]); - _son[j] = (short)i; - _prnt[i] = _prnt[i + 1] = (short)j; - i += 2; + _freq[j] = (ushort)((_freq[i] + 1) / 2); + _son[j] = _son[i]; j++; } - _freq[T] = 0xffff; - _prnt[ROOT] = 0; + /* make a tree : first, connect children nodes */ + for(i = 0, j = N_CHAR; j < T; i += 2, j++) + { + k = (short)(i + 1); + ushort f = _freq[j] = (ushort)(_freq[i] + _freq[k]); + + for(k = (short)(j - 1); f < _freq[k]; k--) {} + + k++; + ushort l = (ushort)((j - k) * 2); + + Array.ConstrainedCopy(_freq, k, _freq, k + 1, l); + _freq[k] = f; + Array.ConstrainedCopy(_son, k, _son, k + 1, l); + _son[k] = i; } - /* reconstruct freq tree */ + /* connect parent nodes */ + for(i = 0; i < T; i++) + if((k = _son[i]) >= T) + _prnt[k] = i; + else + _prnt[k] = _prnt[k + 1] = i; + } - void Reconst() + /* update freq tree */ + + void Update(int c) + { + if(_freq[ROOT] == MAX_FREQ) + Reconst(); + + c = _prnt[c + T]; + + do { - short i, k; + int k = ++_freq[c]; - /* halven cumulative freq for leaf nodes */ - short j = 0; + /* swap nodes to keep the tree freq-ordered */ + int l; - for(i = 0; i < T; i++) - if(_son[i] >= T) - { - _freq[j] = (ushort)((_freq[i] + 1) / 2); - _son[j] = _son[i]; - j++; - } + if(k <= _freq[l = c + 1]) + continue; - /* make a tree : first, connect children nodes */ - for(i = 0, j = N_CHAR; j < T; i += 2, j++) - { - k = (short)(i + 1); - ushort f = _freq[j] = (ushort)(_freq[i] + _freq[k]); + while(k > _freq[++l]) {} - for(k = (short)(j - 1); f < _freq[k]; k--) {} + l--; + _freq[c] = _freq[l]; + _freq[l] = (ushort)k; - k++; - ushort l = (ushort)((j - k) * 2); + int i = _son[c]; + _prnt[i] = (short)l; - Array.ConstrainedCopy(_freq, k, _freq, k + 1, l); - _freq[k] = f; - Array.ConstrainedCopy(_son, k, _son, k + 1, l); - _son[k] = i; - } + if(i < T) + _prnt[i + 1] = (short)l; - /* connect parent nodes */ - for(i = 0; i < T; i++) - if((k = _son[i]) >= T) - _prnt[k] = i; - else - _prnt[k] = _prnt[k + 1] = i; - } + int j = _son[l]; + _son[l] = (short)i; - /* update freq tree */ + _prnt[j] = (short)c; - void Update(int c) + if(j < T) + _prnt[j + 1] = (short)c; + + _son[c] = (short)j; + + c = l; + } while((c = _prnt[c]) != 0); /* do it until reaching the root */ + } + + short DecodeChar() + { + ushort c = (ushort)_son[ROOT]; + + /* + * start searching tree from the root to leaves. + * choose node #(son[]) if input bit == 0 + * else choose #(son[]+1) (input bit == 1) + */ + while(c < T) { - if(_freq[ROOT] == MAX_FREQ) - Reconst(); + int ret; - c = _prnt[c + T]; - - do - { - int k = ++_freq[c]; - - /* swap nodes to keep the tree freq-ordered */ - int l; - - if(k <= _freq[l = c + 1]) - continue; - - while(k > _freq[++l]) {} - - l--; - _freq[c] = _freq[l]; - _freq[l] = (ushort)k; - - int i = _son[c]; - _prnt[i] = (short)l; - - if(i < T) - _prnt[i + 1] = (short)l; - - int j = _son[l]; - _son[l] = (short)i; - - _prnt[j] = (short)c; - - if(j < T) - _prnt[j + 1] = (short)c; - - _son[c] = (short)j; - - c = l; - } while((c = _prnt[c]) != 0); /* do it until reaching the root */ - } - - short DecodeChar() - { - ushort c = (ushort)_son[ROOT]; - - /* - * start searching tree from the root to leaves. - * choose node #(son[]) if input bit == 0 - * else choose #(son[]+1) (input bit == 1) - */ - while(c < T) - { - int ret; - - if((ret = GetBit()) < 0) - return -1; - - c += (ushort)ret; - c = (ushort)_son[c]; - } - - c -= T; - Update(c); - - return (short)c; - } - - short DecodePosition() - { - short bit; - - /* decode upper 6 bits from given table */ - if((bit = (short)GetByte()) < 0) + if((ret = GetBit()) < 0) return -1; - ushort i = (ushort)bit; - ushort c = (ushort)(_dCode[i] << 6); - ushort j = _dLen[i]; - - /* input lower 6 bits directly */ - j -= 2; - - while(j-- > 0) - { - if((bit = (short)GetBit()) < 0) - return -1; - - i = (ushort)((i << 1) + bit); - } - - return (short)(c | (i & 0x3f)); + c += (ushort)ret; + c = (ushort)_son[c]; } - /* update when cumulative frequency */ - /* reaches to this value */ - struct Tdlzhuf + c -= T; + Update(c); + + return (short)c; + } + + short DecodePosition() + { + short bit; + + /* decode upper 6 bits from given table */ + if((bit = (short)GetByte()) < 0) + return -1; + + ushort i = (ushort)bit; + ushort c = (ushort)(_dCode[i] << 6); + ushort j = _dLen[i]; + + /* input lower 6 bits directly */ + j -= 2; + + while(j-- > 0) { - public ushort R, Bufcnt, Bufndx, Bufpos, // string buffer - // the following to allow block reads from input in next_word() - Ibufcnt, Ibufndx; // input buffer counters - public byte[] Inbuf; // input buffer + if((bit = (short)GetBit()) < 0) + return -1; + + i = (ushort)((i << 1) + bit); } + + return (short)(c | (i & 0x3f)); + } + /* update when cumulative frequency */ + /* reaches to this value */ + + struct Tdlzhuf + { + public ushort R, Bufcnt, Bufndx, Bufpos, // string buffer + // the following to allow block reads from input in next_word() + Ibufcnt, Ibufndx; // input buffer counters + public byte[] Inbuf; // input buffer } } \ No newline at end of file diff --git a/AaruBenchmark/AaruBenchmark.csproj b/AaruBenchmark/AaruBenchmark.csproj index 4084a8d..40c7ea2 100644 --- a/AaruBenchmark/AaruBenchmark.csproj +++ b/AaruBenchmark/AaruBenchmark.csproj @@ -6,68 +6,68 @@ - - - - - - + + + + + + - + Always - + Always - + Always - + Always - + Always - + Always - + Always - + Always - + Always - + Always - + Always - - + + - + diff --git a/AaruBenchmark/Benchs.cs b/AaruBenchmark/Benchs.cs index fa2c7c8..a8e1138 100644 --- a/AaruBenchmark/Benchs.cs +++ b/AaruBenchmark/Benchs.cs @@ -3,380 +3,327 @@ using AaruBenchmark.Compression; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; -namespace AaruBenchmark +namespace AaruBenchmark; + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class AppleRleBenchs { - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class AppleRleBenchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Compression.Aaru.AppleRle(); - - [Benchmark] - public void Aaru6() => Aaru6Compressions.AppleRle(); - - [Benchmark] - public void AaruNative() => Compression.AaruNative.AppleRle(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class TeleDiskLzhBenchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Compression.Aaru.TeleDiskLzh(); - - [Benchmark] - public void Aaru6() => Aaru6Compressions.TeleDiskLzh(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class ADCBenchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Compression.SharpCompress.ADC(); - - [Benchmark] - public void Aaru6() => Aaru6Compressions.ADC(); - - [Benchmark] - public void AaruNative() => Compression.AaruNative.ADC(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class GzipBenchs - { - [Benchmark] - public void SharpCompress() => Compression.SharpCompress.Gzip(); - - [Benchmark(Baseline = true)] - public void DotNetRuntime() => NetRuntime.Gzip(); - - [Benchmark] - public void DotNetZip() => Compression.DotNetZip.Gzip(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class CompressGzipBenchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Compression.SharpCompress.CompressGzip(); - - [Benchmark] - public void Aaru6() => NetRuntime.CompressGzip(); - - [Benchmark] - public void DotNetZip() => Compression.DotNetZip.CompressGzip(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Bzip2Benchs - { - [Benchmark(Baseline = true)] - public void SharpCompress() => Compression.SharpCompress.Bzip2(); - - [Benchmark] - public void DotNetZip() => Compression.DotNetZip.Bzip2(); - - [Benchmark] - public void AaruNative() => Compression.AaruNative.Bzip2(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class CompressBzip2Benchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Compression.SharpCompress.CompressBzip2(); - - [Benchmark] - public void Aaru6() => DotNetZip.CompressBzip2(); - - [Benchmark] - public void AaruNative() => Compression.AaruNative.CompressBzip2(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class LzipBenchs - { - [Benchmark(Baseline = true)] - public void SharpCompress() => Compression.SharpCompress.Lzip(); - - [Benchmark] - public void AaruNative() => Compression.AaruNative.Lzip(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class CompressLzipBenchs - { - [Benchmark(Baseline = true)] - public void SharpCompress() => Compression.SharpCompress.CompressLzip(); - - [Benchmark] - public void AaruNative() => Compression.AaruNative.CompressLzip(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class LzmaBenchs - { - [Benchmark(Baseline = true)] - public void SharpCompress() => Compression.SharpCompress.Lzma(); - - [Benchmark] - public void AaruNative() => Compression.AaruNative.Lzma(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class CompressLzmaBenchs - { - [Benchmark(Baseline = true)] - public void SharpCompress() => Compression.SharpCompress.CompressLzma(); - - [Benchmark] - public void AaruNative() => Compression.AaruNative.CompressLzma(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class FlacBenchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Compression.Aaru.Flac(); - - [Benchmark] - public void AaruNative() => Compression.AaruNative.Flac(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class CompressFlacBenchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Compression.Aaru.CompressFlac(); - - [Benchmark] - public void AaruNative() => Compression.AaruNative.CompressFlac(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Adler32Benchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.Adler32(); - - [Benchmark] - public void Aaru6() => Checksums.Aaru6.Adler32(); - - [Benchmark] - public void AaruNative() => Checksums.AaruNative.Adler32(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Fletcher16Benchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.Fletcher16(); - - [Benchmark] - public void Aaru6() => Checksums.Aaru6.Fletcher16(); - - [Benchmark] - public void AaruNative() => Checksums.AaruNative.Fletcher16(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Fletcher32Benchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.Fletcher32(); - - [Benchmark] - public void Aaru6() => Checksums.Aaru6.Fletcher32(); - - [Benchmark] - public void AaruNative() => Checksums.AaruNative.Fletcher32(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Crc16CcittBenchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.Crc16Ccitt(); - - [Benchmark] - public void Aaru6() => Checksums.Aaru6.Crc16Ccitt(); - - [Benchmark] - public void AaruNative() => Checksums.AaruNative.Crc16Ccitt(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Crc16Benchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.Crc16(); - - [Benchmark] - public void Aaru6() => Checksums.Aaru6.Crc16(); - - [Benchmark] - public void AaruNative() => Checksums.AaruNative.Crc16(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Crc32Benchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.Crc32(); - - [Benchmark] - public void Aaru6() => Checksums.Aaru6.Crc32(); - - [Benchmark] - public void AaruNative() => Checksums.AaruNative.Crc32(); - - [Benchmark] - public void rhash() => RHash.Crc32(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Crc64Benchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.Crc64(); - - [Benchmark] - public void Aaru6() => Checksums.Aaru6.Crc64(); - - [Benchmark] - public void AaruNative() => Checksums.AaruNative.Crc64(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Md5Benchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.Md5(); - - [Benchmark] - public void OpenSSL() => OpenSsl.Md5(); - - [Benchmark] - public void rhash() => RHash.Md5(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Sha1Benchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.Sha1(); - - [Benchmark] - public void OpenSSL() => OpenSsl.Sha1(); - - [Benchmark] - public void rhash() => RHash.Sha1(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Sha256Benchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.Sha256(); - - [Benchmark] - public void OpenSSL() => OpenSsl.Sha256(); - - [Benchmark] - public void rhash() => RHash.Sha256(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Sha384Benchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.Sha384(); - - [Benchmark] - public void OpenSSL() => OpenSsl.Sha384(); - - [Benchmark] - public void rhash() => RHash.Sha384(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class Sha512Benchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.Sha512(); - - [Benchmark] - public void OpenSSL() => OpenSsl.Sha512(); - - [Benchmark] - public void rhash() => RHash.Sha512(); - } - - [SimpleJob(RuntimeMoniker.Net60)] - [SimpleJob(RuntimeMoniker.Net70)] - [SimpleJob(RuntimeMoniker.NativeAot70)] - public class SpamSumBenchs - { - [Benchmark(Baseline = true)] - public void Aaru() => Checksums.Aaru.SpamSum(); - - [Benchmark] - public void Aaru6() => Checksums.Aaru6.SpamSum(); - - [Benchmark] - public void AaruNative() => Checksums.AaruNative.SpamSum(); - - [Benchmark] - public void ssdeep() => Checksums.Aaru.CliSpamSum(); - } + [Benchmark(Baseline = true)] + public void Aaru() => Compression.Aaru.AppleRle(); + + [Benchmark] + public void Aaru6() => Aaru6Compressions.AppleRle(); + + [Benchmark] + public void AaruNative() => Compression.AaruNative.AppleRle(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class TeleDiskLzhBenchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Compression.Aaru.TeleDiskLzh(); + + [Benchmark] + public void Aaru6() => Aaru6Compressions.TeleDiskLzh(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class ADCBenchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Compression.SharpCompress.ADC(); + + [Benchmark] + public void Aaru6() => Aaru6Compressions.ADC(); + + [Benchmark] + public void AaruNative() => Compression.AaruNative.ADC(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class GzipBenchs +{ + [Benchmark] + public void SharpCompress() => Compression.SharpCompress.Gzip(); + + [Benchmark(Baseline = true)] + public void DotNetRuntime() => NetRuntime.Gzip(); + + [Benchmark] + public void DotNetZip() => Compression.DotNetZip.Gzip(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class CompressGzipBenchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Compression.SharpCompress.CompressGzip(); + + [Benchmark] + public void Aaru6() => NetRuntime.CompressGzip(); + + [Benchmark] + public void DotNetZip() => Compression.DotNetZip.CompressGzip(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Bzip2Benchs +{ + [Benchmark(Baseline = true)] + public void SharpCompress() => Compression.SharpCompress.Bzip2(); + + [Benchmark] + public void DotNetZip() => Compression.DotNetZip.Bzip2(); + + [Benchmark] + public void AaruNative() => Compression.AaruNative.Bzip2(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class CompressBzip2Benchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Compression.SharpCompress.CompressBzip2(); + + [Benchmark] + public void Aaru6() => DotNetZip.CompressBzip2(); + + [Benchmark] + public void AaruNative() => Compression.AaruNative.CompressBzip2(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class LzipBenchs +{ + [Benchmark(Baseline = true)] + public void SharpCompress() => Compression.SharpCompress.Lzip(); + + [Benchmark] + public void AaruNative() => Compression.AaruNative.Lzip(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class CompressLzipBenchs +{ + [Benchmark(Baseline = true)] + public void SharpCompress() => Compression.SharpCompress.CompressLzip(); + + [Benchmark] + public void AaruNative() => Compression.AaruNative.CompressLzip(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class LzmaBenchs +{ + [Benchmark(Baseline = true)] + public void SharpCompress() => Compression.SharpCompress.Lzma(); + + [Benchmark] + public void AaruNative() => Compression.AaruNative.Lzma(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class CompressLzmaBenchs +{ + [Benchmark(Baseline = true)] + public void SharpCompress() => Compression.SharpCompress.CompressLzma(); + + [Benchmark] + public void AaruNative() => Compression.AaruNative.CompressLzma(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class FlacBenchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Compression.Aaru.Flac(); + + [Benchmark] + public void AaruNative() => Compression.AaruNative.Flac(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class CompressFlacBenchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Compression.Aaru.CompressFlac(); + + [Benchmark] + public void AaruNative() => Compression.AaruNative.CompressFlac(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Adler32Benchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.Adler32(); + + [Benchmark] + public void Aaru6() => Checksums.Aaru6.Adler32(); + + [Benchmark] + public void AaruNative() => Checksums.AaruNative.Adler32(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Fletcher16Benchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.Fletcher16(); + + [Benchmark] + public void Aaru6() => Checksums.Aaru6.Fletcher16(); + + [Benchmark] + public void AaruNative() => Checksums.AaruNative.Fletcher16(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Fletcher32Benchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.Fletcher32(); + + [Benchmark] + public void Aaru6() => Checksums.Aaru6.Fletcher32(); + + [Benchmark] + public void AaruNative() => Checksums.AaruNative.Fletcher32(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Crc16CcittBenchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.Crc16Ccitt(); + + [Benchmark] + public void Aaru6() => Checksums.Aaru6.Crc16Ccitt(); + + [Benchmark] + public void AaruNative() => Checksums.AaruNative.Crc16Ccitt(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Crc16Benchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.Crc16(); + + [Benchmark] + public void Aaru6() => Checksums.Aaru6.Crc16(); + + [Benchmark] + public void AaruNative() => Checksums.AaruNative.Crc16(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Crc32Benchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.Crc32(); + + [Benchmark] + public void Aaru6() => Checksums.Aaru6.Crc32(); + + [Benchmark] + public void AaruNative() => Checksums.AaruNative.Crc32(); + + [Benchmark] + public void rhash() => RHash.Crc32(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Crc64Benchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.Crc64(); + + [Benchmark] + public void Aaru6() => Checksums.Aaru6.Crc64(); + + [Benchmark] + public void AaruNative() => Checksums.AaruNative.Crc64(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Md5Benchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.Md5(); + + [Benchmark] + public void OpenSSL() => OpenSsl.Md5(); + + [Benchmark] + public void rhash() => RHash.Md5(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Sha1Benchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.Sha1(); + + [Benchmark] + public void OpenSSL() => OpenSsl.Sha1(); + + [Benchmark] + public void rhash() => RHash.Sha1(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Sha256Benchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.Sha256(); + + [Benchmark] + public void OpenSSL() => OpenSsl.Sha256(); + + [Benchmark] + public void rhash() => RHash.Sha256(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Sha384Benchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.Sha384(); + + [Benchmark] + public void OpenSSL() => OpenSsl.Sha384(); + + [Benchmark] + public void rhash() => RHash.Sha384(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class Sha512Benchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.Sha512(); + + [Benchmark] + public void OpenSSL() => OpenSsl.Sha512(); + + [Benchmark] + public void rhash() => RHash.Sha512(); +} + +[SimpleJob(RuntimeMoniker.Net60), SimpleJob(RuntimeMoniker.Net70), SimpleJob(RuntimeMoniker.NativeAot70)] +public class SpamSumBenchs +{ + [Benchmark(Baseline = true)] + public void Aaru() => Checksums.Aaru.SpamSum(); + + [Benchmark] + public void Aaru6() => Checksums.Aaru6.SpamSum(); + + [Benchmark] + public void AaruNative() => Checksums.AaruNative.SpamSum(); + + [Benchmark] + public void ssdeep() => Checksums.Aaru.CliSpamSum(); } \ No newline at end of file diff --git a/AaruBenchmark/Checksums/Aaru.cs b/AaruBenchmark/Checksums/Aaru.cs index c1a7e87..4ecdddf 100644 --- a/AaruBenchmark/Checksums/Aaru.cs +++ b/AaruBenchmark/Checksums/Aaru.cs @@ -5,342 +5,341 @@ using System.Linq; using Aaru.Checksums; using Aaru.CommonTypes.Interfaces; -namespace AaruBenchmark.Checksums +namespace AaruBenchmark.Checksums; + +public class Aaru { - public class Aaru + static readonly byte[] _expectedRandomAdler32 = { - static readonly byte[] _expectedRandomAdler32 = - { - 0x37, 0x28, 0xd1, 0x86 - }; - - static readonly byte[] _expectedRandomFletcher16 = - { - 0x33, 0x57 - }; - - static readonly byte[] _expectedRandomFletcher32 = - { - 0x21, 0x12, 0x61, 0xF5 - }; - - static readonly byte[] _expectedRandomCrc16Ccitt = - { - 0xC9, 0xBF - }; - - static readonly byte[] _expectedRandomCrc16 = - { - 0x2d, 0x6d - }; - - static readonly byte[] _expectedRandomCrc32 = - { - 0x2b, 0x6e, 0x68, 0x54 - }; - - static readonly byte[] _expectedRandomCrc64 = - { - 0xbf, 0x09, 0x99, 0x2c, 0xc5, 0xed, 0xe3, 0x8e - }; - - static readonly byte[] _expectedRandomMd5 = - { - 0xd7, 0x8f, 0x0e, 0xec, 0x41, 0x7b, 0xe3, 0x86, 0x21, 0x9b, 0x21, 0xb7, 0x00, 0x04, 0x4b, 0x95 - }; - - static readonly byte[] _expectedRandomSha1 = - { - 0x72, 0x0d, 0x3b, 0x71, 0x7d, 0xe0, 0xc7, 0x4c, 0x77, 0xdd, 0x9c, 0xaa, 0x9e, 0xba, 0x50, 0x60, 0xdc, 0xbd, - 0x28, 0x8d - }; - - static readonly byte[] _expectedRandomSha256 = - { - 0x4d, 0x1a, 0x6b, 0x8a, 0x54, 0x67, 0x00, 0xc4, 0x8e, 0xda, 0x70, 0xd3, 0x39, 0x1c, 0x8f, 0x15, 0x8a, 0x8d, - 0x12, 0xb2, 0x38, 0x92, 0x89, 0x29, 0x50, 0x47, 0x8c, 0x41, 0x8e, 0x25, 0xcc, 0x39 - }; - - static readonly byte[] _expectedRandomSha384 = - { - 0xdb, 0x53, 0x0e, 0x17, 0x9b, 0x81, 0xfe, 0x5f, 0x6d, 0x20, 0x41, 0x04, 0x6e, 0x77, 0xd9, 0x85, 0xf2, 0x85, - 0x8a, 0x66, 0xca, 0xd3, 0x8d, 0x1a, 0xd5, 0xac, 0x67, 0xa9, 0x74, 0xe1, 0xef, 0x3f, 0x4d, 0xdf, 0x94, 0x15, - 0x2e, 0xac, 0x2e, 0xfe, 0x16, 0x95, 0x81, 0x54, 0xdc, 0x59, 0xd4, 0xc3 - }; - - static readonly byte[] _expectedRandomSha512 = - { - 0x6a, 0x0a, 0x18, 0xc2, 0xad, 0xf8, 0x83, 0xac, 0x58, 0xe6, 0x21, 0x96, 0xdb, 0x8d, 0x3d, 0x0e, 0xb9, 0x87, - 0xd1, 0x49, 0x24, 0x97, 0xdb, 0x15, 0xb9, 0xfc, 0xcc, 0xb0, 0x36, 0xdf, 0x64, 0xae, 0xdb, 0x3e, 0x82, 0xa0, - 0x4d, 0xdc, 0xd1, 0x37, 0x48, 0x92, 0x95, 0x51, 0xf9, 0xdd, 0xab, 0x82, 0xf4, 0x8a, 0x85, 0x3f, 0x9a, 0x01, - 0xb5, 0xf2, 0x8c, 0xbb, 0x4a, 0xa5, 0x1b, 0x40, 0x7c, 0xb6 - }; - - public static void Fletcher16() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Fletcher16Context(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomFletcher16.Length) - throw new Exception("Invalid hash length"); - - if(result.Where((t, i) => t != _expectedRandomFletcher16[i]).Any()) - throw new Exception("Invalid hash value"); - } - - public static void Fletcher32() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Fletcher32Context(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomFletcher32.Length) - throw new Exception("Invalid hash length"); - - if(result.Where((t, i) => t != _expectedRandomFletcher32[i]).Any()) - throw new Exception("Invalid hash value"); - } - - public static void Adler32() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Adler32Context(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomAdler32.Length) - throw new Exception("Invalid hash length"); - - if(result.Where((t, i) => t != _expectedRandomAdler32[i]).Any()) - throw new Exception("Invalid hash value"); - } - - public static void Crc16Ccitt() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new CRC16CCITTContext(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomCrc16Ccitt.Length) - throw new Exception("Invalid hash length"); - - if(result.Where((t, i) => t != _expectedRandomCrc16Ccitt[i]).Any()) - throw new Exception("Invalid hash value"); - } - - public static void Crc16() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new CRC16IBMContext(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomCrc16.Length) - throw new Exception("Invalid hash length"); - - if(result.Where((t, i) => t != _expectedRandomCrc16[i]).Any()) - throw new Exception("Invalid hash value"); - } - - public static void Crc32() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Crc32Context(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomCrc32.Length) - throw new Exception("Invalid hash length"); - - if(result.Where((t, i) => t != _expectedRandomCrc32[i]).Any()) - throw new Exception("Invalid hash value"); - } - - public static void Crc64() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Crc64Context(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomCrc64.Length) - throw new Exception("Invalid hash length"); - - if(result.Where((t, i) => t != _expectedRandomCrc64[i]).Any()) - throw new Exception("Invalid hash value"); - } - - public static void Md5() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Md5Context(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomMd5.Length) - throw new Exception("Invalid hash length"); - - if(result.Where((t, i) => t != _expectedRandomMd5[i]).Any()) - throw new Exception("Invalid hash value"); - } - - public static void SpamSum() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new SpamSumContext(); - ctx.Update(data); - string result = ctx.End(); - } - - public static void CliSpamSum() - { - var proc = new Process(); - proc.StartInfo.UseShellExecute = false; - proc.StartInfo.FileName = "/usr/bin/ssdeep"; - proc.StartInfo.CreateNoWindow = true; - proc.StartInfo.ArgumentList.Add(Path.Combine(Program.Folder, "random")); - proc.StartInfo.RedirectStandardOutput = true; - proc.Start(); - proc.StandardOutput.ReadToEnd(); - proc.WaitForExit(); - } - - public static void Sha1() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Sha1Context(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomSha1.Length) - throw new Exception("Invalid hash length"); - - if(result.Where((t, i) => t != _expectedRandomSha1[i]).Any()) - throw new Exception("Invalid hash value"); - } - - public static void Sha256() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Sha256Context(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomSha256.Length) - throw new Exception("Invalid hash length"); - - if(result.Where((t, i) => t != _expectedRandomSha256[i]).Any()) - throw new Exception("Invalid hash value"); - } - - public static void Sha384() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Sha384Context(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomSha384.Length) - throw new Exception("Invalid hash length"); - - if(result.Where((t, i) => t != _expectedRandomSha384[i]).Any()) - throw new Exception("Invalid hash value"); - } - - public static void Sha512() - { - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Sha512Context(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomSha512.Length) - throw new Exception("Invalid hash length"); - - if(result.Where((t, i) => t != _expectedRandomSha512[i]).Any()) - throw new Exception("Invalid hash value"); - } + 0x37, 0x28, 0xd1, 0x86 + }; + + static readonly byte[] _expectedRandomFletcher16 = + { + 0x33, 0x57 + }; + + static readonly byte[] _expectedRandomFletcher32 = + { + 0x21, 0x12, 0x61, 0xF5 + }; + + static readonly byte[] _expectedRandomCrc16Ccitt = + { + 0xC9, 0xBF + }; + + static readonly byte[] _expectedRandomCrc16 = + { + 0x2d, 0x6d + }; + + static readonly byte[] _expectedRandomCrc32 = + { + 0x2b, 0x6e, 0x68, 0x54 + }; + + static readonly byte[] _expectedRandomCrc64 = + { + 0xbf, 0x09, 0x99, 0x2c, 0xc5, 0xed, 0xe3, 0x8e + }; + + static readonly byte[] _expectedRandomMd5 = + { + 0xd7, 0x8f, 0x0e, 0xec, 0x41, 0x7b, 0xe3, 0x86, 0x21, 0x9b, 0x21, 0xb7, 0x00, 0x04, 0x4b, 0x95 + }; + + static readonly byte[] _expectedRandomSha1 = + { + 0x72, 0x0d, 0x3b, 0x71, 0x7d, 0xe0, 0xc7, 0x4c, 0x77, 0xdd, 0x9c, 0xaa, 0x9e, 0xba, 0x50, 0x60, 0xdc, 0xbd, + 0x28, 0x8d + }; + + static readonly byte[] _expectedRandomSha256 = + { + 0x4d, 0x1a, 0x6b, 0x8a, 0x54, 0x67, 0x00, 0xc4, 0x8e, 0xda, 0x70, 0xd3, 0x39, 0x1c, 0x8f, 0x15, 0x8a, 0x8d, + 0x12, 0xb2, 0x38, 0x92, 0x89, 0x29, 0x50, 0x47, 0x8c, 0x41, 0x8e, 0x25, 0xcc, 0x39 + }; + + static readonly byte[] _expectedRandomSha384 = + { + 0xdb, 0x53, 0x0e, 0x17, 0x9b, 0x81, 0xfe, 0x5f, 0x6d, 0x20, 0x41, 0x04, 0x6e, 0x77, 0xd9, 0x85, 0xf2, 0x85, + 0x8a, 0x66, 0xca, 0xd3, 0x8d, 0x1a, 0xd5, 0xac, 0x67, 0xa9, 0x74, 0xe1, 0xef, 0x3f, 0x4d, 0xdf, 0x94, 0x15, + 0x2e, 0xac, 0x2e, 0xfe, 0x16, 0x95, 0x81, 0x54, 0xdc, 0x59, 0xd4, 0xc3 + }; + + static readonly byte[] _expectedRandomSha512 = + { + 0x6a, 0x0a, 0x18, 0xc2, 0xad, 0xf8, 0x83, 0xac, 0x58, 0xe6, 0x21, 0x96, 0xdb, 0x8d, 0x3d, 0x0e, 0xb9, 0x87, + 0xd1, 0x49, 0x24, 0x97, 0xdb, 0x15, 0xb9, 0xfc, 0xcc, 0xb0, 0x36, 0xdf, 0x64, 0xae, 0xdb, 0x3e, 0x82, 0xa0, + 0x4d, 0xdc, 0xd1, 0x37, 0x48, 0x92, 0x95, 0x51, 0xf9, 0xdd, 0xab, 0x82, 0xf4, 0x8a, 0x85, 0x3f, 0x9a, 0x01, + 0xb5, 0xf2, 0x8c, 0xbb, 0x4a, 0xa5, 0x1b, 0x40, 0x7c, 0xb6 + }; + + public static void Fletcher16() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Fletcher16Context(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomFletcher16.Length) + throw new Exception("Invalid hash length"); + + if(result.Where((t, i) => t != _expectedRandomFletcher16[i]).Any()) + throw new Exception("Invalid hash value"); + } + + public static void Fletcher32() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Fletcher32Context(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomFletcher32.Length) + throw new Exception("Invalid hash length"); + + if(result.Where((t, i) => t != _expectedRandomFletcher32[i]).Any()) + throw new Exception("Invalid hash value"); + } + + public static void Adler32() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Adler32Context(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomAdler32.Length) + throw new Exception("Invalid hash length"); + + if(result.Where((t, i) => t != _expectedRandomAdler32[i]).Any()) + throw new Exception("Invalid hash value"); + } + + public static void Crc16Ccitt() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new CRC16CCITTContext(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomCrc16Ccitt.Length) + throw new Exception("Invalid hash length"); + + if(result.Where((t, i) => t != _expectedRandomCrc16Ccitt[i]).Any()) + throw new Exception("Invalid hash value"); + } + + public static void Crc16() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new CRC16IBMContext(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomCrc16.Length) + throw new Exception("Invalid hash length"); + + if(result.Where((t, i) => t != _expectedRandomCrc16[i]).Any()) + throw new Exception("Invalid hash value"); + } + + public static void Crc32() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Crc32Context(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomCrc32.Length) + throw new Exception("Invalid hash length"); + + if(result.Where((t, i) => t != _expectedRandomCrc32[i]).Any()) + throw new Exception("Invalid hash value"); + } + + public static void Crc64() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Crc64Context(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomCrc64.Length) + throw new Exception("Invalid hash length"); + + if(result.Where((t, i) => t != _expectedRandomCrc64[i]).Any()) + throw new Exception("Invalid hash value"); + } + + public static void Md5() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Md5Context(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomMd5.Length) + throw new Exception("Invalid hash length"); + + if(result.Where((t, i) => t != _expectedRandomMd5[i]).Any()) + throw new Exception("Invalid hash value"); + } + + public static void SpamSum() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new SpamSumContext(); + ctx.Update(data); + string result = ctx.End(); + } + + public static void CliSpamSum() + { + var proc = new Process(); + proc.StartInfo.UseShellExecute = false; + proc.StartInfo.FileName = "/usr/bin/ssdeep"; + proc.StartInfo.CreateNoWindow = true; + proc.StartInfo.ArgumentList.Add(Path.Combine(Program.Folder, "random")); + proc.StartInfo.RedirectStandardOutput = true; + proc.Start(); + proc.StandardOutput.ReadToEnd(); + proc.WaitForExit(); + } + + public static void Sha1() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Sha1Context(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomSha1.Length) + throw new Exception("Invalid hash length"); + + if(result.Where((t, i) => t != _expectedRandomSha1[i]).Any()) + throw new Exception("Invalid hash value"); + } + + public static void Sha256() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Sha256Context(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomSha256.Length) + throw new Exception("Invalid hash length"); + + if(result.Where((t, i) => t != _expectedRandomSha256[i]).Any()) + throw new Exception("Invalid hash value"); + } + + public static void Sha384() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Sha384Context(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomSha384.Length) + throw new Exception("Invalid hash length"); + + if(result.Where((t, i) => t != _expectedRandomSha384[i]).Any()) + throw new Exception("Invalid hash value"); + } + + public static void Sha512() + { + byte[] data = new byte[1048576]; + + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Sha512Context(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomSha512.Length) + throw new Exception("Invalid hash length"); + + if(result.Where((t, i) => t != _expectedRandomSha512[i]).Any()) + throw new Exception("Invalid hash value"); } } \ No newline at end of file diff --git a/AaruBenchmark/Checksums/Aaru6.cs b/AaruBenchmark/Checksums/Aaru6.cs index 81a97eb..c7df10d 100644 --- a/AaruBenchmark/Checksums/Aaru6.cs +++ b/AaruBenchmark/Checksums/Aaru6.cs @@ -4,243 +4,242 @@ using System.Linq; using Aaru.CommonTypes.Interfaces; using Aaru6.Checksums; -namespace AaruBenchmark.Checksums +namespace AaruBenchmark.Checksums; + +public class Aaru6 { - public class Aaru6 + static readonly byte[] _expectedRandomAdler32 = { - static readonly byte[] _expectedRandomAdler32 = - { - 0x37, 0x28, 0xd1, 0x86 - }; + 0x37, 0x28, 0xd1, 0x86 + }; - static readonly byte[] _expectedRandomFletcher16 = - { - 0x33, 0x57 - }; + static readonly byte[] _expectedRandomFletcher16 = + { + 0x33, 0x57 + }; - static readonly byte[] _expectedRandomFletcher32 = - { - 0x21, 0x12, 0x61, 0xF5 - }; + static readonly byte[] _expectedRandomFletcher32 = + { + 0x21, 0x12, 0x61, 0xF5 + }; - static readonly byte[] _expectedRandomCrc16Ccitt = - { - 0x36, 0x40 - }; + static readonly byte[] _expectedRandomCrc16Ccitt = + { + 0x36, 0x40 + }; - static readonly byte[] _expectedRandomCrc16 = - { - 0x2d, 0x6d - }; + static readonly byte[] _expectedRandomCrc16 = + { + 0x2d, 0x6d + }; - static readonly byte[] _expectedRandomCrc32 = - { - 0x2b, 0x6e, 0x68, 0x54 - }; + static readonly byte[] _expectedRandomCrc32 = + { + 0x2b, 0x6e, 0x68, 0x54 + }; - static readonly byte[] _expectedRandomCrc64 = - { - 0xbf, 0x09, 0x99, 0x2c, 0xc5, 0xed, 0xe3, 0x8e - }; + static readonly byte[] _expectedRandomCrc64 = + { + 0xbf, 0x09, 0x99, 0x2c, 0xc5, 0xed, 0xe3, 0x8e + }; - static readonly byte[] _expectedRandomMd5 = - { - 0xd7, 0x8f, 0x0e, 0xec, 0x41, 0x7b, 0xe3, 0x86, 0x21, 0x9b, 0x21, 0xb7, 0x00, 0x04, 0x4b, 0x95 - }; + static readonly byte[] _expectedRandomMd5 = + { + 0xd7, 0x8f, 0x0e, 0xec, 0x41, 0x7b, 0xe3, 0x86, 0x21, 0x9b, 0x21, 0xb7, 0x00, 0x04, 0x4b, 0x95 + }; - static readonly byte[] _expectedRandomSha1 = - { - 0x72, 0x0d, 0x3b, 0x71, 0x7d, 0xe0, 0xc7, 0x4c, 0x77, 0xdd, 0x9c, 0xaa, 0x9e, 0xba, 0x50, 0x60, 0xdc, 0xbd, - 0x28, 0x8d - }; + static readonly byte[] _expectedRandomSha1 = + { + 0x72, 0x0d, 0x3b, 0x71, 0x7d, 0xe0, 0xc7, 0x4c, 0x77, 0xdd, 0x9c, 0xaa, 0x9e, 0xba, 0x50, 0x60, 0xdc, 0xbd, + 0x28, 0x8d + }; - static readonly byte[] _expectedRandomSha256 = - { - 0x4d, 0x1a, 0x6b, 0x8a, 0x54, 0x67, 0x00, 0xc4, 0x8e, 0xda, 0x70, 0xd3, 0x39, 0x1c, 0x8f, 0x15, 0x8a, 0x8d, - 0x12, 0xb2, 0x38, 0x92, 0x89, 0x29, 0x50, 0x47, 0x8c, 0x41, 0x8e, 0x25, 0xcc, 0x39 - }; + static readonly byte[] _expectedRandomSha256 = + { + 0x4d, 0x1a, 0x6b, 0x8a, 0x54, 0x67, 0x00, 0xc4, 0x8e, 0xda, 0x70, 0xd3, 0x39, 0x1c, 0x8f, 0x15, 0x8a, 0x8d, + 0x12, 0xb2, 0x38, 0x92, 0x89, 0x29, 0x50, 0x47, 0x8c, 0x41, 0x8e, 0x25, 0xcc, 0x39 + }; - static readonly byte[] _expectedRandomSha384 = - { - 0xdb, 0x53, 0x0e, 0x17, 0x9b, 0x81, 0xfe, 0x5f, 0x6d, 0x20, 0x41, 0x04, 0x6e, 0x77, 0xd9, 0x85, 0xf2, 0x85, - 0x8a, 0x66, 0xca, 0xd3, 0x8d, 0x1a, 0xd5, 0xac, 0x67, 0xa9, 0x74, 0xe1, 0xef, 0x3f, 0x4d, 0xdf, 0x94, 0x15, - 0x2e, 0xac, 0x2e, 0xfe, 0x16, 0x95, 0x81, 0x54, 0xdc, 0x59, 0xd4, 0xc3 - }; + static readonly byte[] _expectedRandomSha384 = + { + 0xdb, 0x53, 0x0e, 0x17, 0x9b, 0x81, 0xfe, 0x5f, 0x6d, 0x20, 0x41, 0x04, 0x6e, 0x77, 0xd9, 0x85, 0xf2, 0x85, + 0x8a, 0x66, 0xca, 0xd3, 0x8d, 0x1a, 0xd5, 0xac, 0x67, 0xa9, 0x74, 0xe1, 0xef, 0x3f, 0x4d, 0xdf, 0x94, 0x15, + 0x2e, 0xac, 0x2e, 0xfe, 0x16, 0x95, 0x81, 0x54, 0xdc, 0x59, 0xd4, 0xc3 + }; - static readonly byte[] _expectedRandomSha512 = - { - 0x6a, 0x0a, 0x18, 0xc2, 0xad, 0xf8, 0x83, 0xac, 0x58, 0xe6, 0x21, 0x96, 0xdb, 0x8d, 0x3d, 0x0e, 0xb9, 0x87, - 0xd1, 0x49, 0x24, 0x97, 0xdb, 0x15, 0xb9, 0xfc, 0xcc, 0xb0, 0x36, 0xdf, 0x64, 0xae, 0xdb, 0x3e, 0x82, 0xa0, - 0x4d, 0xdc, 0xd1, 0x37, 0x48, 0x92, 0x95, 0x51, 0xf9, 0xdd, 0xab, 0x82, 0xf4, 0x8a, 0x85, 0x3f, 0x9a, 0x01, - 0xb5, 0xf2, 0x8c, 0xbb, 0x4a, 0xa5, 0x1b, 0x40, 0x7c, 0xb6 - }; + static readonly byte[] _expectedRandomSha512 = + { + 0x6a, 0x0a, 0x18, 0xc2, 0xad, 0xf8, 0x83, 0xac, 0x58, 0xe6, 0x21, 0x96, 0xdb, 0x8d, 0x3d, 0x0e, 0xb9, 0x87, + 0xd1, 0x49, 0x24, 0x97, 0xdb, 0x15, 0xb9, 0xfc, 0xcc, 0xb0, 0x36, 0xdf, 0x64, 0xae, 0xdb, 0x3e, 0x82, 0xa0, + 0x4d, 0xdc, 0xd1, 0x37, 0x48, 0x92, 0x95, 0x51, 0xf9, 0xdd, 0xab, 0x82, 0xf4, 0x8a, 0x85, 0x3f, 0x9a, 0x01, + 0xb5, 0xf2, 0x8c, 0xbb, 0x4a, 0xa5, 0x1b, 0x40, 0x7c, 0xb6 + }; - public static void Fletcher16() - { - Native.ForceManaged = true; + public static void Fletcher16() + { + Native.ForceManaged = true; - byte[] data = new byte[1048576]; + byte[] data = new byte[1048576]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Fletcher16Context(); - ctx.Update(data); - byte[] result = ctx.Final(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Fletcher16Context(); + ctx.Update(data); + byte[] result = ctx.Final(); - if(result?.Length != _expectedRandomFletcher16.Length) - throw new Exception("Invalid hash length"); + if(result?.Length != _expectedRandomFletcher16.Length) + throw new Exception("Invalid hash length"); - if(result.Where((t, i) => t != _expectedRandomFletcher16[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(result.Where((t, i) => t != _expectedRandomFletcher16[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Fletcher32() - { - Native.ForceManaged = true; + public static void Fletcher32() + { + Native.ForceManaged = true; - byte[] data = new byte[1048576]; + byte[] data = new byte[1048576]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Fletcher32Context(); - ctx.Update(data); - byte[] result = ctx.Final(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Fletcher32Context(); + ctx.Update(data); + byte[] result = ctx.Final(); - if(result?.Length != _expectedRandomFletcher32.Length) - throw new Exception("Invalid hash length"); + if(result?.Length != _expectedRandomFletcher32.Length) + throw new Exception("Invalid hash length"); - if(result.Where((t, i) => t != _expectedRandomFletcher32[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(result.Where((t, i) => t != _expectedRandomFletcher32[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Adler32() - { - Native.ForceManaged = true; + public static void Adler32() + { + Native.ForceManaged = true; - byte[] data = new byte[1048576]; + byte[] data = new byte[1048576]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Adler32Context(); - ctx.Update(data); - byte[] result = ctx.Final(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Adler32Context(); + ctx.Update(data); + byte[] result = ctx.Final(); - if(result?.Length != _expectedRandomAdler32.Length) - throw new Exception("Invalid hash length"); + if(result?.Length != _expectedRandomAdler32.Length) + throw new Exception("Invalid hash length"); - if(result.Where((t, i) => t != _expectedRandomAdler32[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(result.Where((t, i) => t != _expectedRandomAdler32[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Crc16Ccitt() - { - Native.ForceManaged = true; + public static void Crc16Ccitt() + { + Native.ForceManaged = true; - byte[] data = new byte[1048576]; + byte[] data = new byte[1048576]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new CRC16CCITTContext(); - ctx.Update(data); - byte[] result = ctx.Final(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new CRC16CCITTContext(); + ctx.Update(data); + byte[] result = ctx.Final(); - if(result?.Length != _expectedRandomCrc16Ccitt.Length) - throw new Exception("Invalid hash length"); + if(result?.Length != _expectedRandomCrc16Ccitt.Length) + throw new Exception("Invalid hash length"); - if(result.Where((t, i) => t != _expectedRandomCrc16Ccitt[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(result.Where((t, i) => t != _expectedRandomCrc16Ccitt[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Crc16() - { - Native.ForceManaged = true; + public static void Crc16() + { + Native.ForceManaged = true; - byte[] data = new byte[1048576]; + byte[] data = new byte[1048576]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new CRC16IBMContext(); - ctx.Update(data); - byte[] result = ctx.Final(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new CRC16IBMContext(); + ctx.Update(data); + byte[] result = ctx.Final(); - if(result?.Length != _expectedRandomCrc16.Length) - throw new Exception("Invalid hash length"); + if(result?.Length != _expectedRandomCrc16.Length) + throw new Exception("Invalid hash length"); - if(result.Where((t, i) => t != _expectedRandomCrc16[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(result.Where((t, i) => t != _expectedRandomCrc16[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Crc32() - { - Native.ForceManaged = true; + public static void Crc32() + { + Native.ForceManaged = true; - byte[] data = new byte[1048576]; + byte[] data = new byte[1048576]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Crc32Context(); - ctx.Update(data); - byte[] result = ctx.Final(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Crc32Context(); + ctx.Update(data); + byte[] result = ctx.Final(); - if(result?.Length != _expectedRandomCrc32.Length) - throw new Exception("Invalid hash length"); + if(result?.Length != _expectedRandomCrc32.Length) + throw new Exception("Invalid hash length"); - if(result.Where((t, i) => t != _expectedRandomCrc32[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(result.Where((t, i) => t != _expectedRandomCrc32[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Crc64() - { - Native.ForceManaged = true; + public static void Crc64() + { + Native.ForceManaged = true; - byte[] data = new byte[1048576]; + byte[] data = new byte[1048576]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Crc64Context(); - ctx.Update(data); - byte[] result = ctx.Final(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Crc64Context(); + ctx.Update(data); + byte[] result = ctx.Final(); - if(result?.Length != _expectedRandomCrc64.Length) - throw new Exception("Invalid hash length"); + if(result?.Length != _expectedRandomCrc64.Length) + throw new Exception("Invalid hash length"); - if(result.Where((t, i) => t != _expectedRandomCrc64[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(result.Where((t, i) => t != _expectedRandomCrc64[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void SpamSum() - { - byte[] data = new byte[1048576]; + public static void SpamSum() + { + byte[] data = new byte[1048576]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new SpamSumContext(); - ctx.Update(data); - string result = ctx.End(); - } + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new SpamSumContext(); + ctx.Update(data); + string result = ctx.End(); } } \ No newline at end of file diff --git a/AaruBenchmark/Checksums/AaruNative.cs b/AaruBenchmark/Checksums/AaruNative.cs index 4d938f4..1846194 100644 --- a/AaruBenchmark/Checksums/AaruNative.cs +++ b/AaruBenchmark/Checksums/AaruNative.cs @@ -5,270 +5,269 @@ using System.Runtime.InteropServices; using Aaru.CommonTypes.Interfaces; using Aaru6.Checksums; -namespace AaruBenchmark.Checksums +namespace AaruBenchmark.Checksums; + +public class AaruNative { - public class AaruNative + static readonly byte[] _expectedRandomAdler32 = { - static readonly byte[] _expectedRandomAdler32 = - { - 0x37, 0x28, 0xd1, 0x86 - }; - - static readonly byte[] _expectedRandomFletcher16 = - { - 0x33, 0x57 - }; - - static readonly byte[] _expectedRandomFletcher32 = - { - 0x21, 0x12, 0x61, 0xF5 - }; - - static readonly byte[] _expectedRandomCrc16Ccitt = - { - 0x36, 0x40 - }; - - static readonly byte[] _expectedRandomCrc16 = - { - 0x2d, 0x6d - }; - - static readonly byte[] _expectedRandomCrc32 = - { - 0x2b, 0x6e, 0x68, 0x54 - }; - - static readonly byte[] _expectedRandomCrc64 = - { - 0xbf, 0x09, 0x99, 0x2c, 0xc5, 0xed, 0xe3, 0x8e - }; - - static readonly byte[] _expectedRandomMd5 = - { - 0xd7, 0x8f, 0x0e, 0xec, 0x41, 0x7b, 0xe3, 0x86, 0x21, 0x9b, 0x21, 0xb7, 0x00, 0x04, 0x4b, 0x95 - }; - - static readonly byte[] _expectedRandomSha1 = - { - 0x72, 0x0d, 0x3b, 0x71, 0x7d, 0xe0, 0xc7, 0x4c, 0x77, 0xdd, 0x9c, 0xaa, 0x9e, 0xba, 0x50, 0x60, 0xdc, 0xbd, - 0x28, 0x8d - }; - - static readonly byte[] _expectedRandomSha256 = - { - 0x4d, 0x1a, 0x6b, 0x8a, 0x54, 0x67, 0x00, 0xc4, 0x8e, 0xda, 0x70, 0xd3, 0x39, 0x1c, 0x8f, 0x15, 0x8a, 0x8d, - 0x12, 0xb2, 0x38, 0x92, 0x89, 0x29, 0x50, 0x47, 0x8c, 0x41, 0x8e, 0x25, 0xcc, 0x39 - }; - - static readonly byte[] _expectedRandomSha384 = - { - 0xdb, 0x53, 0x0e, 0x17, 0x9b, 0x81, 0xfe, 0x5f, 0x6d, 0x20, 0x41, 0x04, 0x6e, 0x77, 0xd9, 0x85, 0xf2, 0x85, - 0x8a, 0x66, 0xca, 0xd3, 0x8d, 0x1a, 0xd5, 0xac, 0x67, 0xa9, 0x74, 0xe1, 0xef, 0x3f, 0x4d, 0xdf, 0x94, 0x15, - 0x2e, 0xac, 0x2e, 0xfe, 0x16, 0x95, 0x81, 0x54, 0xdc, 0x59, 0xd4, 0xc3 - }; - - static readonly byte[] _expectedRandomSha512 = - { - 0x6a, 0x0a, 0x18, 0xc2, 0xad, 0xf8, 0x83, 0xac, 0x58, 0xe6, 0x21, 0x96, 0xdb, 0x8d, 0x3d, 0x0e, 0xb9, 0x87, - 0xd1, 0x49, 0x24, 0x97, 0xdb, 0x15, 0xb9, 0xfc, 0xcc, 0xb0, 0x36, 0xdf, 0x64, 0xae, 0xdb, 0x3e, 0x82, 0xa0, - 0x4d, 0xdc, 0xd1, 0x37, 0x48, 0x92, 0x95, 0x51, 0xf9, 0xdd, 0xab, 0x82, 0xf4, 0x8a, 0x85, 0x3f, 0x9a, 0x01, - 0xb5, 0xf2, 0x8c, 0xbb, 0x4a, 0xa5, 0x1b, 0x40, 0x7c, 0xb6 - }; - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern IntPtr spamsum_init(); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int spamsum_update(IntPtr ctx, byte[] data, uint len); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern int spamsum_final(IntPtr ctx, byte[] result); - - [DllImport("libAaru.Checksums.Native", SetLastError = true)] - static extern void spamsum_free(IntPtr ctx); - - public static void Fletcher16() - { - Native.ForceManaged = false; - - byte[] data = new byte[1048576]; - - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Fletcher16Context(); - ctx.Update(data); - byte[] result = ctx.Final(); - - if(result?.Length != _expectedRandomFletcher16.Length) - throw new Exception("Invalid hash length"); + 0x37, 0x28, 0xd1, 0x86 + }; + + static readonly byte[] _expectedRandomFletcher16 = + { + 0x33, 0x57 + }; + + static readonly byte[] _expectedRandomFletcher32 = + { + 0x21, 0x12, 0x61, 0xF5 + }; + + static readonly byte[] _expectedRandomCrc16Ccitt = + { + 0x36, 0x40 + }; + + static readonly byte[] _expectedRandomCrc16 = + { + 0x2d, 0x6d + }; + + static readonly byte[] _expectedRandomCrc32 = + { + 0x2b, 0x6e, 0x68, 0x54 + }; + + static readonly byte[] _expectedRandomCrc64 = + { + 0xbf, 0x09, 0x99, 0x2c, 0xc5, 0xed, 0xe3, 0x8e + }; + + static readonly byte[] _expectedRandomMd5 = + { + 0xd7, 0x8f, 0x0e, 0xec, 0x41, 0x7b, 0xe3, 0x86, 0x21, 0x9b, 0x21, 0xb7, 0x00, 0x04, 0x4b, 0x95 + }; + + static readonly byte[] _expectedRandomSha1 = + { + 0x72, 0x0d, 0x3b, 0x71, 0x7d, 0xe0, 0xc7, 0x4c, 0x77, 0xdd, 0x9c, 0xaa, 0x9e, 0xba, 0x50, 0x60, 0xdc, 0xbd, + 0x28, 0x8d + }; + + static readonly byte[] _expectedRandomSha256 = + { + 0x4d, 0x1a, 0x6b, 0x8a, 0x54, 0x67, 0x00, 0xc4, 0x8e, 0xda, 0x70, 0xd3, 0x39, 0x1c, 0x8f, 0x15, 0x8a, 0x8d, + 0x12, 0xb2, 0x38, 0x92, 0x89, 0x29, 0x50, 0x47, 0x8c, 0x41, 0x8e, 0x25, 0xcc, 0x39 + }; + + static readonly byte[] _expectedRandomSha384 = + { + 0xdb, 0x53, 0x0e, 0x17, 0x9b, 0x81, 0xfe, 0x5f, 0x6d, 0x20, 0x41, 0x04, 0x6e, 0x77, 0xd9, 0x85, 0xf2, 0x85, + 0x8a, 0x66, 0xca, 0xd3, 0x8d, 0x1a, 0xd5, 0xac, 0x67, 0xa9, 0x74, 0xe1, 0xef, 0x3f, 0x4d, 0xdf, 0x94, 0x15, + 0x2e, 0xac, 0x2e, 0xfe, 0x16, 0x95, 0x81, 0x54, 0xdc, 0x59, 0xd4, 0xc3 + }; + + static readonly byte[] _expectedRandomSha512 = + { + 0x6a, 0x0a, 0x18, 0xc2, 0xad, 0xf8, 0x83, 0xac, 0x58, 0xe6, 0x21, 0x96, 0xdb, 0x8d, 0x3d, 0x0e, 0xb9, 0x87, + 0xd1, 0x49, 0x24, 0x97, 0xdb, 0x15, 0xb9, 0xfc, 0xcc, 0xb0, 0x36, 0xdf, 0x64, 0xae, 0xdb, 0x3e, 0x82, 0xa0, + 0x4d, 0xdc, 0xd1, 0x37, 0x48, 0x92, 0x95, 0x51, 0xf9, 0xdd, 0xab, 0x82, 0xf4, 0x8a, 0x85, 0x3f, 0x9a, 0x01, + 0xb5, 0xf2, 0x8c, 0xbb, 0x4a, 0xa5, 0x1b, 0x40, 0x7c, 0xb6 + }; + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern IntPtr spamsum_init(); + + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int spamsum_update(IntPtr ctx, byte[] data, uint len); - if(result.Where((t, i) => t != _expectedRandomFletcher16[i]).Any()) - throw new Exception("Invalid hash value"); - } - - public static void Fletcher32() - { - Native.ForceManaged = false; + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern int spamsum_final(IntPtr ctx, byte[] result); - byte[] data = new byte[1048576]; + [DllImport("libAaru.Checksums.Native", SetLastError = true)] + static extern void spamsum_free(IntPtr ctx); + + public static void Fletcher16() + { + Native.ForceManaged = false; + + byte[] data = new byte[1048576]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Fletcher16Context(); + ctx.Update(data); + byte[] result = ctx.Final(); + + if(result?.Length != _expectedRandomFletcher16.Length) + throw new Exception("Invalid hash length"); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Fletcher32Context(); - ctx.Update(data); - byte[] result = ctx.Final(); + if(result.Where((t, i) => t != _expectedRandomFletcher16[i]).Any()) + throw new Exception("Invalid hash value"); + } - if(result?.Length != _expectedRandomFletcher32.Length) - throw new Exception("Invalid hash length"); + public static void Fletcher32() + { + Native.ForceManaged = false; - if(result.Where((t, i) => t != _expectedRandomFletcher32[i]).Any()) - throw new Exception("Invalid hash value"); - } + byte[] data = new byte[1048576]; - public static void Adler32() - { - Native.ForceManaged = false; + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - byte[] data = new byte[1048576]; + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Fletcher32Context(); + ctx.Update(data); + byte[] result = ctx.Final(); - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + if(result?.Length != _expectedRandomFletcher32.Length) + throw new Exception("Invalid hash length"); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Adler32Context(); - ctx.Update(data); - byte[] result = ctx.Final(); + if(result.Where((t, i) => t != _expectedRandomFletcher32[i]).Any()) + throw new Exception("Invalid hash value"); + } - if(result?.Length != _expectedRandomAdler32.Length) - throw new Exception("Invalid hash length"); + public static void Adler32() + { + Native.ForceManaged = false; - if(result.Where((t, i) => t != _expectedRandomAdler32[i]).Any()) - throw new Exception("Invalid hash value"); - } + byte[] data = new byte[1048576]; - public static void Crc16Ccitt() - { - Native.ForceManaged = false; + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - byte[] data = new byte[1048576]; + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Adler32Context(); + ctx.Update(data); + byte[] result = ctx.Final(); - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + if(result?.Length != _expectedRandomAdler32.Length) + throw new Exception("Invalid hash length"); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new CRC16CCITTContext(); - ctx.Update(data); - byte[] result = ctx.Final(); + if(result.Where((t, i) => t != _expectedRandomAdler32[i]).Any()) + throw new Exception("Invalid hash value"); + } - if(result?.Length != _expectedRandomCrc16Ccitt.Length) - throw new Exception("Invalid hash length"); + public static void Crc16Ccitt() + { + Native.ForceManaged = false; - if(result.Where((t, i) => t != _expectedRandomCrc16Ccitt[i]).Any()) - throw new Exception("Invalid hash value"); - } + byte[] data = new byte[1048576]; - public static void Crc16() - { - Native.ForceManaged = false; + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - byte[] data = new byte[1048576]; + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new CRC16CCITTContext(); + ctx.Update(data); + byte[] result = ctx.Final(); - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + if(result?.Length != _expectedRandomCrc16Ccitt.Length) + throw new Exception("Invalid hash length"); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new CRC16IBMContext(); - ctx.Update(data); - byte[] result = ctx.Final(); + if(result.Where((t, i) => t != _expectedRandomCrc16Ccitt[i]).Any()) + throw new Exception("Invalid hash value"); + } - if(result?.Length != _expectedRandomCrc16.Length) - throw new Exception("Invalid hash length"); + public static void Crc16() + { + Native.ForceManaged = false; - if(result.Where((t, i) => t != _expectedRandomCrc16[i]).Any()) - throw new Exception("Invalid hash value"); - } + byte[] data = new byte[1048576]; - public static void Crc32() - { - Native.ForceManaged = false; + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - byte[] data = new byte[1048576]; + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new CRC16IBMContext(); + ctx.Update(data); + byte[] result = ctx.Final(); - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + if(result?.Length != _expectedRandomCrc16.Length) + throw new Exception("Invalid hash length"); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Crc32Context(); - ctx.Update(data); - byte[] result = ctx.Final(); + if(result.Where((t, i) => t != _expectedRandomCrc16[i]).Any()) + throw new Exception("Invalid hash value"); + } - if(result?.Length != _expectedRandomCrc32.Length) - throw new Exception("Invalid hash length"); + public static void Crc32() + { + Native.ForceManaged = false; - if(result.Where((t, i) => t != _expectedRandomCrc32[i]).Any()) - throw new Exception("Invalid hash value"); - } + byte[] data = new byte[1048576]; - public static void Crc64() - { - Native.ForceManaged = false; + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - byte[] data = new byte[1048576]; + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Crc32Context(); + ctx.Update(data); + byte[] result = ctx.Final(); - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + if(result?.Length != _expectedRandomCrc32.Length) + throw new Exception("Invalid hash length"); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); - IChecksum ctx = new Crc64Context(); - ctx.Update(data); - byte[] result = ctx.Final(); + if(result.Where((t, i) => t != _expectedRandomCrc32[i]).Any()) + throw new Exception("Invalid hash value"); + } - if(result?.Length != _expectedRandomCrc64.Length) - throw new Exception("Invalid hash length"); + public static void Crc64() + { + Native.ForceManaged = false; - if(result.Where((t, i) => t != _expectedRandomCrc64[i]).Any()) - throw new Exception("Invalid hash value"); - } + byte[] data = new byte[1048576]; - public static void SpamSum() - { - byte[] data = new byte[1048576]; - byte[] hash = new byte[256]; + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); + IChecksum ctx = new Crc64Context(); + ctx.Update(data); + byte[] result = ctx.Final(); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); + if(result?.Length != _expectedRandomCrc64.Length) + throw new Exception("Invalid hash length"); - IntPtr ctx = spamsum_init(); + if(result.Where((t, i) => t != _expectedRandomCrc64[i]).Any()) + throw new Exception("Invalid hash value"); + } - if(ctx == IntPtr.Zero) - throw new Exception("Could not initialize digest"); + public static void SpamSum() + { + byte[] data = new byte[1048576]; + byte[] hash = new byte[256]; - int ret = spamsum_update(ctx, data, (uint)data.Length); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - if(ret != 0) - throw new Exception("Could not digest block"); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); - ret = spamsum_final(ctx, hash); + IntPtr ctx = spamsum_init(); - if(ret != 0) - throw new Exception("Could not finalize block"); + if(ctx == IntPtr.Zero) + throw new Exception("Could not initialize digest"); - spamsum_free(ctx); - } + int ret = spamsum_update(ctx, data, (uint)data.Length); + + if(ret != 0) + throw new Exception("Could not digest block"); + + ret = spamsum_final(ctx, hash); + + if(ret != 0) + throw new Exception("Could not finalize block"); + + spamsum_free(ctx); } } \ No newline at end of file diff --git a/AaruBenchmark/Checksums/OpenSsl.cs b/AaruBenchmark/Checksums/OpenSsl.cs index 59fa771..1a9b14c 100644 --- a/AaruBenchmark/Checksums/OpenSsl.cs +++ b/AaruBenchmark/Checksums/OpenSsl.cs @@ -3,245 +3,244 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; -namespace AaruBenchmark.Checksums +namespace AaruBenchmark.Checksums; + +public class OpenSsl { - public class OpenSsl + static readonly byte[] _expectedRandomMd5 = { - static readonly byte[] _expectedRandomMd5 = - { - 0xd7, 0x8f, 0x0e, 0xec, 0x41, 0x7b, 0xe3, 0x86, 0x21, 0x9b, 0x21, 0xb7, 0x00, 0x04, 0x4b, 0x95 - }; + 0xd7, 0x8f, 0x0e, 0xec, 0x41, 0x7b, 0xe3, 0x86, 0x21, 0x9b, 0x21, 0xb7, 0x00, 0x04, 0x4b, 0x95 + }; - static readonly byte[] _expectedRandomSha1 = - { - 0x72, 0x0d, 0x3b, 0x71, 0x7d, 0xe0, 0xc7, 0x4c, 0x77, 0xdd, 0x9c, 0xaa, 0x9e, 0xba, 0x50, 0x60, 0xdc, 0xbd, - 0x28, 0x8d - }; + static readonly byte[] _expectedRandomSha1 = + { + 0x72, 0x0d, 0x3b, 0x71, 0x7d, 0xe0, 0xc7, 0x4c, 0x77, 0xdd, 0x9c, 0xaa, 0x9e, 0xba, 0x50, 0x60, 0xdc, 0xbd, + 0x28, 0x8d + }; - static readonly byte[] _expectedRandomSha256 = - { - 0x4d, 0x1a, 0x6b, 0x8a, 0x54, 0x67, 0x00, 0xc4, 0x8e, 0xda, 0x70, 0xd3, 0x39, 0x1c, 0x8f, 0x15, 0x8a, 0x8d, - 0x12, 0xb2, 0x38, 0x92, 0x89, 0x29, 0x50, 0x47, 0x8c, 0x41, 0x8e, 0x25, 0xcc, 0x39 - }; + static readonly byte[] _expectedRandomSha256 = + { + 0x4d, 0x1a, 0x6b, 0x8a, 0x54, 0x67, 0x00, 0xc4, 0x8e, 0xda, 0x70, 0xd3, 0x39, 0x1c, 0x8f, 0x15, 0x8a, 0x8d, + 0x12, 0xb2, 0x38, 0x92, 0x89, 0x29, 0x50, 0x47, 0x8c, 0x41, 0x8e, 0x25, 0xcc, 0x39 + }; - static readonly byte[] _expectedRandomSha384 = - { - 0xdb, 0x53, 0x0e, 0x17, 0x9b, 0x81, 0xfe, 0x5f, 0x6d, 0x20, 0x41, 0x04, 0x6e, 0x77, 0xd9, 0x85, 0xf2, 0x85, - 0x8a, 0x66, 0xca, 0xd3, 0x8d, 0x1a, 0xd5, 0xac, 0x67, 0xa9, 0x74, 0xe1, 0xef, 0x3f, 0x4d, 0xdf, 0x94, 0x15, - 0x2e, 0xac, 0x2e, 0xfe, 0x16, 0x95, 0x81, 0x54, 0xdc, 0x59, 0xd4, 0xc3 - }; + static readonly byte[] _expectedRandomSha384 = + { + 0xdb, 0x53, 0x0e, 0x17, 0x9b, 0x81, 0xfe, 0x5f, 0x6d, 0x20, 0x41, 0x04, 0x6e, 0x77, 0xd9, 0x85, 0xf2, 0x85, + 0x8a, 0x66, 0xca, 0xd3, 0x8d, 0x1a, 0xd5, 0xac, 0x67, 0xa9, 0x74, 0xe1, 0xef, 0x3f, 0x4d, 0xdf, 0x94, 0x15, + 0x2e, 0xac, 0x2e, 0xfe, 0x16, 0x95, 0x81, 0x54, 0xdc, 0x59, 0xd4, 0xc3 + }; - static readonly byte[] _expectedRandomSha512 = - { - 0x6a, 0x0a, 0x18, 0xc2, 0xad, 0xf8, 0x83, 0xac, 0x58, 0xe6, 0x21, 0x96, 0xdb, 0x8d, 0x3d, 0x0e, 0xb9, 0x87, - 0xd1, 0x49, 0x24, 0x97, 0xdb, 0x15, 0xb9, 0xfc, 0xcc, 0xb0, 0x36, 0xdf, 0x64, 0xae, 0xdb, 0x3e, 0x82, 0xa0, - 0x4d, 0xdc, 0xd1, 0x37, 0x48, 0x92, 0x95, 0x51, 0xf9, 0xdd, 0xab, 0x82, 0xf4, 0x8a, 0x85, 0x3f, 0x9a, 0x01, - 0xb5, 0xf2, 0x8c, 0xbb, 0x4a, 0xa5, 0x1b, 0x40, 0x7c, 0xb6 - }; + static readonly byte[] _expectedRandomSha512 = + { + 0x6a, 0x0a, 0x18, 0xc2, 0xad, 0xf8, 0x83, 0xac, 0x58, 0xe6, 0x21, 0x96, 0xdb, 0x8d, 0x3d, 0x0e, 0xb9, 0x87, + 0xd1, 0x49, 0x24, 0x97, 0xdb, 0x15, 0xb9, 0xfc, 0xcc, 0xb0, 0x36, 0xdf, 0x64, 0xae, 0xdb, 0x3e, 0x82, 0xa0, + 0x4d, 0xdc, 0xd1, 0x37, 0x48, 0x92, 0x95, 0x51, 0xf9, 0xdd, 0xab, 0x82, 0xf4, 0x8a, 0x85, 0x3f, 0x9a, 0x01, + 0xb5, 0xf2, 0x8c, 0xbb, 0x4a, 0xa5, 0x1b, 0x40, 0x7c, 0xb6 + }; - [DllImport("libssl", SetLastError = true)] - static extern int EVP_DigestInit(IntPtr ctx, IntPtr type); + [DllImport("libssl", SetLastError = true)] + static extern int EVP_DigestInit(IntPtr ctx, IntPtr type); - [DllImport("libssl", SetLastError = true)] - static extern IntPtr EVP_MD_CTX_new(); + [DllImport("libssl", SetLastError = true)] + static extern IntPtr EVP_MD_CTX_new(); - [DllImport("libssl", SetLastError = true)] - static extern IntPtr EVP_md5(); + [DllImport("libssl", SetLastError = true)] + static extern IntPtr EVP_md5(); - [DllImport("libssl", SetLastError = true)] - static extern IntPtr EVP_sha1(); + [DllImport("libssl", SetLastError = true)] + static extern IntPtr EVP_sha1(); - [DllImport("libssl", SetLastError = true)] - static extern IntPtr EVP_sha256(); + [DllImport("libssl", SetLastError = true)] + static extern IntPtr EVP_sha256(); - [DllImport("libssl", SetLastError = true)] - static extern IntPtr EVP_sha384(); + [DllImport("libssl", SetLastError = true)] + static extern IntPtr EVP_sha384(); - [DllImport("libssl", SetLastError = true)] - static extern IntPtr EVP_sha512(); + [DllImport("libssl", SetLastError = true)] + static extern IntPtr EVP_sha512(); - [DllImport("libssl", SetLastError = true)] - static extern int EVP_DigestUpdate(IntPtr ctx, byte[] d, ulong cnt); + [DllImport("libssl", SetLastError = true)] + static extern int EVP_DigestUpdate(IntPtr ctx, byte[] d, ulong cnt); - [DllImport("libssl", SetLastError = true)] - static extern int EVP_DigestFinal(IntPtr ctx, byte[] md, ref uint s); + [DllImport("libssl", SetLastError = true)] + static extern int EVP_DigestFinal(IntPtr ctx, byte[] md, ref uint s); - [DllImport("libssl", SetLastError = true)] - static extern void EVP_MD_CTX_free(IntPtr ctx); + [DllImport("libssl", SetLastError = true)] + static extern void EVP_MD_CTX_free(IntPtr ctx); - public static void Md5() - { - byte[] data = new byte[1048576]; - uint s = 0; - byte[] hash = new byte[16]; + public static void Md5() + { + byte[] data = new byte[1048576]; + uint s = 0; + byte[] hash = new byte[16]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); - IntPtr md = EVP_md5(); - IntPtr ctx = EVP_MD_CTX_new(); - int ret = EVP_DigestInit(ctx, md); + IntPtr md = EVP_md5(); + IntPtr ctx = EVP_MD_CTX_new(); + int ret = EVP_DigestInit(ctx, md); - if(ret != 1) - throw new Exception("Could not initialize digest"); + if(ret != 1) + throw new Exception("Could not initialize digest"); - ret = EVP_DigestUpdate(ctx, data, (ulong)data.Length); + ret = EVP_DigestUpdate(ctx, data, (ulong)data.Length); - if(ret != 1) - throw new Exception("Could not digest block"); + if(ret != 1) + throw new Exception("Could not digest block"); - ret = EVP_DigestFinal(ctx, hash, ref s); + ret = EVP_DigestFinal(ctx, hash, ref s); - if(ret != 1) - throw new Exception("Could not finalize hash"); + if(ret != 1) + throw new Exception("Could not finalize hash"); - EVP_MD_CTX_free(ctx); + EVP_MD_CTX_free(ctx); - if(hash.Where((t, i) => t != _expectedRandomMd5[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(hash.Where((t, i) => t != _expectedRandomMd5[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Sha1() - { - byte[] data = new byte[1048576]; - uint s = 0; - byte[] hash = new byte[20]; + public static void Sha1() + { + byte[] data = new byte[1048576]; + uint s = 0; + byte[] hash = new byte[20]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); - IntPtr md = EVP_sha1(); - IntPtr ctx = EVP_MD_CTX_new(); - int ret = EVP_DigestInit(ctx, md); + IntPtr md = EVP_sha1(); + IntPtr ctx = EVP_MD_CTX_new(); + int ret = EVP_DigestInit(ctx, md); - if(ret != 1) - throw new Exception("Could not initialize digest"); + if(ret != 1) + throw new Exception("Could not initialize digest"); - ret = EVP_DigestUpdate(ctx, data, (ulong)data.Length); + ret = EVP_DigestUpdate(ctx, data, (ulong)data.Length); - if(ret != 1) - throw new Exception("Could not digest block"); + if(ret != 1) + throw new Exception("Could not digest block"); - ret = EVP_DigestFinal(ctx, hash, ref s); + ret = EVP_DigestFinal(ctx, hash, ref s); - if(ret != 1) - throw new Exception("Could not finalize hash"); + if(ret != 1) + throw new Exception("Could not finalize hash"); - EVP_MD_CTX_free(ctx); + EVP_MD_CTX_free(ctx); - if(hash.Where((t, i) => t != _expectedRandomSha1[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(hash.Where((t, i) => t != _expectedRandomSha1[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Sha256() - { - byte[] data = new byte[1048576]; - uint s = 0; - byte[] hash = new byte[32]; + public static void Sha256() + { + byte[] data = new byte[1048576]; + uint s = 0; + byte[] hash = new byte[32]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); - IntPtr md = EVP_sha256(); - IntPtr ctx = EVP_MD_CTX_new(); - int ret = EVP_DigestInit(ctx, md); + IntPtr md = EVP_sha256(); + IntPtr ctx = EVP_MD_CTX_new(); + int ret = EVP_DigestInit(ctx, md); - if(ret != 1) - throw new Exception("Could not initialize digest"); + if(ret != 1) + throw new Exception("Could not initialize digest"); - ret = EVP_DigestUpdate(ctx, data, (ulong)data.Length); + ret = EVP_DigestUpdate(ctx, data, (ulong)data.Length); - if(ret != 1) - throw new Exception("Could not digest block"); + if(ret != 1) + throw new Exception("Could not digest block"); - ret = EVP_DigestFinal(ctx, hash, ref s); + ret = EVP_DigestFinal(ctx, hash, ref s); - if(ret != 1) - throw new Exception("Could not finalize hash"); + if(ret != 1) + throw new Exception("Could not finalize hash"); - EVP_MD_CTX_free(ctx); + EVP_MD_CTX_free(ctx); - if(hash.Where((t, i) => t != _expectedRandomSha256[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(hash.Where((t, i) => t != _expectedRandomSha256[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Sha384() - { - byte[] data = new byte[1048576]; - uint s = 0; - byte[] hash = new byte[48]; + public static void Sha384() + { + byte[] data = new byte[1048576]; + uint s = 0; + byte[] hash = new byte[48]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); - IntPtr md = EVP_sha384(); - IntPtr ctx = EVP_MD_CTX_new(); - int ret = EVP_DigestInit(ctx, md); + IntPtr md = EVP_sha384(); + IntPtr ctx = EVP_MD_CTX_new(); + int ret = EVP_DigestInit(ctx, md); - if(ret != 1) - throw new Exception("Could not initialize digest"); + if(ret != 1) + throw new Exception("Could not initialize digest"); - ret = EVP_DigestUpdate(ctx, data, (ulong)data.Length); + ret = EVP_DigestUpdate(ctx, data, (ulong)data.Length); - if(ret != 1) - throw new Exception("Could not digest block"); + if(ret != 1) + throw new Exception("Could not digest block"); - ret = EVP_DigestFinal(ctx, hash, ref s); + ret = EVP_DigestFinal(ctx, hash, ref s); - if(ret != 1) - throw new Exception("Could not finalize hash"); + if(ret != 1) + throw new Exception("Could not finalize hash"); - EVP_MD_CTX_free(ctx); + EVP_MD_CTX_free(ctx); - if(hash.Where((t, i) => t != _expectedRandomSha384[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(hash.Where((t, i) => t != _expectedRandomSha384[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Sha512() - { - byte[] data = new byte[1048576]; - uint s = 0; - byte[] hash = new byte[64]; + public static void Sha512() + { + byte[] data = new byte[1048576]; + uint s = 0; + byte[] hash = new byte[64]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); - IntPtr md = EVP_sha512(); - IntPtr ctx = EVP_MD_CTX_new(); - int ret = EVP_DigestInit(ctx, md); + IntPtr md = EVP_sha512(); + IntPtr ctx = EVP_MD_CTX_new(); + int ret = EVP_DigestInit(ctx, md); - if(ret != 1) - throw new Exception("Could not initialize digest"); + if(ret != 1) + throw new Exception("Could not initialize digest"); - ret = EVP_DigestUpdate(ctx, data, (ulong)data.Length); + ret = EVP_DigestUpdate(ctx, data, (ulong)data.Length); - if(ret != 1) - throw new Exception("Could not digest block"); + if(ret != 1) + throw new Exception("Could not digest block"); - ret = EVP_DigestFinal(ctx, hash, ref s); + ret = EVP_DigestFinal(ctx, hash, ref s); - if(ret != 1) - throw new Exception("Could not finalize hash"); + if(ret != 1) + throw new Exception("Could not finalize hash"); - EVP_MD_CTX_free(ctx); + EVP_MD_CTX_free(ctx); - if(hash.Where((t, i) => t != _expectedRandomSha512[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(hash.Where((t, i) => t != _expectedRandomSha512[i]).Any()) + throw new Exception("Invalid hash value"); } } \ No newline at end of file diff --git a/AaruBenchmark/Checksums/RHash.cs b/AaruBenchmark/Checksums/RHash.cs index 7dfc7b8..a560e12 100644 --- a/AaruBenchmark/Checksums/RHash.cs +++ b/AaruBenchmark/Checksums/RHash.cs @@ -3,264 +3,263 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; -namespace AaruBenchmark.Checksums +namespace AaruBenchmark.Checksums; + +public class RHash { - public class RHash + const uint RHASH_CRC32 = 0x01; + const uint RHASH_MD5 = 0x04; + const uint RHASH_SHA1 = 0x08; + const uint RHASH_SHA256 = 0x20000; + const uint RHASH_SHA384 = 0x40000; + const uint RHASH_SHA512 = 0x80000; + const uint RHASH_CRC32C = 0x4000000; + static readonly byte[] _expectedRandomCrc32 = { - const uint RHASH_CRC32 = 0x01; - const uint RHASH_MD5 = 0x04; - const uint RHASH_SHA1 = 0x08; - const uint RHASH_SHA256 = 0x20000; - const uint RHASH_SHA384 = 0x40000; - const uint RHASH_SHA512 = 0x80000; - const uint RHASH_CRC32C = 0x4000000; - static readonly byte[] _expectedRandomCrc32 = - { - 0x2b, 0x6e, 0x68, 0x54 - }; - static readonly byte[] _expectedRandomMd5 = - { - 0xd7, 0x8f, 0x0e, 0xec, 0x41, 0x7b, 0xe3, 0x86, 0x21, 0x9b, 0x21, 0xb7, 0x00, 0x04, 0x4b, 0x95 - }; + 0x2b, 0x6e, 0x68, 0x54 + }; + static readonly byte[] _expectedRandomMd5 = + { + 0xd7, 0x8f, 0x0e, 0xec, 0x41, 0x7b, 0xe3, 0x86, 0x21, 0x9b, 0x21, 0xb7, 0x00, 0x04, 0x4b, 0x95 + }; - static readonly byte[] _expectedRandomSha1 = - { - 0x72, 0x0d, 0x3b, 0x71, 0x7d, 0xe0, 0xc7, 0x4c, 0x77, 0xdd, 0x9c, 0xaa, 0x9e, 0xba, 0x50, 0x60, 0xdc, 0xbd, - 0x28, 0x8d - }; + static readonly byte[] _expectedRandomSha1 = + { + 0x72, 0x0d, 0x3b, 0x71, 0x7d, 0xe0, 0xc7, 0x4c, 0x77, 0xdd, 0x9c, 0xaa, 0x9e, 0xba, 0x50, 0x60, 0xdc, 0xbd, + 0x28, 0x8d + }; - static readonly byte[] _expectedRandomSha256 = - { - 0x4d, 0x1a, 0x6b, 0x8a, 0x54, 0x67, 0x00, 0xc4, 0x8e, 0xda, 0x70, 0xd3, 0x39, 0x1c, 0x8f, 0x15, 0x8a, 0x8d, - 0x12, 0xb2, 0x38, 0x92, 0x89, 0x29, 0x50, 0x47, 0x8c, 0x41, 0x8e, 0x25, 0xcc, 0x39 - }; + static readonly byte[] _expectedRandomSha256 = + { + 0x4d, 0x1a, 0x6b, 0x8a, 0x54, 0x67, 0x00, 0xc4, 0x8e, 0xda, 0x70, 0xd3, 0x39, 0x1c, 0x8f, 0x15, 0x8a, 0x8d, + 0x12, 0xb2, 0x38, 0x92, 0x89, 0x29, 0x50, 0x47, 0x8c, 0x41, 0x8e, 0x25, 0xcc, 0x39 + }; - static readonly byte[] _expectedRandomSha384 = - { - 0xdb, 0x53, 0x0e, 0x17, 0x9b, 0x81, 0xfe, 0x5f, 0x6d, 0x20, 0x41, 0x04, 0x6e, 0x77, 0xd9, 0x85, 0xf2, 0x85, - 0x8a, 0x66, 0xca, 0xd3, 0x8d, 0x1a, 0xd5, 0xac, 0x67, 0xa9, 0x74, 0xe1, 0xef, 0x3f, 0x4d, 0xdf, 0x94, 0x15, - 0x2e, 0xac, 0x2e, 0xfe, 0x16, 0x95, 0x81, 0x54, 0xdc, 0x59, 0xd4, 0xc3 - }; + static readonly byte[] _expectedRandomSha384 = + { + 0xdb, 0x53, 0x0e, 0x17, 0x9b, 0x81, 0xfe, 0x5f, 0x6d, 0x20, 0x41, 0x04, 0x6e, 0x77, 0xd9, 0x85, 0xf2, 0x85, + 0x8a, 0x66, 0xca, 0xd3, 0x8d, 0x1a, 0xd5, 0xac, 0x67, 0xa9, 0x74, 0xe1, 0xef, 0x3f, 0x4d, 0xdf, 0x94, 0x15, + 0x2e, 0xac, 0x2e, 0xfe, 0x16, 0x95, 0x81, 0x54, 0xdc, 0x59, 0xd4, 0xc3 + }; - static readonly byte[] _expectedRandomSha512 = - { - 0x6a, 0x0a, 0x18, 0xc2, 0xad, 0xf8, 0x83, 0xac, 0x58, 0xe6, 0x21, 0x96, 0xdb, 0x8d, 0x3d, 0x0e, 0xb9, 0x87, - 0xd1, 0x49, 0x24, 0x97, 0xdb, 0x15, 0xb9, 0xfc, 0xcc, 0xb0, 0x36, 0xdf, 0x64, 0xae, 0xdb, 0x3e, 0x82, 0xa0, - 0x4d, 0xdc, 0xd1, 0x37, 0x48, 0x92, 0x95, 0x51, 0xf9, 0xdd, 0xab, 0x82, 0xf4, 0x8a, 0x85, 0x3f, 0x9a, 0x01, - 0xb5, 0xf2, 0x8c, 0xbb, 0x4a, 0xa5, 0x1b, 0x40, 0x7c, 0xb6 - }; + static readonly byte[] _expectedRandomSha512 = + { + 0x6a, 0x0a, 0x18, 0xc2, 0xad, 0xf8, 0x83, 0xac, 0x58, 0xe6, 0x21, 0x96, 0xdb, 0x8d, 0x3d, 0x0e, 0xb9, 0x87, + 0xd1, 0x49, 0x24, 0x97, 0xdb, 0x15, 0xb9, 0xfc, 0xcc, 0xb0, 0x36, 0xdf, 0x64, 0xae, 0xdb, 0x3e, 0x82, 0xa0, + 0x4d, 0xdc, 0xd1, 0x37, 0x48, 0x92, 0x95, 0x51, 0xf9, 0xdd, 0xab, 0x82, 0xf4, 0x8a, 0x85, 0x3f, 0x9a, 0x01, + 0xb5, 0xf2, 0x8c, 0xbb, 0x4a, 0xa5, 0x1b, 0x40, 0x7c, 0xb6 + }; - [DllImport("librhash", SetLastError = true)] - static extern void rhash_library_init(); + [DllImport("librhash", SetLastError = true)] + static extern void rhash_library_init(); - [DllImport("librhash", SetLastError = true)] - static extern IntPtr rhash_init(uint hash_id); + [DllImport("librhash", SetLastError = true)] + static extern IntPtr rhash_init(uint hash_id); - [DllImport("librhash", SetLastError = true)] - static extern int rhash_update(IntPtr ctx, byte[] message, ulong length); + [DllImport("librhash", SetLastError = true)] + static extern int rhash_update(IntPtr ctx, byte[] message, ulong length); - [DllImport("librhash", SetLastError = true)] - static extern int rhash_final(IntPtr ctx, byte[] first_result); + [DllImport("librhash", SetLastError = true)] + static extern int rhash_final(IntPtr ctx, byte[] first_result); - [DllImport("librhash", SetLastError = true)] - static extern void rhash_free(IntPtr ctx); + [DllImport("librhash", SetLastError = true)] + static extern void rhash_free(IntPtr ctx); - public static void Crc32() - { - byte[] data = new byte[1048576]; - byte[] hash = new byte[4]; + public static void Crc32() + { + byte[] data = new byte[1048576]; + byte[] hash = new byte[4]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); - rhash_library_init(); - IntPtr ctx = rhash_init(RHASH_CRC32); + rhash_library_init(); + IntPtr ctx = rhash_init(RHASH_CRC32); - if(ctx == IntPtr.Zero) - throw new Exception("Could not initialize digest"); + if(ctx == IntPtr.Zero) + throw new Exception("Could not initialize digest"); - int ret = rhash_update(ctx, data, (ulong)data.Length); + int ret = rhash_update(ctx, data, (ulong)data.Length); - if(ret != 0) - throw new Exception("Could not digest block"); + if(ret != 0) + throw new Exception("Could not digest block"); - ret = rhash_final(ctx, hash); + ret = rhash_final(ctx, hash); - if(ret != 0) - throw new Exception("Could not finalize hash"); + if(ret != 0) + throw new Exception("Could not finalize hash"); - rhash_free(ctx); + rhash_free(ctx); - if(hash.Where((t, i) => t != _expectedRandomCrc32[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(hash.Where((t, i) => t != _expectedRandomCrc32[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Md5() - { - byte[] data = new byte[1048576]; - byte[] hash = new byte[16]; + public static void Md5() + { + byte[] data = new byte[1048576]; + byte[] hash = new byte[16]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); - rhash_library_init(); - IntPtr ctx = rhash_init(RHASH_MD5); + rhash_library_init(); + IntPtr ctx = rhash_init(RHASH_MD5); - if(ctx == IntPtr.Zero) - throw new Exception("Could not initialize digest"); + if(ctx == IntPtr.Zero) + throw new Exception("Could not initialize digest"); - int ret = rhash_update(ctx, data, (ulong)data.Length); + int ret = rhash_update(ctx, data, (ulong)data.Length); - if(ret != 0) - throw new Exception("Could not digest block"); + if(ret != 0) + throw new Exception("Could not digest block"); - ret = rhash_final(ctx, hash); + ret = rhash_final(ctx, hash); - if(ret != 0) - throw new Exception("Could not finalize hash"); + if(ret != 0) + throw new Exception("Could not finalize hash"); - rhash_free(ctx); + rhash_free(ctx); - if(hash.Where((t, i) => t != _expectedRandomMd5[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(hash.Where((t, i) => t != _expectedRandomMd5[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Sha1() - { - byte[] data = new byte[1048576]; - byte[] hash = new byte[20]; + public static void Sha1() + { + byte[] data = new byte[1048576]; + byte[] hash = new byte[20]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); - rhash_library_init(); - IntPtr ctx = rhash_init(RHASH_SHA1); + rhash_library_init(); + IntPtr ctx = rhash_init(RHASH_SHA1); - if(ctx == IntPtr.Zero) - throw new Exception("Could not initialize digest"); + if(ctx == IntPtr.Zero) + throw new Exception("Could not initialize digest"); - int ret = rhash_update(ctx, data, (ulong)data.Length); + int ret = rhash_update(ctx, data, (ulong)data.Length); - if(ret != 0) - throw new Exception("Could not digest block"); + if(ret != 0) + throw new Exception("Could not digest block"); - ret = rhash_final(ctx, hash); + ret = rhash_final(ctx, hash); - if(ret != 0) - throw new Exception("Could not finalize hash"); + if(ret != 0) + throw new Exception("Could not finalize hash"); - rhash_free(ctx); + rhash_free(ctx); - if(hash.Where((t, i) => t != _expectedRandomSha1[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(hash.Where((t, i) => t != _expectedRandomSha1[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Sha256() - { - byte[] data = new byte[1048576]; - byte[] hash = new byte[32]; + public static void Sha256() + { + byte[] data = new byte[1048576]; + byte[] hash = new byte[32]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); - rhash_library_init(); - IntPtr ctx = rhash_init(RHASH_SHA256); + rhash_library_init(); + IntPtr ctx = rhash_init(RHASH_SHA256); - if(ctx == IntPtr.Zero) - throw new Exception("Could not initialize digest"); + if(ctx == IntPtr.Zero) + throw new Exception("Could not initialize digest"); - int ret = rhash_update(ctx, data, (ulong)data.Length); + int ret = rhash_update(ctx, data, (ulong)data.Length); - if(ret != 0) - throw new Exception("Could not digest block"); + if(ret != 0) + throw new Exception("Could not digest block"); - ret = rhash_final(ctx, hash); + ret = rhash_final(ctx, hash); - if(ret != 0) - throw new Exception("Could not finalize hash"); + if(ret != 0) + throw new Exception("Could not finalize hash"); - rhash_free(ctx); + rhash_free(ctx); - if(hash.Where((t, i) => t != _expectedRandomSha256[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(hash.Where((t, i) => t != _expectedRandomSha256[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Sha384() - { - byte[] data = new byte[1048576]; - byte[] hash = new byte[48]; + public static void Sha384() + { + byte[] data = new byte[1048576]; + byte[] hash = new byte[48]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); - rhash_library_init(); - IntPtr ctx = rhash_init(RHASH_SHA384); + rhash_library_init(); + IntPtr ctx = rhash_init(RHASH_SHA384); - if(ctx == IntPtr.Zero) - throw new Exception("Could not initialize digest"); + if(ctx == IntPtr.Zero) + throw new Exception("Could not initialize digest"); - int ret = rhash_update(ctx, data, (ulong)data.Length); + int ret = rhash_update(ctx, data, (ulong)data.Length); - if(ret != 0) - throw new Exception("Could not digest block"); + if(ret != 0) + throw new Exception("Could not digest block"); - ret = rhash_final(ctx, hash); + ret = rhash_final(ctx, hash); - if(ret != 0) - throw new Exception("Could not finalize hash"); + if(ret != 0) + throw new Exception("Could not finalize hash"); - rhash_free(ctx); + rhash_free(ctx); - if(hash.Where((t, i) => t != _expectedRandomSha384[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(hash.Where((t, i) => t != _expectedRandomSha384[i]).Any()) + throw new Exception("Invalid hash value"); + } - public static void Sha512() - { - byte[] data = new byte[1048576]; - byte[] hash = new byte[64]; + public static void Sha512() + { + byte[] data = new byte[1048576]; + byte[] hash = new byte[64]; - var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); + var fs = new FileStream(Path.Combine(Program.Folder, "random"), FileMode.Open, FileAccess.Read); - fs.Read(data, 0, 1048576); - fs.Close(); - fs.Dispose(); + fs.Read(data, 0, 1048576); + fs.Close(); + fs.Dispose(); - rhash_library_init(); - IntPtr ctx = rhash_init(RHASH_SHA512); + rhash_library_init(); + IntPtr ctx = rhash_init(RHASH_SHA512); - if(ctx == IntPtr.Zero) - throw new Exception("Could not initialize digest"); + if(ctx == IntPtr.Zero) + throw new Exception("Could not initialize digest"); - int ret = rhash_update(ctx, data, (ulong)data.Length); + int ret = rhash_update(ctx, data, (ulong)data.Length); - if(ret != 0) - throw new Exception("Could not digest block"); + if(ret != 0) + throw new Exception("Could not digest block"); - ret = rhash_final(ctx, hash); + ret = rhash_final(ctx, hash); - if(ret != 0) - throw new Exception("Could not finalize hash"); + if(ret != 0) + throw new Exception("Could not finalize hash"); - rhash_free(ctx); + rhash_free(ctx); - if(hash.Where((t, i) => t != _expectedRandomSha512[i]).Any()) - throw new Exception("Invalid hash value"); - } + if(hash.Where((t, i) => t != _expectedRandomSha512[i]).Any()) + throw new Exception("Invalid hash value"); } } \ No newline at end of file diff --git a/AaruBenchmark/Compression/Aaru.cs b/AaruBenchmark/Compression/Aaru.cs index be62282..7b79621 100644 --- a/AaruBenchmark/Compression/Aaru.cs +++ b/AaruBenchmark/Compression/Aaru.cs @@ -4,128 +4,127 @@ using Aaru6.Checksums; using CUETools.Codecs; using CUETools.Codecs.Flake; -namespace AaruBenchmark.Compression +namespace AaruBenchmark.Compression; + +public class Aaru { - public class Aaru + public static void AppleRle() { - public static void AppleRle() + const int bufferSize = 20960; + + byte[] input = new byte[1102]; + + var fs = new FileStream(Path.Combine(Program.Folder, "apple_rle.bin"), FileMode.Open, FileAccess.Read); + + fs.Read(input, 0, input.Length); + fs.Close(); + fs.Dispose(); + + byte[] output = new byte[bufferSize]; + + var rle = new AppleRle(new MemoryStream(input)); + + for(int i = 0; i < bufferSize; i++) + output[i] = (byte)rle.ProduceByte(); + + string crc = Crc32Context.Data(output, out _); + + if(crc != "3525ef06") + throw new InvalidDataException("Incorrect decompressed checksum"); + } + + public static void TeleDiskLzh() + { + const int bufsz = 512; + + byte[] input = new byte[9040]; + + var fs = new FileStream(Path.Combine(Program.Folder, "teledisk_lzh.bin"), FileMode.Open, FileAccess.Read); + + fs.Read(input, 0, input.Length); + fs.Close(); + fs.Dispose(); + + int rd; + int total_rd = 0; + var lzh = new TeleDiskLzh(new MemoryStream(input)); + var outMs = new MemoryStream(); + + do { - const int bufferSize = 20960; + if((rd = lzh.Decode(out byte[] obuf, bufsz)) > 0) + outMs.Write(obuf, 0, rd); - byte[] input = new byte[1102]; + total_rd += rd; + } while(rd == bufsz); - var fs = new FileStream(Path.Combine(Program.Folder, "apple_rle.bin"), FileMode.Open, FileAccess.Read); + byte[] output = outMs.ToArray(); - fs.Read(input, 0, input.Length); - fs.Close(); - fs.Dispose(); + if(total_rd != 39820) + throw new InvalidDataException("Incorrect decompressed data"); - byte[] output = new byte[bufferSize]; + if(output.Length != 39820) + throw new InvalidDataException("Incorrect decompressed data"); - var rle = new AppleRle(new MemoryStream(input)); + string crc = Crc32Context.Data(output, out _); - for(int i = 0; i < bufferSize; i++) - output[i] = (byte)rle.ProduceByte(); + if(crc != "22bd5d44") + throw new InvalidDataException("Incorrect decompressed checksum"); + } - string crc = Crc32Context.Data(output, out _); + public static void Flac() + { + var flacMs = new FileStream(Path.Combine(Program.Folder, "flac.flac"), FileMode.Open, FileAccess.Read); + var flakeReader = new AudioDecoder(new DecoderSettings(), "", flacMs); + byte[] block = new byte[9633792]; + int samples = block.Length / 2352 * 588; + var audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, block, samples); + flakeReader.Read(audioBuffer, samples); + flakeReader.Close(); + flacMs.Close(); - if(crc != "3525ef06") - throw new InvalidDataException("Incorrect decompressed checksum"); - } + string crc = Crc32Context.Data(block, out _); - public static void TeleDiskLzh() + if(crc != "dfbc99bb") + throw new InvalidDataException("Incorrect decompressed checksum"); + } + + public static void CompressFlac() + { + var dataStream = new FileStream(Path.Combine(Program.Folder, "audio.bin"), FileMode.Open, FileAccess.Read); + byte[] decompressed = new byte[9633792]; + dataStream.Read(decompressed, 0, decompressed.Length); + dataStream.Close(); + byte[] backendBuffer = new byte[9633792]; + + var flakeWriterSettings = new EncoderSettings { - const int bufsz = 512; + PCM = AudioPCMConfig.RedBook, + DoMD5 = false, + BlockSize = 4608, + MinFixedOrder = 0, + MaxFixedOrder = 4, + MinLPCOrder = 1, + MaxLPCOrder = 32, + MaxPartitionOrder = 8, + StereoMethod = StereoMethod.Evaluate, + PredictionType = PredictionType.Search, + WindowMethod = WindowMethod.EvaluateN, + EstimationDepth = 5, + MinPrecisionSearch = 1, + MaxPrecisionSearch = 1, + TukeyParts = 0, + TukeyOverlap = 1.0, + TukeyP = 1.0, + AllowNonSubset = true + }; - byte[] input = new byte[9040]; - - var fs = new FileStream(Path.Combine(Program.Folder, "teledisk_lzh.bin"), FileMode.Open, FileAccess.Read); - - fs.Read(input, 0, input.Length); - fs.Close(); - fs.Dispose(); - - int rd; - int total_rd = 0; - var lzh = new TeleDiskLzh(new MemoryStream(input)); - var outMs = new MemoryStream(); - - do - { - if((rd = lzh.Decode(out byte[] obuf, bufsz)) > 0) - outMs.Write(obuf, 0, rd); - - total_rd += rd; - } while(rd == bufsz); - - byte[] output = outMs.ToArray(); - - if(total_rd != 39820) - throw new InvalidDataException("Incorrect decompressed data"); - - if(output.Length != 39820) - throw new InvalidDataException("Incorrect decompressed data"); - - string crc = Crc32Context.Data(output, out _); - - if(crc != "22bd5d44") - throw new InvalidDataException("Incorrect decompressed checksum"); - } - - public static void Flac() + var flakeWriter = new AudioEncoder(flakeWriterSettings, "", new MemoryStream(backendBuffer)) { - var flacMs = new FileStream(Path.Combine(Program.Folder, "flac.flac"), FileMode.Open, FileAccess.Read); - var flakeReader = new AudioDecoder(new DecoderSettings(), "", flacMs); - byte[] block = new byte[9633792]; - int samples = block.Length / 2352 * 588; - var audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, block, samples); - flakeReader.Read(audioBuffer, samples); - flakeReader.Close(); - flacMs.Close(); + DoSeekTable = false + }; - string crc = Crc32Context.Data(block, out _); - - if(crc != "dfbc99bb") - throw new InvalidDataException("Incorrect decompressed checksum"); - } - - public static void CompressFlac() - { - var dataStream = new FileStream(Path.Combine(Program.Folder, "audio.bin"), FileMode.Open, FileAccess.Read); - byte[] decompressed = new byte[9633792]; - dataStream.Read(decompressed, 0, decompressed.Length); - dataStream.Close(); - byte[] backendBuffer = new byte[9633792]; - - var flakeWriterSettings = new EncoderSettings - { - PCM = AudioPCMConfig.RedBook, - DoMD5 = false, - BlockSize = 4608, - MinFixedOrder = 0, - MaxFixedOrder = 4, - MinLPCOrder = 1, - MaxLPCOrder = 32, - MaxPartitionOrder = 8, - StereoMethod = StereoMethod.Evaluate, - PredictionType = PredictionType.Search, - WindowMethod = WindowMethod.EvaluateN, - EstimationDepth = 5, - MinPrecisionSearch = 1, - MaxPrecisionSearch = 1, - TukeyParts = 0, - TukeyOverlap = 1.0, - TukeyP = 1.0, - AllowNonSubset = true - }; - - var flakeWriter = new AudioEncoder(flakeWriterSettings, "", new MemoryStream(backendBuffer)) - { - DoSeekTable = false - }; - - var audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, decompressed, 2408448); - flakeWriter.Write(audioBuffer); - } + var audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, decompressed, 2408448); + flakeWriter.Write(audioBuffer); } } \ No newline at end of file diff --git a/AaruBenchmark/Compression/Aaru6Compressions.cs b/AaruBenchmark/Compression/Aaru6Compressions.cs index 9eee4b6..809dbb0 100644 --- a/AaruBenchmark/Compression/Aaru6Compressions.cs +++ b/AaruBenchmark/Compression/Aaru6Compressions.cs @@ -2,95 +2,94 @@ using System.IO; using Aaru6.Checksums; using Aaru6.Compression; -namespace AaruBenchmark.Compression +namespace AaruBenchmark.Compression; + +public class Aaru6Compressions { - public class Aaru6Compressions + public static void AppleRle() { - public static void AppleRle() + const int bufferSize = 32768; + byte[] input = new byte[1102]; + + var fs = new FileStream(Path.Combine(Program.Folder, "apple_rle.bin"), FileMode.Open, FileAccess.Read); + + fs.Read(input, 0, input.Length); + fs.Close(); + fs.Dispose(); + + byte[] output = new byte[bufferSize]; + + int realSize = Aaru6.Compression.AppleRle.DecodeBuffer(input, output); + + if(realSize != 20960) + throw new InvalidDataException("Incorrect decompressed size"); + + string crc = Crc32Context.Data(output, (uint)realSize, out _); + + if(crc != "3525ef06") + throw new InvalidDataException("Incorrect decompressed checksum"); + } + + public static void TeleDiskLzh() + { + const int bufsz = 512; + + byte[] input = new byte[9040]; + + var fs = new FileStream(Path.Combine(Program.Folder, "teledisk_lzh.bin"), FileMode.Open, FileAccess.Read); + + fs.Read(input, 0, input.Length); + fs.Close(); + fs.Dispose(); + + int rd; + int total_rd = 0; + var lzh = new TeleDiskLzh(new MemoryStream(input)); + var outMs = new MemoryStream(); + + do { - const int bufferSize = 32768; - byte[] input = new byte[1102]; + if((rd = lzh.Decode(out byte[] obuf, bufsz)) > 0) + outMs.Write(obuf, 0, rd); - var fs = new FileStream(Path.Combine(Program.Folder, "apple_rle.bin"), FileMode.Open, FileAccess.Read); + total_rd += rd; + } while(rd == bufsz); - fs.Read(input, 0, input.Length); - fs.Close(); - fs.Dispose(); + byte[] output = outMs.ToArray(); - byte[] output = new byte[bufferSize]; + if(total_rd != 39820) + throw new InvalidDataException("Incorrect decompressed data"); - int realSize = Aaru6.Compression.AppleRle.DecodeBuffer(input, output); + if(output.Length != 39820) + throw new InvalidDataException("Incorrect decompressed data"); - if(realSize != 20960) - throw new InvalidDataException("Incorrect decompressed size"); + string crc = Crc32Context.Data(output, out _); - string crc = Crc32Context.Data(output, (uint)realSize, out _); + if(crc != "22bd5d44") + throw new InvalidDataException("Incorrect decompressed checksum"); + } - if(crc != "3525ef06") - throw new InvalidDataException("Incorrect decompressed checksum"); - } + public static void ADC() + { + const int bufferSize = 262144; + byte[] input = new byte[34367]; - public static void TeleDiskLzh() - { - const int bufsz = 512; + var fs = new FileStream(Path.Combine(Program.Folder, "adc.bin"), FileMode.Open, FileAccess.Read); - byte[] input = new byte[9040]; + fs.Read(input, 0, input.Length); + fs.Close(); + fs.Dispose(); - var fs = new FileStream(Path.Combine(Program.Folder, "teledisk_lzh.bin"), FileMode.Open, FileAccess.Read); + byte[] output = new byte[bufferSize]; - fs.Read(input, 0, input.Length); - fs.Close(); - fs.Dispose(); + int realSize = Aaru6.Compression.ADC.DecodeBuffer(input, output); - int rd; - int total_rd = 0; - var lzh = new TeleDiskLzh(new MemoryStream(input)); - var outMs = new MemoryStream(); + if(realSize != 262144) + throw new InvalidDataException("Incorrect decompressed size"); - do - { - if((rd = lzh.Decode(out byte[] obuf, bufsz)) > 0) - outMs.Write(obuf, 0, rd); + string crc = Crc32Context.Data(output, (uint)realSize, out _); - total_rd += rd; - } while(rd == bufsz); - - byte[] output = outMs.ToArray(); - - if(total_rd != 39820) - throw new InvalidDataException("Incorrect decompressed data"); - - if(output.Length != 39820) - throw new InvalidDataException("Incorrect decompressed data"); - - string crc = Crc32Context.Data(output, out _); - - if(crc != "22bd5d44") - throw new InvalidDataException("Incorrect decompressed checksum"); - } - - public static void ADC() - { - const int bufferSize = 262144; - byte[] input = new byte[34367]; - - var fs = new FileStream(Path.Combine(Program.Folder, "adc.bin"), FileMode.Open, FileAccess.Read); - - fs.Read(input, 0, input.Length); - fs.Close(); - fs.Dispose(); - - byte[] output = new byte[bufferSize]; - - int realSize = Aaru6.Compression.ADC.DecodeBuffer(input, output); - - if(realSize != 262144) - throw new InvalidDataException("Incorrect decompressed size"); - - string crc = Crc32Context.Data(output, (uint)realSize, out _); - - if(crc != "5a5a7388") - throw new InvalidDataException("Incorrect decompressed checksum"); - } + if(crc != "5a5a7388") + throw new InvalidDataException("Incorrect decompressed checksum"); } } \ No newline at end of file diff --git a/AaruBenchmark/Compression/DotNetZip.cs b/AaruBenchmark/Compression/DotNetZip.cs index e88bee8..7ae007a 100644 --- a/AaruBenchmark/Compression/DotNetZip.cs +++ b/AaruBenchmark/Compression/DotNetZip.cs @@ -2,160 +2,159 @@ using System.IO; using Ionic.BZip2; using Ionic.Zlib; -namespace AaruBenchmark.Compression +namespace AaruBenchmark.Compression; + +public static class DotNetZip { - public static class DotNetZip + public static void Gzip() { - public static void Gzip() + var _dataStream = new FileStream(Path.Combine(Program.Folder, "gzip.gz"), FileMode.Open, FileAccess.Read); + Stream str = new GZipStream(_dataStream, CompressionMode.Decompress, true); + byte[] compressed = new byte[1048576]; + int pos = 0; + int left = 1048576; + bool oneZero = false; + + while(left > 0) { - var _dataStream = new FileStream(Path.Combine(Program.Folder, "gzip.gz"), FileMode.Open, FileAccess.Read); - Stream str = new GZipStream(_dataStream, CompressionMode.Decompress, true); - byte[] compressed = new byte[1048576]; - int pos = 0; - int left = 1048576; - bool oneZero = false; + int done = str.Read(compressed, pos, left); - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - str.Close(); - str.Dispose(); + left -= done; + pos += done; } - public static void CompressGzip() + str.Close(); + str.Dispose(); + } + + public static void CompressGzip() + { + var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); + byte[] decompressed = new byte[8388608]; + dataStream.Read(decompressed, 0, decompressed.Length); + dataStream.Close(); + + var cmpMs = new MemoryStream(); + + Stream cmpStream = new GZipStream(cmpMs, CompressionMode.Compress, true); + cmpStream.Write(decompressed, 0, decompressed.Length); + cmpStream.Close(); + cmpMs.Position = 0; + + /* This is just to test integrity, disabled for benchmarking + Stream str = new GZipStream(cmpMs, CompressionMode.Decompress, true); + byte[] compressed = new byte[decompressed.Length]; + int pos = 0; + int left = compressed.Length; + bool oneZero = false; + + while(left > 0) { - var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); - byte[] decompressed = new byte[8388608]; - dataStream.Read(decompressed, 0, decompressed.Length); - dataStream.Close(); + int done = str.Read(compressed, pos, left); - var cmpMs = new MemoryStream(); - - Stream cmpStream = new GZipStream(cmpMs, CompressionMode.Compress, true); - cmpStream.Write(decompressed, 0, decompressed.Length); - cmpStream.Close(); - cmpMs.Position = 0; - - /* This is just to test integrity, disabled for benchmarking - Stream str = new GZipStream(cmpMs, CompressionMode.Decompress, true); - byte[] compressed = new byte[decompressed.Length]; - int pos = 0; - int left = compressed.Length; - bool oneZero = false; - - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - str.Close(); - str.Dispose(); - - string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); - - if(newCrc != "954bf76e") - throw new InvalidDataException("Incorrect decompressed checksum"); - */ + left -= done; + pos += done; } - public static void Bzip2() + str.Close(); + str.Dispose(); + + string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); + + if(newCrc != "954bf76e") + throw new InvalidDataException("Incorrect decompressed checksum"); + */ + } + + public static void Bzip2() + { + var _dataStream = new FileStream(Path.Combine(Program.Folder, "bzip2.bz2"), FileMode.Open, FileAccess.Read); + Stream str = new BZip2InputStream(_dataStream, true); + byte[] compressed = new byte[1048576]; + int pos = 0; + int left = 1048576; + bool oneZero = false; + + while(left > 0) { - var _dataStream = new FileStream(Path.Combine(Program.Folder, "bzip2.bz2"), FileMode.Open, FileAccess.Read); - Stream str = new BZip2InputStream(_dataStream, true); - byte[] compressed = new byte[1048576]; - int pos = 0; - int left = 1048576; - bool oneZero = false; + int done = str.Read(compressed, pos, left); - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - str.Close(); - str.Dispose(); + left -= done; + pos += done; } - public static void CompressBzip2() + str.Close(); + str.Dispose(); + } + + public static void CompressBzip2() + { + var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); + byte[] decompressed = new byte[8388608]; + dataStream.Read(decompressed, 0, decompressed.Length); + dataStream.Close(); + + var cmpMs = new MemoryStream(); + + Stream cmpStream = new BZip2OutputStream(cmpMs, 9, true); + cmpStream.Write(decompressed, 0, decompressed.Length); + cmpStream.Close(); + cmpMs.Position = 0; + + /* This is just to test integrity, disabled for benchmarking + Stream str = new BZip2InputStream(cmpMs, true); + byte[] compressed = new byte[decompressed.Length]; + int pos = 0; + int left = compressed.Length; + bool oneZero = false; + + while(left > 0) { - var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); - byte[] decompressed = new byte[8388608]; - dataStream.Read(decompressed, 0, decompressed.Length); - dataStream.Close(); + int done = str.Read(compressed, pos, left); - var cmpMs = new MemoryStream(); - - Stream cmpStream = new BZip2OutputStream(cmpMs, 9, true); - cmpStream.Write(decompressed, 0, decompressed.Length); - cmpStream.Close(); - cmpMs.Position = 0; - - /* This is just to test integrity, disabled for benchmarking - Stream str = new BZip2InputStream(cmpMs, true); - byte[] compressed = new byte[decompressed.Length]; - int pos = 0; - int left = compressed.Length; - bool oneZero = false; - - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - str.Close(); - str.Dispose(); - - string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); - - if(newCrc != "954bf76e") - throw new InvalidDataException("Incorrect decompressed checksum"); - */ + left -= done; + pos += done; } + + str.Close(); + str.Dispose(); + + string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); + + if(newCrc != "954bf76e") + throw new InvalidDataException("Incorrect decompressed checksum"); + */ } } \ No newline at end of file diff --git a/AaruBenchmark/Compression/NetRuntime.cs b/AaruBenchmark/Compression/NetRuntime.cs index 6840dd7..082829d 100644 --- a/AaruBenchmark/Compression/NetRuntime.cs +++ b/AaruBenchmark/Compression/NetRuntime.cs @@ -1,84 +1,83 @@ using System.IO; using System.IO.Compression; -namespace AaruBenchmark.Compression +namespace AaruBenchmark.Compression; + +public static class NetRuntime { - public static class NetRuntime + public static void Gzip() { - public static void Gzip() + var _dataStream = new FileStream(Path.Combine(Program.Folder, "gzip.gz"), FileMode.Open, FileAccess.Read); + Stream str = new GZipStream(_dataStream, CompressionMode.Decompress, true); + byte[] compressed = new byte[1048576]; + int pos = 0; + int left = 1048576; + bool oneZero = false; + + while(left > 0) { - var _dataStream = new FileStream(Path.Combine(Program.Folder, "gzip.gz"), FileMode.Open, FileAccess.Read); - Stream str = new GZipStream(_dataStream, CompressionMode.Decompress, true); - byte[] compressed = new byte[1048576]; - int pos = 0; - int left = 1048576; - bool oneZero = false; + int done = str.Read(compressed, pos, left); - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - str.Close(); - str.Dispose(); + left -= done; + pos += done; } - public static void CompressGzip() + str.Close(); + str.Dispose(); + } + + public static void CompressGzip() + { + var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); + byte[] decompressed = new byte[8388608]; + dataStream.Read(decompressed, 0, decompressed.Length); + dataStream.Close(); + + var cmpMs = new MemoryStream(); + + Stream cmpStream = new GZipStream(cmpMs, CompressionMode.Compress, true); + cmpStream.Write(decompressed, 0, decompressed.Length); + cmpStream.Close(); + cmpMs.Position = 0; + + /* This is just to test integrity, disabled for benchmarking + Stream str = new GZipStream(cmpMs, CompressionMode.Decompress, true); + byte[] compressed = new byte[decompressed.Length]; + int pos = 0; + int left = compressed.Length; + bool oneZero = false; + + while(left > 0) { - var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); - byte[] decompressed = new byte[8388608]; - dataStream.Read(decompressed, 0, decompressed.Length); - dataStream.Close(); + int done = str.Read(compressed, pos, left); - var cmpMs = new MemoryStream(); - - Stream cmpStream = new GZipStream(cmpMs, CompressionMode.Compress, true); - cmpStream.Write(decompressed, 0, decompressed.Length); - cmpStream.Close(); - cmpMs.Position = 0; - - /* This is just to test integrity, disabled for benchmarking - Stream str = new GZipStream(cmpMs, CompressionMode.Decompress, true); - byte[] compressed = new byte[decompressed.Length]; - int pos = 0; - int left = compressed.Length; - bool oneZero = false; - - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - str.Close(); - str.Dispose(); - - string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); - - if(newCrc != "954bf76e") - throw new InvalidDataException("Incorrect decompressed checksum"); - */ + left -= done; + pos += done; } + + str.Close(); + str.Dispose(); + + string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); + + if(newCrc != "954bf76e") + throw new InvalidDataException("Incorrect decompressed checksum"); + */ } } \ No newline at end of file diff --git a/AaruBenchmark/Compression/SharpCompress.cs b/AaruBenchmark/Compression/SharpCompress.cs index 169d71e..897868e 100644 --- a/AaruBenchmark/Compression/SharpCompress.cs +++ b/AaruBenchmark/Compression/SharpCompress.cs @@ -6,348 +6,347 @@ using SharpCompress.Compressors.BZip2; using SharpCompress.Compressors.Deflate; using SharpCompress.Compressors.LZMA; -namespace AaruBenchmark.Compression +namespace AaruBenchmark.Compression; + +public static class SharpCompress { - public static class SharpCompress + public static void Gzip() { - public static void Gzip() + var _dataStream = new FileStream(Path.Combine(Program.Folder, "gzip.gz"), FileMode.Open, FileAccess.Read); + Stream str = new GZipStream(_dataStream, CompressionMode.Decompress); + byte[] compressed = new byte[1048576]; + int pos = 0; + int left = 1048576; + bool oneZero = false; + + while(left > 0) { - var _dataStream = new FileStream(Path.Combine(Program.Folder, "gzip.gz"), FileMode.Open, FileAccess.Read); - Stream str = new GZipStream(_dataStream, CompressionMode.Decompress); - byte[] compressed = new byte[1048576]; - int pos = 0; - int left = 1048576; - bool oneZero = false; + int done = str.Read(compressed, pos, left); - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - str.Close(); - str.Dispose(); + left -= done; + pos += done; } - public static void CompressGzip() + str.Close(); + str.Dispose(); + } + + public static void CompressGzip() + { + var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); + byte[] decompressed = new byte[8388608]; + dataStream.Read(decompressed, 0, decompressed.Length); + dataStream.Close(); + byte[] backendBuffer = new byte[8388608]; + + Stream cmpStream = new GZipStream(new MemoryStream(backendBuffer), CompressionMode.Compress, + CompressionLevel.Level9); + + cmpStream.Write(decompressed, 0, decompressed.Length); + cmpStream.Close(); + + /* This is just to test integrity, disabled for benchmarking + Stream str = new GZipStream(new MemoryStream(backendBuffer), CompressionMode.Decompress); + byte[] compressed = new byte[decompressed.Length]; + int pos = 0; + int left = compressed.Length; + bool oneZero = false; + + while(left > 0) { - var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); - byte[] decompressed = new byte[8388608]; - dataStream.Read(decompressed, 0, decompressed.Length); - dataStream.Close(); - byte[] backendBuffer = new byte[8388608]; + int done = str.Read(compressed, pos, left); - Stream cmpStream = new GZipStream(new MemoryStream(backendBuffer), CompressionMode.Compress, - CompressionLevel.Level9); - - cmpStream.Write(decompressed, 0, decompressed.Length); - cmpStream.Close(); - - /* This is just to test integrity, disabled for benchmarking - Stream str = new GZipStream(new MemoryStream(backendBuffer), CompressionMode.Decompress); - byte[] compressed = new byte[decompressed.Length]; - int pos = 0; - int left = compressed.Length; - bool oneZero = false; - - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); - - if(newCrc != "954bf76e") - throw new InvalidDataException("Incorrect decompressed checksum"); - */ + left -= done; + pos += done; } - public static void Bzip2() + string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); + + if(newCrc != "954bf76e") + throw new InvalidDataException("Incorrect decompressed checksum"); + */ + } + + public static void Bzip2() + { + var _dataStream = new FileStream(Path.Combine(Program.Folder, "bzip2.bz2"), FileMode.Open, FileAccess.Read); + Stream str = new BZip2Stream(_dataStream, CompressionMode.Decompress, true); + byte[] compressed = new byte[1048576]; + int pos = 0; + int left = 1048576; + bool oneZero = false; + + while(left > 0) { - var _dataStream = new FileStream(Path.Combine(Program.Folder, "bzip2.bz2"), FileMode.Open, FileAccess.Read); - Stream str = new BZip2Stream(_dataStream, CompressionMode.Decompress, true); - byte[] compressed = new byte[1048576]; - int pos = 0; - int left = 1048576; - bool oneZero = false; + int done = str.Read(compressed, pos, left); - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - str.Close(); - str.Dispose(); + left -= done; + pos += done; } - public static void CompressBzip2() + str.Close(); + str.Dispose(); + } + + public static void CompressBzip2() + { + var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); + byte[] decompressed = new byte[8388608]; + dataStream.Read(decompressed, 0, decompressed.Length); + dataStream.Close(); + byte[] backendBuffer = new byte[8388608]; + + Stream cmpStream = new BZip2Stream(new MemoryStream(backendBuffer), CompressionMode.Compress, true); + cmpStream.Write(decompressed, 0, decompressed.Length); + cmpStream.Close(); + + /* This is just to test integrity, disabled for benchmarking + Stream str = new BZip2Stream(new MemoryStream(backendBuffer), CompressionMode.Decompress, false); + byte[] compressed = new byte[decompressed.Length]; + int pos = 0; + int left = compressed.Length; + bool oneZero = false; + + while(left > 0) { - var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); - byte[] decompressed = new byte[8388608]; - dataStream.Read(decompressed, 0, decompressed.Length); - dataStream.Close(); - byte[] backendBuffer = new byte[8388608]; + int done = str.Read(compressed, pos, left); - Stream cmpStream = new BZip2Stream(new MemoryStream(backendBuffer), CompressionMode.Compress, true); - cmpStream.Write(decompressed, 0, decompressed.Length); - cmpStream.Close(); - - /* This is just to test integrity, disabled for benchmarking - Stream str = new BZip2Stream(new MemoryStream(backendBuffer), CompressionMode.Decompress, false); - byte[] compressed = new byte[decompressed.Length]; - int pos = 0; - int left = compressed.Length; - bool oneZero = false; - - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); - - if(newCrc != "954bf76e") - throw new InvalidDataException("Incorrect decompressed checksum"); - */ + left -= done; + pos += done; } - public static void ADC() + string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); + + if(newCrc != "954bf76e") + throw new InvalidDataException("Incorrect decompressed checksum"); + */ + } + + public static void ADC() + { + var _dataStream = new FileStream(Path.Combine(Program.Folder, "adc.bin"), FileMode.Open, FileAccess.Read); + Stream str = new ADCStream(_dataStream); + byte[] compressed = new byte[262144]; + int pos = 0; + int left = 262144; + bool oneZero = false; + + while(left > 0) { - var _dataStream = new FileStream(Path.Combine(Program.Folder, "adc.bin"), FileMode.Open, FileAccess.Read); - Stream str = new ADCStream(_dataStream); - byte[] compressed = new byte[262144]; - int pos = 0; - int left = 262144; - bool oneZero = false; + int done = str.Read(compressed, pos, left); - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - str.Close(); - str.Dispose(); - - string crc = Crc32Context.Data(compressed, 262144, out _); - - if(crc != "5a5a7388") - throw new InvalidDataException("Incorrect decompressed checksum"); + left -= done; + pos += done; } - public static void Lzip() + str.Close(); + str.Dispose(); + + string crc = Crc32Context.Data(compressed, 262144, out _); + + if(crc != "5a5a7388") + throw new InvalidDataException("Incorrect decompressed checksum"); + } + + public static void Lzip() + { + var _dataStream = new FileStream(Path.Combine(Program.Folder, "lzip.lz"), FileMode.Open, FileAccess.Read); + Stream str = new LZipStream(_dataStream, CompressionMode.Decompress); + byte[] compressed = new byte[1048576]; + int pos = 0; + int left = 1048576; + bool oneZero = false; + + while(left > 0) { - var _dataStream = new FileStream(Path.Combine(Program.Folder, "lzip.lz"), FileMode.Open, FileAccess.Read); - Stream str = new LZipStream(_dataStream, CompressionMode.Decompress); - byte[] compressed = new byte[1048576]; - int pos = 0; - int left = 1048576; - bool oneZero = false; + int done = str.Read(compressed, pos, left); - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - str.Close(); - str.Dispose(); - - string crc = Crc32Context.Data(compressed, 1048576, out _); - - if(crc != "c64059c0") - throw new InvalidDataException("Incorrect decompressed checksum"); + left -= done; + pos += done; } - public static void CompressLzip() + str.Close(); + str.Dispose(); + + string crc = Crc32Context.Data(compressed, 1048576, out _); + + if(crc != "c64059c0") + throw new InvalidDataException("Incorrect decompressed checksum"); + } + + public static void CompressLzip() + { + var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); + byte[] decompressed = new byte[8388608]; + dataStream.Read(decompressed, 0, decompressed.Length); + dataStream.Close(); + byte[] backendBuffer = new byte[8388608]; + + Stream cmpStream = new LZipStream(new MemoryStream(backendBuffer), CompressionMode.Compress); + cmpStream.Write(decompressed, 0, decompressed.Length); + cmpStream.Close(); + + /* This is just to test integrity, disabled for benchmarking + Stream str = new LZipStream(new MemoryStream(backendBuffer), CompressionMode.Decompress); + byte[] compressed = new byte[decompressed.Length]; + int pos = 0; + int left = compressed.Length; + bool oneZero = false; + + while(left > 0) { - var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); - byte[] decompressed = new byte[8388608]; - dataStream.Read(decompressed, 0, decompressed.Length); - dataStream.Close(); - byte[] backendBuffer = new byte[8388608]; + int done = str.Read(compressed, pos, left); - Stream cmpStream = new LZipStream(new MemoryStream(backendBuffer), CompressionMode.Compress); - cmpStream.Write(decompressed, 0, decompressed.Length); - cmpStream.Close(); - - /* This is just to test integrity, disabled for benchmarking - Stream str = new LZipStream(new MemoryStream(backendBuffer), CompressionMode.Decompress); - byte[] compressed = new byte[decompressed.Length]; - int pos = 0; - int left = compressed.Length; - bool oneZero = false; - - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); - - if(newCrc != "954bf76e") - throw new InvalidDataException("Incorrect decompressed checksum"); - */ + left -= done; + pos += done; } - public static void Lzma() + string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); + + if(newCrc != "954bf76e") + throw new InvalidDataException("Incorrect decompressed checksum"); + */ + } + + public static void Lzma() + { + var _dataStream = new FileStream(Path.Combine(Program.Folder, "lzma.bin"), FileMode.Open, FileAccess.Read); + + Stream str = new LzmaStream(new byte[] { - var _dataStream = new FileStream(Path.Combine(Program.Folder, "lzma.bin"), FileMode.Open, FileAccess.Read); + 0x5D, 0x00, 0x00, 0x00, 0x02 + }, _dataStream); - Stream str = new LzmaStream(new byte[] + byte[] compressed = new byte[8388608]; + int pos = 0; + int left = 8388608; + bool oneZero = false; + + while(left > 0) + { + int done = str.Read(compressed, pos, left); + + if(done == 0) { - 0x5D, 0x00, 0x00, 0x00, 0x02 - }, _dataStream); + if(oneZero) + throw new IOException("Could not read the file!"); - byte[] compressed = new byte[8388608]; - int pos = 0; - int left = 8388608; - bool oneZero = false; - - while(left > 0) - { - int done = str.Read(compressed, pos, left); - - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - str.Close(); - str.Dispose(); - - string crc = Crc32Context.Data(compressed, 8388608, out _); - - if(crc != "954bf76e") - throw new InvalidDataException("Incorrect decompressed checksum"); + left -= done; + pos += done; } - public static void CompressLzma() + str.Close(); + str.Dispose(); + + string crc = Crc32Context.Data(compressed, 8388608, out _); + + if(crc != "954bf76e") + throw new InvalidDataException("Incorrect decompressed checksum"); + } + + public static void CompressLzma() + { + var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); + byte[] decompressed = new byte[8388608]; + dataStream.Read(decompressed, 0, decompressed.Length); + dataStream.Close(); + byte[] backendBuffer = new byte[8388608]; + + var cmpStream = new LzmaStream(new LzmaEncoderProperties(true, 1048576, 273), false, + new MemoryStream(backendBuffer)); + + byte[] propertiesArray = new byte[cmpStream.Properties.Length]; + cmpStream.Properties.CopyTo(propertiesArray, 0); + + cmpStream.Write(decompressed, 0, decompressed.Length); + cmpStream.Close(); + + /* This is just to test integrity, disabled for benchmarking + Stream str = new LzmaStream(propertiesArray, new MemoryStream(backendBuffer)); + byte[] compressed = new byte[decompressed.Length]; + int pos = 0; + int left = compressed.Length; + bool oneZero = false; + + while(left > 0) { - var dataStream = new FileStream(Path.Combine(Program.Folder, "data.bin"), FileMode.Open, FileAccess.Read); - byte[] decompressed = new byte[8388608]; - dataStream.Read(decompressed, 0, decompressed.Length); - dataStream.Close(); - byte[] backendBuffer = new byte[8388608]; + int done = str.Read(compressed, pos, left); - var cmpStream = new LzmaStream(new LzmaEncoderProperties(true, 1048576, 273), false, - new MemoryStream(backendBuffer)); - - byte[] propertiesArray = new byte[cmpStream.Properties.Length]; - cmpStream.Properties.CopyTo(propertiesArray, 0); - - cmpStream.Write(decompressed, 0, decompressed.Length); - cmpStream.Close(); - - /* This is just to test integrity, disabled for benchmarking - Stream str = new LzmaStream(propertiesArray, new MemoryStream(backendBuffer)); - byte[] compressed = new byte[decompressed.Length]; - int pos = 0; - int left = compressed.Length; - bool oneZero = false; - - while(left > 0) + if(done == 0) { - int done = str.Read(compressed, pos, left); + if(oneZero) + throw new IOException("Could not read the file!"); - if(done == 0) - { - if(oneZero) - throw new IOException("Could not read the file!"); - - oneZero = true; - } - - left -= done; - pos += done; + oneZero = true; } - string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); - - if(newCrc != "954bf76e") - throw new InvalidDataException("Incorrect decompressed checksum"); - */ + left -= done; + pos += done; } + + string newCrc = Crc32Context.Data(compressed, (uint)compressed.Length, out _); + + if(newCrc != "954bf76e") + throw new InvalidDataException("Incorrect decompressed checksum"); + */ } } \ No newline at end of file diff --git a/AaruBenchmark/Program.cs b/AaruBenchmark/Program.cs index 4c20e0b..ce13e19 100644 --- a/AaruBenchmark/Program.cs +++ b/AaruBenchmark/Program.cs @@ -5,7 +5,7 @@ using BenchmarkDotNet.Running; namespace AaruBenchmark; -internal static class Program +static class Program { internal static string Folder => Path.Combine(Environment.CurrentDirectory, "data"); diff --git a/Benchmarks.ods b/Benchmarks.ods index 70cdb06556b3824f6324a912813274c3b8950e0f..ad5570a803dadc6baf3108cf106a3bfb213c7a9f 100644 GIT binary patch literal 132552 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 A6#xJL literal 238309 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

ip|HAE#&cp1wBmxeW9C_^up7=H`lsIem>AEpi{k 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

9?`=Tsl>Tcv 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

jReOA6?Y9II&E*CDbz6+E~_l&36ui^FH^3 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