262 lines
7.9 KiB
C#
262 lines
7.9 KiB
C#
using System;
|
|
using System.Numerics;
|
|
using System.Text;
|
|
|
|
namespace BaseNcoding
|
|
{
|
|
public class BaseBigN : Base
|
|
{
|
|
private BigInteger[] _powN;
|
|
|
|
public bool ReverseOrder { get; }
|
|
|
|
public uint BlockMaxBitsCount { get; }
|
|
|
|
public bool MaxCompression { get; }
|
|
|
|
public override bool HasSpecial => false;
|
|
|
|
public BaseBigN(string alphabet, uint blockMaxBitsCount = 64,
|
|
Encoding encoding = null, bool reverseOrder = false, bool parallel = false,
|
|
bool maxCompression = false)
|
|
: base((uint)alphabet.Length, alphabet, '\0', encoding, parallel)
|
|
{
|
|
BlockMaxBitsCount = blockMaxBitsCount;
|
|
BlockBitsCount = GetOptimalBitsCount(CharsCount, out var charsCountInBits, BlockMaxBitsCount, 2);
|
|
BlockCharsCount = (int)charsCountInBits;
|
|
|
|
PreparePowN(BlockCharsCount);
|
|
|
|
ReverseOrder = reverseOrder;
|
|
MaxCompression = maxCompression;
|
|
}
|
|
|
|
public override string Encode(byte[] data)
|
|
{
|
|
if (data == null || data.Length == 0)
|
|
return "";
|
|
|
|
int blockBitsCount, blockCharsCount;
|
|
if (!MaxCompression)
|
|
{
|
|
blockBitsCount = BlockBitsCount;
|
|
blockCharsCount = BlockCharsCount;
|
|
}
|
|
else
|
|
{
|
|
blockBitsCount = data.Length * 8;
|
|
blockCharsCount = (int)Math.Ceiling(blockBitsCount * Math.Log(2, Alphabet.Length));
|
|
PreparePowN(blockCharsCount);
|
|
}
|
|
|
|
int data8 = data.Length * 8;
|
|
int mainBitsLength = data8 / blockBitsCount * blockBitsCount;
|
|
int tailBitsLength = data8 - mainBitsLength;
|
|
int mainCharsCount = mainBitsLength * blockCharsCount / blockBitsCount;
|
|
int tailCharsCount = (tailBitsLength * blockCharsCount + blockBitsCount - 1) / blockBitsCount;
|
|
int totalCharsCount = mainCharsCount + tailCharsCount;
|
|
int iterationCount = mainCharsCount / blockCharsCount;
|
|
|
|
var result = new char[totalCharsCount];
|
|
|
|
if (!Parallel)
|
|
EncodeBlock(data, result, 0, iterationCount, blockBitsCount, blockCharsCount);
|
|
else
|
|
{
|
|
int processorCount = Math.Min(iterationCount, Environment.ProcessorCount);
|
|
System.Threading.Tasks.Parallel.For(0, processorCount, i =>
|
|
{
|
|
int beginInd = i * iterationCount / processorCount;
|
|
int endInd = (i + 1) * iterationCount / processorCount;
|
|
EncodeBlock(data, result, beginInd, endInd, blockBitsCount, blockCharsCount);
|
|
});
|
|
}
|
|
|
|
if (tailBitsLength != 0)
|
|
{
|
|
BigInteger bits = GetBitsN(data, mainBitsLength, tailBitsLength);
|
|
BitsToChars(result, mainCharsCount, tailCharsCount, bits);
|
|
}
|
|
|
|
return new string(result);
|
|
}
|
|
|
|
public override byte[] Decode(string data)
|
|
{
|
|
if (string.IsNullOrEmpty(data))
|
|
return new byte[0];
|
|
|
|
int blockBitsCount, blockCharsCount;
|
|
if (!MaxCompression)
|
|
{
|
|
blockBitsCount = BlockBitsCount;
|
|
blockCharsCount = BlockCharsCount;
|
|
}
|
|
else
|
|
{
|
|
blockCharsCount = data.Length;
|
|
blockBitsCount = (int)(blockCharsCount / Math.Log(2, Alphabet.Length)) / 8 * 8;
|
|
PreparePowN(blockCharsCount);
|
|
}
|
|
|
|
int totalBitsLength = ((data.Length - 1) * blockBitsCount / blockCharsCount + 8) / 8 * 8;
|
|
int mainBitsLength = totalBitsLength / blockBitsCount * blockBitsCount;
|
|
int tailBitsLength = totalBitsLength - mainBitsLength;
|
|
int mainCharsCount = mainBitsLength * blockCharsCount / blockBitsCount;
|
|
int tailCharsCount = (tailBitsLength * blockCharsCount + blockBitsCount - 1) / blockBitsCount;
|
|
BigInteger tailBits = CharsToBits(data, mainCharsCount, tailCharsCount);
|
|
if (tailBits >> tailBitsLength != 0)
|
|
{
|
|
totalBitsLength += 8;
|
|
mainBitsLength = totalBitsLength / blockBitsCount * blockBitsCount;
|
|
tailBitsLength = totalBitsLength - mainBitsLength;
|
|
mainCharsCount = mainBitsLength * blockCharsCount / blockBitsCount;
|
|
tailCharsCount = (tailBitsLength * blockCharsCount + blockBitsCount - 1) / blockBitsCount;
|
|
}
|
|
int iterationCount = mainCharsCount / blockCharsCount;
|
|
|
|
byte[] result = new byte[totalBitsLength / 8];
|
|
|
|
if (!Parallel)
|
|
{
|
|
DecodeBlock(data, result, 0, iterationCount, blockBitsCount, blockCharsCount);
|
|
}
|
|
else
|
|
{
|
|
int processorCount = Math.Min(iterationCount, Environment.ProcessorCount);
|
|
System.Threading.Tasks.Parallel.For(0, processorCount, i =>
|
|
{
|
|
int beginInd = i * iterationCount / processorCount;
|
|
int endInd = (i + 1) * iterationCount / processorCount;
|
|
DecodeBlock(data, result, beginInd, endInd, blockBitsCount, blockCharsCount);
|
|
});
|
|
}
|
|
|
|
if (tailCharsCount != 0)
|
|
{
|
|
BigInteger bits = CharsToBits(data, mainCharsCount, tailCharsCount);
|
|
AddBitsN(result, bits, mainBitsLength, tailBitsLength);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void EncodeBlock(byte[] src, char[] dst, int beginInd, int endInd, int blockBitsCount, int blockCharsCount)
|
|
{
|
|
for (int ind = beginInd; ind < endInd; ind++)
|
|
{
|
|
int charInd = ind * blockCharsCount;
|
|
int bitInd = ind * blockBitsCount;
|
|
BigInteger bits = GetBitsN(src, bitInd, blockBitsCount);
|
|
BitsToChars(dst, charInd, blockCharsCount, bits);
|
|
}
|
|
}
|
|
|
|
private void DecodeBlock(string src, byte[] dst, int beginInd, int endInd, int blockBitsCount, int blockCharsCount)
|
|
{
|
|
for (int ind = beginInd; ind < endInd; ind++)
|
|
{
|
|
int charInd = ind * blockCharsCount;
|
|
int bitInd = ind * blockBitsCount;
|
|
BigInteger bits = CharsToBits(src, charInd, blockCharsCount);
|
|
AddBitsN(dst, bits, bitInd, blockBitsCount);
|
|
}
|
|
}
|
|
|
|
private static BigInteger GetBitsN(byte[] data, int bitPos, int bitsCount)
|
|
{
|
|
BigInteger result = 0;
|
|
|
|
int currentBytePos = Math.DivRem(bitPos, 8, out int currentBitInBytePos);
|
|
int shift = 8 - currentBitInBytePos;
|
|
int xLength = Math.Min(bitsCount, shift);
|
|
if (xLength != 0)
|
|
{
|
|
result = new BigInteger((data[currentBytePos] >> shift - xLength) & ((1 << shift) - 1)) << bitsCount - xLength;
|
|
|
|
currentBytePos += Math.DivRem(currentBitInBytePos + xLength, 8, out currentBitInBytePos);
|
|
|
|
int x2Length = bitsCount - xLength;
|
|
if (x2Length > 8)
|
|
x2Length = 8;
|
|
|
|
while (x2Length > 0)
|
|
{
|
|
xLength += x2Length;
|
|
result |= (BigInteger)(data[currentBytePos] >> 8 - x2Length) << bitsCount - xLength;
|
|
|
|
currentBytePos += Math.DivRem(currentBitInBytePos + x2Length, 8, out currentBitInBytePos);
|
|
|
|
x2Length = bitsCount - xLength;
|
|
if (x2Length > 8)
|
|
x2Length = 8;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static void AddBitsN(byte[] data, BigInteger value, int bitPos, int bitsCount)
|
|
{
|
|
int currentBytePos = Math.DivRem(bitPos, 8, out int currentBitInBytePos);
|
|
|
|
int shift = 8 - currentBitInBytePos;
|
|
int xLength = Math.Min(bitsCount, shift);
|
|
if (xLength != 0)
|
|
{
|
|
data[currentBytePos] |= (byte) ((byte) (value >> bitsCount - shift) & ((1 << shift) - 1));
|
|
|
|
currentBytePos += Math.DivRem(currentBitInBytePos + xLength, 8, out currentBitInBytePos);
|
|
|
|
int x2Length = bitsCount - xLength;
|
|
if (x2Length > 8)
|
|
x2Length = 8;
|
|
|
|
while (x2Length > 0)
|
|
{
|
|
xLength += x2Length;
|
|
var bitsCountMinusXLength = bitsCount - xLength;
|
|
data[currentBytePos] |= (byte)((value >> (bitsCountMinusXLength - 8 + x2Length)) & 0xFF);
|
|
|
|
currentBytePos += Math.DivRem(currentBitInBytePos + x2Length, 8, out currentBitInBytePos);
|
|
|
|
x2Length = bitsCountMinusXLength;
|
|
if (x2Length > 8)
|
|
x2Length = 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void BitsToChars(char[] chars, int ind, int count, BigInteger block)
|
|
{
|
|
BigInteger quotient = block;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
quotient = BigInteger.DivRem(quotient, CharsCount, out BigInteger remainder);
|
|
chars[ind + (!ReverseOrder ? i : count - 1 - i)] = Alphabet[(int)remainder];
|
|
}
|
|
}
|
|
|
|
private BigInteger CharsToBits(string data, int ind, int count)
|
|
{
|
|
BigInteger result = 0;
|
|
for (int i = 0; i < count; i++)
|
|
result += InvAlphabet[data[ind + (!ReverseOrder ? i : count - 1 - i)]] * _powN[_powN.Length - 1 - i];
|
|
return result;
|
|
}
|
|
|
|
private void PreparePowN(int blockCharsCount)
|
|
{
|
|
_powN = new BigInteger[blockCharsCount];
|
|
BigInteger pow = 1;
|
|
for (int i = 0; i < blockCharsCount - 1; i++)
|
|
{
|
|
_powN[blockCharsCount - 1 - i] = pow;
|
|
pow *= CharsCount;
|
|
}
|
|
if (blockCharsCount > 0)
|
|
_powN[0] = pow;
|
|
}
|
|
}
|
|
}
|