182 lines
4.5 KiB
C#
182 lines
4.5 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace BaseNcoding
|
|
{
|
|
public class Base85 : Base
|
|
{
|
|
public const string DefaultAlphabet = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstu";
|
|
public const char DefaultSpecial = (char)0;
|
|
private static readonly uint[] Pow85 = { 85 * 85 * 85 * 85, 85 * 85 * 85, 85 * 85, 85, 1 };
|
|
|
|
public const string Prefix = "<~";
|
|
public const string Postfix = "~>";
|
|
|
|
public override bool HasSpecial => false;
|
|
|
|
public bool PrefixPostfix { get; set; }
|
|
|
|
public Base85(string alphabet = DefaultAlphabet, char special = DefaultSpecial, bool prefixPostfix = false, Encoding textEncoding = null)
|
|
: base(85, alphabet, special, textEncoding)
|
|
{
|
|
PrefixPostfix = prefixPostfix;
|
|
BlockBitsCount = 32;
|
|
BlockCharsCount = 5;
|
|
}
|
|
|
|
public override string Encode(byte[] data)
|
|
{
|
|
unchecked
|
|
{
|
|
byte[] encodedBlock = new byte[5];
|
|
int decodedBlockLength = 4;
|
|
int resultLength = data.Length * (encodedBlock.Length / decodedBlockLength);
|
|
if (PrefixPostfix)
|
|
resultLength += Prefix.Length + Postfix.Length;
|
|
char[] result = new char[resultLength];
|
|
int resultInd = 0;
|
|
|
|
if (PrefixPostfix)
|
|
CopyString(Prefix, result, ref resultInd);
|
|
|
|
int count = 0;
|
|
uint tuple = 0;
|
|
foreach (byte b in data)
|
|
{
|
|
if (count >= decodedBlockLength - 1)
|
|
{
|
|
tuple |= b;
|
|
if (tuple == 0)
|
|
result[resultInd++] = 'z';
|
|
else
|
|
EncodeBlock(encodedBlock.Length, result, ref resultInd, encodedBlock, tuple);
|
|
tuple = 0;
|
|
count = 0;
|
|
}
|
|
else
|
|
{
|
|
tuple |= (uint)(b << (24 - count * 8));
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (count > 0)
|
|
EncodeBlock(count + 1, result, ref resultInd, encodedBlock, tuple);
|
|
|
|
if (PrefixPostfix)
|
|
CopyString(Postfix, result, ref resultInd);
|
|
|
|
return new string(result);
|
|
}
|
|
}
|
|
|
|
public override byte[] Decode(string data)
|
|
{
|
|
unchecked
|
|
{
|
|
string dataWithoutPrefixPostfix = data;
|
|
if (PrefixPostfix)
|
|
{
|
|
if (!dataWithoutPrefixPostfix.StartsWith(Prefix) || !dataWithoutPrefixPostfix.EndsWith(Postfix))
|
|
{
|
|
throw new Exception("ASCII85 encoded data should begin with '" + Prefix + "' and end with '" + Postfix + "'");
|
|
}
|
|
dataWithoutPrefixPostfix = dataWithoutPrefixPostfix.Substring(Prefix.Length, dataWithoutPrefixPostfix.Length - Prefix.Length - Postfix.Length);
|
|
}
|
|
|
|
MemoryStream ms = new MemoryStream();
|
|
int count = 0;
|
|
|
|
uint tuple = 0;
|
|
int encodedBlockLength = 5;
|
|
byte[] decodedBlock = new byte[4];
|
|
foreach (char c in dataWithoutPrefixPostfix)
|
|
{
|
|
bool processChar;
|
|
switch (c)
|
|
{
|
|
case 'z':
|
|
if (count != 0)
|
|
{
|
|
throw new Exception("The character 'z' is invalid inside an ASCII85 block.");
|
|
}
|
|
decodedBlock[0] = 0;
|
|
decodedBlock[1] = 0;
|
|
decodedBlock[2] = 0;
|
|
decodedBlock[3] = 0;
|
|
ms.Write(decodedBlock, 0, decodedBlock.Length);
|
|
processChar = false;
|
|
break;
|
|
default:
|
|
processChar = true;
|
|
break;
|
|
}
|
|
|
|
if (processChar)
|
|
{
|
|
tuple += (uint)InvAlphabet[c] * Pow85[count];
|
|
count++;
|
|
if (count == encodedBlockLength)
|
|
{
|
|
DecodeBlock(decodedBlock.Length, decodedBlock, tuple);
|
|
ms.Write(decodedBlock, 0, decodedBlock.Length);
|
|
tuple = 0;
|
|
count = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count != 0)
|
|
{
|
|
if (count == 1)
|
|
{
|
|
throw new Exception("The last block of ASCII85 data cannot be a single byte.");
|
|
}
|
|
count--;
|
|
tuple += Pow85[count];
|
|
DecodeBlock(count, decodedBlock, tuple);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
ms.WriteByte(decodedBlock[i]);
|
|
}
|
|
}
|
|
|
|
return ms.ToArray();
|
|
}
|
|
}
|
|
|
|
private static void CopyString(string source, char[] dest, ref int destInd)
|
|
{
|
|
for (int i = 0; i < source.Length; i++)
|
|
dest[destInd++] = source[i];
|
|
}
|
|
|
|
private void EncodeBlock(int count, char[] result, ref int resultInd, byte[] encodedBlock, uint tuple)
|
|
{
|
|
unchecked
|
|
{
|
|
for (int i = encodedBlock.Length - 1; i >= 0; i--)
|
|
{
|
|
uint quotient = tuple % 85;
|
|
uint remainder = tuple - quotient * 85;
|
|
tuple = quotient;
|
|
encodedBlock[i] = (byte)remainder;
|
|
}
|
|
|
|
for (int i = 0; i < count; i++)
|
|
result[resultInd++] = Alphabet[encodedBlock[i]];
|
|
}
|
|
}
|
|
|
|
private void DecodeBlock(int bytes, byte[] decodedBlock, uint tuple)
|
|
{
|
|
unchecked
|
|
{
|
|
for (int i = 0; i < bytes; i++)
|
|
decodedBlock[i] = (byte)(tuple >> 24 - i * 8);
|
|
}
|
|
}
|
|
}
|
|
}
|