#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 contains various values and methods used for reading and writing /// RDiff compatible binary files /// public class RDiffBinary { /// /// A small helper to determine the endianness of the machine /// private static readonly byte[] ENDIAN = BitConverter.GetBytes((short)1); /// /// The magic header value in an RDiff signature file /// public static readonly byte[] SIGNATURE_MAGIC = { (byte)'r', (byte)'s', 0x1, (byte)'6' }; /// /// The magic header value in an RDiff delta file /// public static readonly byte[] DELTA_MAGIC = { (byte)'r', (byte)'s', 0x2, (byte)'6' }; /// /// Denotes the end of a delta stream. /// public const byte EndCommand = 0x0; /// /// This is the max number of literal bytes that can be encoded without having the size present. /// The delta files created from this library does not contain these, but they can be read. /// public static byte LiteralLimit = 0x40; /// /// This is a lookup table to find a copy command. /// The first index is size of the offset in the original file, /// the second index is the size of the length of the block to copy. /// public static readonly CopyDeltaCommand[][] CopyCommand = { new CopyDeltaCommand[] {CopyDeltaCommand.Copy_Byte_Byte, CopyDeltaCommand.Copy_Short_Byte, CopyDeltaCommand.Copy_Int_Byte, CopyDeltaCommand.Copy_Long_Byte }, new CopyDeltaCommand[] {CopyDeltaCommand.Copy_Byte_Short, CopyDeltaCommand.Copy_Short_Short, CopyDeltaCommand.Copy_Int_Short, CopyDeltaCommand.Copy_Long_Short}, new CopyDeltaCommand[] {CopyDeltaCommand.Copy_Byte_Int, CopyDeltaCommand.Copy_Short_Int, CopyDeltaCommand.Copy_Int_Int, CopyDeltaCommand.Copy_Long_Int }, new CopyDeltaCommand[] {CopyDeltaCommand.Copy_Byte_Long, CopyDeltaCommand.Copy_Short_Long, CopyDeltaCommand.Copy_Int_Long, CopyDeltaCommand.Copy_Long_Long }, }; /// /// Determines the number of bytes required to encode the given size /// /// The length to encode /// The number of bytes required public static int FindLength(long size) { int count = 1; if (size > byte.MaxValue) count++; if (size > ushort.MaxValue) count += 2; if (size > uint.MaxValue) count += 4; if (size > long.MaxValue) throw new Exception(string.Format(Strings.RDiffBinary.ValueTooLargeError, long.MaxValue)); return count; } /// /// Decodes the size parameter. /// /// The data to decode /// The decoded size value public static long DecodeLength(byte[] data) { if (data.Length == 1) return (long)data[0]; else if (data.Length == 2) return (long)BitConverter.ToUInt16(FixEndian(data), 0); else if (data.Length == 4) return (long)BitConverter.ToUInt32(FixEndian(data), 0); else if (data.Length == 8) { long tmp = BitConverter.ToInt64(FixEndian(data), 0); if (tmp < 0) throw new Exception(string.Format(Strings.RDiffBinary.SizeTooLargeError, long.MaxValue)); return tmp; } else throw new Exception(Strings.RDiffBinary.InvalidDataLengthError); } /// /// Returns an encoded array with the given size value, /// using the least possible number of bytes. /// The value is fixed with regards to endianness. /// /// The value to write /// The written bytes public static byte[] EncodeLength(long size) { int len = FindLength(size); if (len == 1) return new byte[] { (byte)size }; else if (len == 2) return FixEndian(BitConverter.GetBytes((short)size)); else if (len == 4) return FixEndian(BitConverter.GetBytes((int)size)); else if (len == 8) return FixEndian(BitConverter.GetBytes((long)size)); else throw new Exception(Strings.RDiffBinary.EncodedLengthError); } /// /// Returns the correct delta copy command, given the offset and size. /// /// The offset in the file where the copy begins /// The size of the copy /// The delta copy command public static CopyDeltaCommand FindCopyDeltaCommand(long offset, long size) { int i1 = FindLength(offset); int i2 = FindLength(size); if (i1 == 4) i1 = 3; if (i1 == 8) i1 = 4; if (i2 == 4) i2 = 3; if (i2 == 8) i2 = 4; return CopyCommand[i2 - 1][i1 - 1]; } /// /// Returns the literal delta command for the given size /// /// The number that is to be encoded /// The literal delta command public static LiteralDeltaCommand FindLiteralDeltaCommand(long size) { switch (FindLength(size)) { case 1: return LiteralDeltaCommand.LiteralSizeByte; case 2: return LiteralDeltaCommand.LiteralSizeShort; case 4: return LiteralDeltaCommand.LiteralSizeInt; case 8: return LiteralDeltaCommand.LiteralSizeLong; default: throw new Exception("Invalid size for encoded value!"); } } /// /// Returns the number of bytes the copy commands offset argument occupies. /// /// The copy command to evaluate /// The number of bytes the copy commands offset argument occupies. public static int GetCopyOffsetSize(CopyDeltaCommand command) { switch (command) { case CopyDeltaCommand.Copy_Byte_Byte: case CopyDeltaCommand.Copy_Byte_Short: case CopyDeltaCommand.Copy_Byte_Int: case CopyDeltaCommand.Copy_Byte_Long: return 1; case CopyDeltaCommand.Copy_Short_Byte: case CopyDeltaCommand.Copy_Short_Short: case CopyDeltaCommand.Copy_Short_Int: case CopyDeltaCommand.Copy_Short_Long: return 2; case CopyDeltaCommand.Copy_Int_Byte: case CopyDeltaCommand.Copy_Int_Short: case CopyDeltaCommand.Copy_Int_Int: case CopyDeltaCommand.Copy_Int_Long: return 4; case CopyDeltaCommand.Copy_Long_Byte: case CopyDeltaCommand.Copy_Long_Short: case CopyDeltaCommand.Copy_Long_Int: case CopyDeltaCommand.Copy_Long_Long: return 8; default: throw new Exception(string.Format(Strings.RDiffBinary.InvalidDeltaCopyCommandError, command)); } } /// /// Returns the number of bytes the copy commands length argument occupies. /// /// The copy command to evaluate /// The number of bytes the copy commands length argument occupies. public static int GetCopyLengthSize(CopyDeltaCommand command) { switch (command) { case CopyDeltaCommand.Copy_Byte_Byte: case CopyDeltaCommand.Copy_Short_Byte: case CopyDeltaCommand.Copy_Int_Byte: case CopyDeltaCommand.Copy_Long_Byte: return 1; case CopyDeltaCommand.Copy_Byte_Short: case CopyDeltaCommand.Copy_Short_Short: case CopyDeltaCommand.Copy_Int_Short: case CopyDeltaCommand.Copy_Long_Short: return 2; case CopyDeltaCommand.Copy_Byte_Int: case CopyDeltaCommand.Copy_Short_Int: case CopyDeltaCommand.Copy_Int_Int: case CopyDeltaCommand.Copy_Long_Int: return 4; case CopyDeltaCommand.Copy_Byte_Long: case CopyDeltaCommand.Copy_Short_Long: case CopyDeltaCommand.Copy_Int_Long: case CopyDeltaCommand.Copy_Long_Long: return 8; default: throw new Exception(string.Format(Strings.RDiffBinary.InvalidDeltaCopyCommandError, command)); } } /// /// Returns the number of bytes the literal command's size argument fills /// /// The command to find the size for /// The number of bytes the literal command's size argument fills public static int GetLiteralLength(LiteralDeltaCommand command) { switch (command) { case LiteralDeltaCommand.LiteralSizeByte: return 1; case LiteralDeltaCommand.LiteralSizeShort: return 2; case LiteralDeltaCommand.LiteralSizeInt: return 4; case LiteralDeltaCommand.LiteralSizeLong: return 8; default: throw new Exception(string.Format(Strings.RDiffBinary.InvalidLiteralCommand, command)); } } /// /// Reverses endian order, if required /// /// The data to reverse /// The reversed data public static byte[] FixEndian(byte[] data) { if (ENDIAN[0] != 0) Array.Reverse(data); return data; } /// /// This table contains all the delta commands for literal data /// public enum LiteralDeltaCommand : byte { /// /// The next byte contains the number of bytes with literal data that follows /// LiteralSizeByte = 0x41, /// /// The next short contains the number of bytes with literal data that follows /// LiteralSizeShort = 0x42, /// /// The next integer contains the number of bytes with literal data that follows /// LiteralSizeInt = 0x43, /// /// The next long contains the number of bytes with literal data that follows /// LiteralSizeLong = 0x44, } /// /// These are the possible commands to specify a copy. /// The first item is the offset in the original file, /// the second item is the length of the block to copy. /// public enum CopyDeltaCommand : byte { Copy_Byte_Byte = 0x45, Copy_Byte_Short = 0x46, Copy_Byte_Int = 0x47, Copy_Byte_Long = 0x48, Copy_Short_Byte = 0x49, Copy_Short_Short = 0x4a, Copy_Short_Int = 0x4b, Copy_Short_Long = 0x4c, Copy_Int_Byte = 0x4d, Copy_Int_Short = 0x4e, Copy_Int_Int = 0x4f, Copy_Int_Long = 0x50, Copy_Long_Byte = 0x51, Copy_Long_Short = 0x52, Copy_Long_Int = 0x53, Copy_Long_Long = 0x54, } } }