Last active
January 7, 2019 06:27
-
-
Save acple/33fb4493f1f4988cb63677aeb96527bb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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