#region Disclaimer / License // Copyright (C) 2011, Kenneth Skovhede // http://www.hexad.dk, opensource@hexad.dk // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion 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 is a list of all weak values, sorted by value /// private uint[] m_weakValues; /// /// This list contains index values for and is sorted to match the index of /// private long[] m_weakToStrongIndex; /// /// 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; /// /// Reads a ChecksumFile from a stream /// /// The stream to read from public ChecksumFileReader(System.IO.Stream input) { m_hashAlgorithm = MD4Helper.Create(); 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; KeyComparer 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++; } m_strongIndex = tempStrongIndex.ToArray(); tempStrongIndex.Clear(); tempStrongIndex = null; tempWeakTable.Sort(comparer); m_weakValues = new uint[tempWeakTable.Count]; m_weakToStrongIndex = new long[m_weakValues.Length]; //Initialize the weakest lookup table for (int i = 0; i < tempWeakTable.Count; i++) { m_weakValues[i] = tempWeakTable[i].Key; m_weakToStrongIndex[i] = tempWeakTable[i].Value; m_weakLookup[m_weakValues[i] >> 16] = true; } tempWeakTable.Clear(); tempWeakTable = null; } /// /// A comparer that allows sorting a list/array of KeyValuePair items /// /// The key type /// 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; long sh; if (m_weakValues.LongLength <= int.MaxValue) { //Do a O(log n) lookup on the weak hash, a hashtable would have O(1), but requires too much memory sh = Array.BinarySearch(m_weakValues, weakChecksum); } else { //If the table gets too big, Array.BinarySearch fails because it can only return int values //Below is a custom binary search algorithm long maxIndex = m_weakValues.LongLength - 1; long minIndex = 0; sh = -1; while (minIndex < maxIndex) { long testIndex = ((maxIndex - minIndex) / 2) + minIndex; if (m_weakValues[testIndex] == weakChecksum) { sh = testIndex; break; } else if (m_weakValues[testIndex] > weakChecksum) maxIndex = testIndex - 1; else minIndex = testIndex + 1; } if (sh == -1) sh = ~minIndex; } 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_weakValues.LongLength && m_weakValues[sh] == weakChecksum) { long index = m_weakToStrongIndex[sh]; 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_weakValues[sh] == weakChecksum) { long index = m_weakToStrongIndex[sh]; 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--; } //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; } } } }