using System; | |
using System.Buffers.Binary; | |
using System.Runtime.CompilerServices; | |
namespace Base32 | |
{ | |
public class Base32Encoding | |
{ | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public string Encode(ReadOnlySpan<byte> bytes) | |
{ | |
var groupLength = (bytes.Length + 4) / 5; // 0 -> 0, (1..5) -> 1, (6..10) -> 2 ... | |
var output = (groupLength <= 64) ? stackalloc char[groupLength * 8] : new char[groupLength * 8]; | |
for (var i = 0; i < groupLength; i++) | |
Convert(bytes.Slice(i * 5), output.Slice(i * 8, 8)); | |
const char paddingChar = '='; | |
var paddingLength = (5 - bytes.Length % 5) * 8 / 5 & 0x07; | |
output.Slice(output.Length - paddingLength).Fill(paddingChar); | |
return output.ToString(); | |
} | |
[MethodImpl(MethodImplOptions.NoInlining)] | |
private static void Convert(ReadOnlySpan<byte> input, Span<char> output) | |
{ | |
var buffer = (Span<byte>)stackalloc byte[8]; // as ulong (8 byte) | |
var maxInputLength = Math.Min(5, input.Length); // max 5 byte == 40 bit | |
input.Slice(0, maxInputLength).CopyTo(buffer); | |
var bitArray = BinaryPrimitives.ReadUInt64BigEndian(buffer); | |
const int charBit = 5; // 5 bit * 8 == 40 bit | |
for (var (i, shift) = (0, 64 - charBit); i < output.Length; (i, shift) = (i + 1, shift - charBit)) | |
output[i] = ToBase32Char((int)(bitArray >> shift) & 0b11111); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static char ToBase32Char(int value) | |
=> (char)((value < 26) ? value + 'A' : value - 26 + '2'); // A to Z, 2 to 7 | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public byte[] Decode(string data) | |
{ | |
var count = data.Length / 8; | |
var paddingIndex = data.IndexOf('='); | |
var paddingLength = (paddingIndex < 0) ? 0 : ((data.Length - paddingIndex) * 5 + 7) / 8; | |
var output = new byte[count * 5 - paddingLength]; | |
this.Decode(data, output.AsSpan()); | |
return output; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public void Decode(string data, Span<byte> output) | |
=> this.Decode(data.AsSpan(), output); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public void Decode(ReadOnlySpan<char> input, Span<byte> output) | |
{ | |
if ((input.Length & 0x07) != 0) | |
throw new ArgumentException(); | |
var count = input.Length / 8; | |
for (var i = 0; i < count; i++) | |
Build(input.Slice(i * 8, 8), output.Slice(i * 5)); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static void Build(ReadOnlySpan<char> input, Span<byte> output) | |
{ | |
const int charBit = 5; | |
var binary = default(ulong); | |
foreach (var value in input) | |
binary = (binary << charBit) ^ (ulong)(ToBinary(value) & 0b11111); | |
var result = (Span<byte>)stackalloc byte[8]; | |
BinaryPrimitives.WriteUInt64BigEndian(result, binary); | |
var outputLength = Math.Min(5, output.Length); | |
result.Slice(3, outputLength).CopyTo(output); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static int ToBinary(char value) | |
=> (value < 'A') ? value - '2' + 26 : value - 'A'; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment