All checks were successful
continuous-integration/drone/push Build is passing
245 lines
7.2 KiB
C#
245 lines
7.2 KiB
C#
using System;
|
|
using System.Text;
|
|
|
|
namespace Cinema.Webshare;
|
|
|
|
/// <summary>
|
|
/// Performs generic binary-to-text encoding.
|
|
/// </summary>
|
|
class BaseEncoding : Encoding
|
|
{
|
|
int _bitCount;
|
|
int _bitMask;
|
|
string _characters;
|
|
bool _msbComesFirst;
|
|
Dictionary<char, int> _values;
|
|
|
|
/// <summary>
|
|
/// Defines a binary-to-text encoding.
|
|
/// </summary>
|
|
/// <param name="characterSet">The characters of the encoding.</param>
|
|
/// <param name="msbComesFirst">
|
|
/// <c>true</c> to begin with the most-significant bit of each byte.
|
|
/// Otherwise, the encoding begins with the least-significant bit.
|
|
/// </param>
|
|
public BaseEncoding(string characterSet, bool msbComesFirst)
|
|
: this(characterSet, msbComesFirst, null)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines a binary-to-text encoding.
|
|
/// Additional decode characters let you add aliases, and a filter callback can be used
|
|
/// to make decoding case-insensitive among other things.
|
|
/// </summary>
|
|
/// <param name="characterSet">The characters of the encoding.</param>
|
|
/// <param name="msbComesFirst">
|
|
/// <c>true</c> to begin with the most-significant bit of each byte.
|
|
/// Otherwise, the encoding begins with the least-significant bit.
|
|
/// </param>
|
|
/// <param name="additionalDecodeCharacters">
|
|
/// A dictionary of alias characters, or <c>null</c> if no aliases are desired.
|
|
/// </param>
|
|
/// <param name="decodeFilterCallback">
|
|
/// A callback to map arbitrary characters onto the characters that can be decoded.
|
|
/// </param>
|
|
public BaseEncoding(string characterSet, bool msbComesFirst,
|
|
IDictionary<char, int> additionalDecodeCharacters)
|
|
{
|
|
if (characterSet == null)
|
|
throw new ArgumentNullException();
|
|
|
|
if (!IsPositivePowerOf2(characterSet.Length))
|
|
{
|
|
throw new ArgumentException("Length must be a power of 2.", "characterSet");
|
|
}
|
|
|
|
if (characterSet.Length > 256)
|
|
{
|
|
throw new ArgumentException("Character sets with over 256 characters are not supported.", "characterSet");
|
|
}
|
|
|
|
_bitCount = 31 - CountLeadingZeros(characterSet.Length);
|
|
_bitMask = (1 << _bitCount) - 1;
|
|
_characters = characterSet;
|
|
_msbComesFirst = msbComesFirst;
|
|
|
|
_values = additionalDecodeCharacters != null
|
|
? new Dictionary<char, int>(additionalDecodeCharacters)
|
|
: new Dictionary<char, int>();
|
|
for (int i = 0; i < characterSet.Length; i++)
|
|
{
|
|
char ch = characterSet[i];
|
|
if (_values.ContainsKey(ch))
|
|
{
|
|
throw new ArgumentException("Duplicate characters are not supported.", "characterSet");
|
|
}
|
|
_values.Add(ch, (byte)i);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the value corresponding to the specified character.
|
|
/// </summary>
|
|
/// <param name="character">A character.</param>
|
|
/// <returns>A value, or <c>-1</c> if the character is not part of the encoding.</returns>
|
|
public virtual int GetValue(char character)
|
|
{
|
|
int value;
|
|
return _values.TryGetValue(character, out value) ? value : -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the character corresponding to the specified value.
|
|
/// </summary>
|
|
/// <param name="value">A value.</param>
|
|
/// <returns>A character.</returns>
|
|
public virtual char GetChar(int value)
|
|
{
|
|
return _characters[value & BitMask];
|
|
}
|
|
|
|
/// <summary>
|
|
/// The bit mask for a single character in the current encoding.
|
|
/// </summary>
|
|
public int BitMask
|
|
{
|
|
get { return _bitMask; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number of bits per character in the current encoding.
|
|
/// </summary>
|
|
public int BitsPerCharacter
|
|
{
|
|
get { return _bitCount; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if the encoding begins with the most-significant bit of each byte.
|
|
/// Otherwise, the encoding begins with the least-significant bit.
|
|
/// </summary>
|
|
public bool MsbComesFirst
|
|
{
|
|
get { return _msbComesFirst; }
|
|
}
|
|
|
|
public override int GetMaxCharCount(int byteCount)
|
|
{
|
|
if (byteCount < 0)
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
return checked((byteCount * 8 + BitsPerCharacter - 1) / BitsPerCharacter);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
|
|
{
|
|
int charCount = GetCharCount(bytes, byteIndex, byteCount);
|
|
|
|
return GetChars(bytes, byteIndex, byteCount, chars, charIndex, charCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts bytes from their binary representation to a text representation.
|
|
/// </summary>
|
|
/// <param name="bytes">An input array of bytes.</param>
|
|
/// <param name="byteIndex">The index of the first byte.</param>
|
|
/// <param name="byteCount">The number of bytes to read.</param>
|
|
/// <param name="chars">An output array of characters.</param>
|
|
/// <param name="charIndex">The index of the first character.</param>
|
|
/// <param name="charCount">The number of characters to write.</param>
|
|
/// <returns>The number of characters written.</returns>
|
|
public int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex, int charCount)
|
|
{
|
|
if (charIndex < 0 || charCount < 0 || chars.Length - charIndex < charCount
|
|
|| byteIndex < 0 || byteCount < 0 || bytes.Length - byteIndex < byteCount)
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
int byteEnd = checked(byteIndex + byteCount);
|
|
|
|
int bitStartOffset = 0;
|
|
for (int i = 0; i < charCount; i++)
|
|
{
|
|
byte value;
|
|
|
|
byte thisByte = byteIndex + 0 < byteEnd ? bytes[byteIndex + 0] : (byte)0;
|
|
byte nextByte = byteIndex + 1 < byteEnd ? bytes[byteIndex + 1] : (byte)0;
|
|
|
|
int bitEndOffset = bitStartOffset + BitsPerCharacter;
|
|
if (MsbComesFirst)
|
|
{
|
|
value = ShiftRight(thisByte, 8 - bitStartOffset - BitsPerCharacter);
|
|
if (bitEndOffset > 8)
|
|
{
|
|
value |= ShiftRight(nextByte, 16 - bitStartOffset - BitsPerCharacter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
value = ShiftRight(thisByte, bitStartOffset);
|
|
if (bitEndOffset > 8)
|
|
{
|
|
value |= ShiftRight(nextByte, bitStartOffset - 8);
|
|
}
|
|
}
|
|
|
|
bitStartOffset = bitEndOffset;
|
|
if (bitStartOffset >= 8)
|
|
{
|
|
bitStartOffset -= 8; byteIndex++;
|
|
}
|
|
|
|
chars[i] = GetChar(value);
|
|
}
|
|
|
|
return charCount;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override int GetCharCount(byte[] bytes, int index, int count)
|
|
{
|
|
if (index < 0 || count < 0 || bytes.Length - index < count)
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
return GetMaxCharCount(count);
|
|
}
|
|
|
|
public override int GetByteCount(char[] chars, int index, int count)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public override int GetMaxByteCount(int charCount)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
private static bool IsPositivePowerOf2(int value)
|
|
{
|
|
return 0 < value && 0 == (value & (value - 1));
|
|
}
|
|
|
|
private static int CountLeadingZeros(int value)
|
|
{
|
|
int count;
|
|
for (count = 0; count < 32 && 0 == (value & (0x80000000 >> count)); count++) ;
|
|
return count;
|
|
}
|
|
|
|
private static byte ShiftLeft(byte value, int bits)
|
|
{
|
|
return (byte)(bits > 0 ? value << bits : value >> (-bits));
|
|
}
|
|
|
|
private static byte ShiftRight(byte value, int bits)
|
|
{
|
|
return ShiftLeft(value, -bits);
|
|
}
|
|
}
|