#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,
}
}
}