Files
duplicati/Duplicati/Library/SharpRSync/ChecksumFileReader.cs
T
kenneth.skovhede@gmail.com 6d2ee81a85 Changed the improved SharpRSync module to only use the embedded MD4 implementation.
The purpose of this change is to prevent loading a defunct MD4 implementation,
which exists in the Mono library in all versions prior to this path (july 2010):
http://github.com/mono/mono/commit/6be3c20a61d4c9937d4b9f1e95783a6203f9f9c7

The drawback is that IF the system has a native MD4 library, it is not used.
That is of course a negative thing, but so far I have not seen any stock 
windows/linux system with native MD4 support.

git-svn-id: https://duplicati.googlecode.com/svn/trunk@481 59da171f-624f-0410-aa54-27559c288bec
2010-08-21 12:24:22 +00:00

289 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
namespace Duplicati.Library.SharpRSync
{
/// <summary>
/// This class reads signature files in the same format as RDiff
/// </summary>
public class ChecksumFileReader
{
/// <summary>
/// The number of bytes in a long
/// </summary>
private const int BYTES_PR_LONG = 8;
/// <summary>
/// The re-used MD4 algorithm
/// </summary>
private System.Security.Cryptography.HashAlgorithm m_hashAlgorithm;
/// <summary>
/// The length of a datablock
/// </summary>
private int m_blocklen;
/// <summary>
/// The number of bytes used for storing a single strong signature
/// </summary>
private int m_stronglen;
/// <summary>
/// This is a lookup table for a hash of the weak checksum,
/// used to quickly determine if a weak checksum exists
/// </summary>
private bool[] m_weakLookup;
/// <summary>
/// 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
/// </summary>
private KeyValuePair<uint, long>[] m_weakTable;
/// <summary>
/// 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.
/// </summary>
private ulong[] m_strongIndex;
/// <summary>
/// The number of long values each strong hash occupies
/// </summary>
private int m_longs_pr_strong;
/// <summary>
/// The comparer used to perform binary searches in the weak table
/// </summary>
private KeyComparer<uint, long> m_comparer;
/// <summary>
/// Reads a ChecksumFile from a stream
/// </summary>
/// <param name="input">The stream to read from</param>
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<uint,long>();
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<KeyValuePair<uint, long>> tempWeakTable = new List<KeyValuePair<uint, long>>();
List<ulong> tempStrongIndex = new List<ulong>();
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<uint,long>(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<KeyValuePair<uint, long>>(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;
}
/// <summary>
/// A comparer that allows sorting and searching with a list/array of KeyValuePair items
/// </summary>
/// <typeparam name="TKey">The key tyoe</typeparam>
/// <typeparam name="TValue">The value type</typeparam>
private class KeyComparer<TKey, TValue> : IEqualityComparer<KeyValuePair<TKey, TValue>>, IComparer<KeyValuePair<TKey, TValue>>
where TKey : IComparable
{
#region IEqualityComparer<KeyValuePair<TKey,TValue>> Members
/// <summary>
/// Determines if the two entries are equal
/// </summary>
/// <param name="x">A KeyValuePair</param>
/// <param name="y">A KeyValuePair</param>
/// <returns>True if the keys are equal, false otherwise</returns>
public bool Equals(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
{
return x.Key.Equals(y.Key);
}
/// <summary>
/// Returns the hash code of the KeyValuePair
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public int GetHashCode(KeyValuePair<TKey, TValue> obj)
{
return obj.GetHashCode();
}
#endregion
#region IComparer<KeyValuePair<TKey,TValue>> Members
/// <summary>
/// Determines the sort order of the KeyValuePairs
/// </summary>
/// <param name="x">A KeyValuePair</param>
/// <param name="y">A KeyValuePair</param>
/// <returns>A signed value indicating the sort order of the KeyValuePairs</returns>
public int Compare(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
{
return x.Key.CompareTo(y.Key);
}
#endregion
}
/// <summary>
/// Gets the block index of data the equals the given data
/// </summary>
/// <param name="weakChecksum">The weak checksum for the data</param>
/// <param name="data">The data buffer</param>
/// <param name="offset">The offset into the data buffer</param>
/// <param name="length">The number of bytes to read from the buffer</param>
/// <param name="preferedIndex">The prefered index if multiple entries are found</param>
/// <returns>The index of the matching element</returns>
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<KeyValuePair<uint, long>>(m_weakTable, new KeyValuePair<uint, long>(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<byte>(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;
}
/// <summary>
/// Gets the number of bytes in each hashed block
/// </summary>
public int BlockLength { get { return m_blocklen; } }
/// <summary>
/// Gets the number of bytes in a signature file
/// </summary>
public int StrongLength { get { return m_stronglen; } }
/// <summary>
/// Gets a lookup table with a checksum of all known weak hashes
/// </summary>
public bool[] WeakLookup { get { return m_weakLookup; } }
}
}