using System;
using System.Collections.Generic;
using System.Text;
namespace Duplicati.Library.SharpRSync
{
///
/// This class reads signature files in the same format as RDiff
///
public class ChecksumFileReader
{
///
/// The number of bytes in a long
///
private const int BYTES_PR_LONG = 8;
///
/// The re-used MD4 algorithm
///
private System.Security.Cryptography.HashAlgorithm m_hashAlgorithm;
///
/// The length of a datablock
///
private int m_blocklen;
///
/// The number of bytes used for storing a single strong signature
///
private int m_stronglen;
///
/// This is a lookup table for a hash of the weak checksum,
/// used to quickly determine if a weak checksum exists
///
private bool[] m_weakLookup;
///
/// This list contains all the weak hashes and the index of the corresponding strong hash.
/// The list is sorted by the weak hash to allow for O(log n) lookups
///
private KeyValuePair[] m_weakTable;
///
/// This list contains all strong hashes, encoded as unsigned int64,
/// which compares much faster than a byte[]. If the strong hash does not fill
/// an equal number of longs, the remaining bytes are set to zero
/// before generating the long value.
///
private ulong[] m_strongIndex;
///
/// The number of long values each strong hash occupies
///
private int m_longs_pr_strong;
///
/// The comparer used to perform binary searches in the weak table
///
private KeyComparer m_comparer;
///
/// Reads a ChecksumFile from a stream
///
/// The stream to read from
public ChecksumFileReader(System.IO.Stream input)
{
m_hashAlgorithm = Mono.Security.Cryptography.MD4.Create("MD4");
m_hashAlgorithm.Initialize();
//Read and verify that the file header is valid
byte[] sig = new byte[4];
if (Utility.ForceStreamRead(input, sig, 4) != 4)
throw new Exception(Strings.ChecksumFile.EndofstreamBeforeSignatureError);
for (int i = 0; i < sig.Length; i++)
if (RDiffBinary.SIGNATURE_MAGIC[i] != sig[i])
throw new Exception(Strings.ChecksumFile.InvalidSignatureHeaderError);
if (Utility.ForceStreamRead(input, sig, 4) != 4)
throw new Exception(Strings.ChecksumFile.EndofstreamInBlocksizeError);
m_blocklen = BitConverter.ToInt32(RDiffBinary.FixEndian(sig), 0);
if (m_blocklen < 1 || m_blocklen > int.MaxValue / 2)
throw new Exception(string.Format(Strings.ChecksumFile.InvalidBlocksizeError, m_blocklen));
if (Utility.ForceStreamRead(input, sig, 4) != 4)
throw new Exception(Strings.ChecksumFile.EndofstreamInStronglenError);
m_stronglen = BitConverter.ToInt32(RDiffBinary.FixEndian(sig), 0);
if (m_stronglen < 1 || m_stronglen > (m_hashAlgorithm.HashSize / 8))
throw new Exception(string.Format(Strings.ChecksumFile.InvalidStrongsizeError, m_stronglen));
//Prepare the data structures
m_weakLookup = new bool[0x10000];
m_longs_pr_strong = (m_stronglen + (BYTES_PR_LONG - 1)) / BYTES_PR_LONG;
m_comparer = new KeyComparer();
byte[] strongbuf = new byte[m_longs_pr_strong * BYTES_PR_LONG];
//We would like to use static allocation for these lists, but unfortunately
// the zip stream does not report the correct length
List> tempWeakTable = new List>();
List tempStrongIndex = new List();
long count = 0;
//Repeat until the stream is exhausted
while (true)
{
if (Utility.ForceStreamRead(input, sig, 4) != 4)
break;
uint weak = BitConverter.ToUInt32(RDiffBinary.FixEndian(sig), 0); ;
if (Utility.ForceStreamRead(input, strongbuf, m_stronglen) != m_stronglen)
throw new Exception(Strings.ChecksumFile.EndofstreamInStrongSignatureError);
//Record the entries
tempWeakTable.Add(new KeyValuePair(weak, count));
for (int i = 0; i < m_longs_pr_strong; i++)
tempStrongIndex.Add(BitConverter.ToUInt64(strongbuf, i * BYTES_PR_LONG));
count++;
}
//Copy the lists into fixed sized arrays
m_weakTable = tempWeakTable.ToArray();
tempWeakTable.Clear();
tempWeakTable = null;
m_strongIndex = tempStrongIndex.ToArray();
tempStrongIndex.Clear();
tempStrongIndex = null;
//Sort the m_weakTable by weak checksum so we can make binary lookups
Array.Sort>(m_weakTable, m_comparer);
//Initialize the weakest lookup table
for (int i = 0; i < m_weakTable.Length; i++)
m_weakLookup[m_weakTable[i].Key >> 16] = true;
}
///
/// A comparer that allows sorting and searching with a list/array of KeyValuePair items
///
/// The key tyoe
/// The value type
private class KeyComparer : IEqualityComparer>, IComparer>
where TKey : IComparable
{
#region IEqualityComparer> Members
///
/// Determines if the two entries are equal
///
/// A KeyValuePair
/// A KeyValuePair
/// True if the keys are equal, false otherwise
public bool Equals(KeyValuePair x, KeyValuePair y)
{
return x.Key.Equals(y.Key);
}
///
/// Returns the hash code of the KeyValuePair
///
///
///
public int GetHashCode(KeyValuePair obj)
{
return obj.GetHashCode();
}
#endregion
#region IComparer> Members
///
/// Determines the sort order of the KeyValuePairs
///
/// A KeyValuePair
/// A KeyValuePair
/// A signed value indicating the sort order of the KeyValuePairs
public int Compare(KeyValuePair x, KeyValuePair y)
{
return x.Key.CompareTo(y.Key);
}
#endregion
}
///
/// Gets the block index of data the equals the given data
///
/// The weak checksum for the data
/// The data buffer
/// The offset into the data buffer
/// The number of bytes to read from the buffer
/// The prefered index if multiple entries are found
/// The index of the matching element
public long LookupChunck(uint weakChecksum, byte[] data, int offset, int length, long preferedIndex)
{
//Entry level check, this table is quite small and lookup is O(1)
if (!m_weakLookup[weakChecksum >> 16])
return -1;
//Do a O(log n) lookup on the weak hash, a hashtable would have O(1), but requires too much memory
long sh = Array.BinarySearch>(m_weakTable, new KeyValuePair(weakChecksum, 0), m_comparer);
if (sh < 0)
return -1;
//At this point we know that the weak hash matches something, so we have to calculate the strong hash
if (!m_hashAlgorithm.CanReuseTransform)
m_hashAlgorithm = Mono.Security.Cryptography.MD4.Create("MD4");
byte[] hash = m_hashAlgorithm.ComputeHash(data, offset, length);
//Pad with zeros if needed
if (hash.Length % BYTES_PR_LONG != 0)
Array.Resize(ref hash,hash.Length + (BYTES_PR_LONG - hash.Length % BYTES_PR_LONG));
//Convert the hash to a list of long's so we can compare at max speed
//Even though we allocate this buffer each time, it is optimized away,
// so it is faster than having a pre-allocated buffer
ulong[] stronghash = new ulong[m_longs_pr_strong];
for (int i = 0; i < stronghash.Length; i++)
stronghash[i] = BitConverter.ToUInt64(hash, (i * BYTES_PR_LONG));
//Test first with prefered index as that gives the smallest possible file
// we should test with the weak first, but we do not have a direct lookup into
// the list of weak checksums, and this check should be rather fast,
// the slow part is generating the MD4 hash, and that has already been done
if (preferedIndex >= 0 && preferedIndex < (m_strongIndex.Length / m_longs_pr_strong))
{
int i;
for (i = 0; i < stronghash.Length; i++)
if (stronghash[i] != m_strongIndex[(preferedIndex * m_longs_pr_strong) + i])
break;
if (i == stronghash.Length)
return preferedIndex;
}
//Since the binary search can hit any of the possible matches, we must search both up and down
long shMid = sh;
//First we search down, starting with the match
while (sh < m_weakTable.LongLength && m_weakTable[sh].Key == weakChecksum)
{
long index = m_weakTable[sh].Value;
int i;
for (i = 0; i < stronghash.Length; i++)
if (stronghash[i] != m_strongIndex[(index * m_longs_pr_strong) + i])
break;
if (i == stronghash.Length)
return index;
sh++;
}
//Then we search up, starting with the one above the match
sh = shMid - 1;
while (sh >= 0 && m_weakTable[sh].Key == weakChecksum)
{
long index = m_weakTable[sh].Value;
int i;
for (i = 0; i < stronghash.Length; i++)
if (stronghash[i] != m_strongIndex[(index * m_longs_pr_strong) + 1])
break;
if (i == stronghash.Length)
return index;
sh--;
}
//No matches
return -1;
}
///
/// Gets the number of bytes in each hashed block
///
public int BlockLength { get { return m_blocklen; } }
///
/// Gets the number of bytes in a signature file
///
public int StrongLength { get { return m_stronglen; } }
///
/// Gets a lookup table with a checksum of all known weak hashes
///
public bool[] WeakLookup { get { return m_weakLookup; } }
}
}