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; } } } }