-
Notifications
You must be signed in to change notification settings - Fork 0
/
QuickXorHash.cs
125 lines (108 loc) · 4.94 KB
/
QuickXorHash.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
namespace QuickXorHash
{
public class QuickXorHash : System.Security.Cryptography.HashAlgorithm
{
private const int BitsInLastCell = 32;
private const byte Shift = 11;
#pragma warning disable IDE0051 // Remove unused private members
private const int Threshold = 600;
#pragma warning restore IDE0051 // Remove unused private members
private const byte WidthInBits = 160;
private UInt64[] _data;
private Int64 _lengthSoFar;
private int _shiftSoFar;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public QuickXorHash()
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
{
this.Initialize();
}
protected override void HashCore(byte[] array, int ibStart, int cbSize)
{
unchecked
{
int currentShift = this._shiftSoFar;
// The bitvector where we'll start xoring
int vectorArrayIndex = currentShift / 64;
// The position within the bit vector at which we begin xoring
int vectorOffset = currentShift % 64;
int iterations = Math.Min(cbSize, QuickXorHash.WidthInBits);
for (int i = 0; i < iterations; i++)
{
bool isLastCell = vectorArrayIndex == this._data.Length - 1;
int bitsInVectorCell = isLastCell ? QuickXorHash.BitsInLastCell : 64;
// There's at least 2 bitvectors before we reach the end of the array
if (vectorOffset <= bitsInVectorCell - 8)
{
for (int j = ibStart + i; j < cbSize + ibStart; j += QuickXorHash.WidthInBits)
{
this._data[vectorArrayIndex] ^= (ulong)array[j] << vectorOffset;
}
}
else
{
int index1 = vectorArrayIndex;
int index2 = isLastCell ? 0 : (vectorArrayIndex + 1);
byte low = (byte)(bitsInVectorCell - vectorOffset);
byte xoredByte = 0;
for (int j = ibStart + i; j < cbSize + ibStart; j += QuickXorHash.WidthInBits)
{
xoredByte ^= array[j];
}
this._data[index1] ^= (ulong)xoredByte << vectorOffset;
this._data[index2] ^= (ulong)xoredByte >> low;
}
vectorOffset += QuickXorHash.Shift;
while (vectorOffset >= bitsInVectorCell)
{
vectorArrayIndex = isLastCell ? 0 : vectorArrayIndex + 1;
vectorOffset -= bitsInVectorCell;
}
}
// Update the starting position in a circular shift pattern
this._shiftSoFar = (this._shiftSoFar + QuickXorHash.Shift * (cbSize % QuickXorHash.WidthInBits)) % QuickXorHash.WidthInBits;
}
this._lengthSoFar += cbSize;
}
protected override byte[] HashFinal()
{
// Create a byte array big enough to hold all our data
byte[] rgb = new byte[(QuickXorHash.WidthInBits - 1) / 8 + 1];
// Block copy all our bitvectors to this byte array
for (Int32 i = 0; i < this._data.Length - 1; i++)
{
Buffer.BlockCopy(
BitConverter.GetBytes(this._data[i]), 0,
rgb, i * 8,
8);
}
Buffer.BlockCopy(
BitConverter.GetBytes(this._data[this._data.Length - 1]), 0,
rgb, (this._data.Length - 1) * 8,
rgb.Length - (this._data.Length - 1) * 8);
// XOR the file length with the least significant bits
// Note that GetBytes is architecture-dependent, so care should
// be taken with porting. The expected value is 8-bytes in length in little-endian format
var lengthBytes = BitConverter.GetBytes(this._lengthSoFar);
System.Diagnostics.Debug.Assert(lengthBytes.Length == 8);
for (int i = 0; i < lengthBytes.Length; i++)
{
rgb[(QuickXorHash.WidthInBits / 8) - lengthBytes.Length + i] ^= lengthBytes[i];
}
return rgb;
}
public override sealed void Initialize()
{
this._data = new ulong[(QuickXorHash.WidthInBits - 1) / 64 + 1];
this._shiftSoFar = 0;
this._lengthSoFar = 0;
}
public override int HashSize
{
get
{
return QuickXorHash.WidthInBits;
}
}
}
}