Skip to content

Instantly share code, notes, and snippets.

@acple
Last active January 7, 2019 06:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save acple/33fb4493f1f4988cb63677aeb96527bb to your computer and use it in GitHub Desktop.
Save acple/33fb4493f1f4988cb63677aeb96527bb to your computer and use it in GitHub Desktop.
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