using System.Runtime.InteropServices;
namespace Just.Core;
///
/// URL-safe Base64 encoder/decoder for bytes, , and .
/// Uses the standard Base64Url character set (- and _ instead of + and /), padding stripped by default.
///
public static class Base64Url
{
private const char Padding = '=';
/// Decodes an 11-character Base64Url string into a .
[Pure] public static long DecodeLong(ReadOnlySpan value)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(value.Length, 11);
Span longBytes = stackalloc byte[8];
Span chars = stackalloc char[12];
value.CopyTo(chars);
chars[^1] = Padding;
ReplaceNonUrlChars(chars);
if (!Convert.TryFromBase64Chars(chars, longBytes, out int _))
throw new FormatException("Invalid Base64 string.");
return MemoryMarshal.Read(longBytes);
}
/// Decodes a 22-character Base64Url string into a .
[Pure] public static Guid DecodeGuid(ReadOnlySpan value)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(value.Length, 22);
Span guidBytes = stackalloc byte[16];
Span chars = stackalloc char[24];
value.CopyTo(chars);
chars[^2..].Fill(Padding);
ReplaceNonUrlChars(chars);
if (!Convert.TryFromBase64Chars(chars, guidBytes, out int _))
throw new FormatException("Invalid Base64 string.");
return new Guid(guidBytes);
}
/// Decodes a Base64Url string into a byte array.
[Pure] public static byte[] Decode(ReadOnlySpan input)
{
if (input.IsEmpty) return [];
Span output = stackalloc byte[3 * ((input.Length + 3) / 4)];
var size = Decode(input, output);
return output[..size].ToArray();
}
/// Decodes a Base64Url string into a pre-allocated byte span. Returns the number of bytes written.
[Pure] public static int Decode(ReadOnlySpan value, Span output)
{
var padding = (4 - (value.Length & 3)) & 3;
var charlen = value.Length + padding;
var outputBytes = 3 * (charlen / 4);
ArgumentOutOfRangeException.ThrowIfLessThan(output.Length, outputBytes);
Span chars = stackalloc char[charlen];
value.CopyTo(chars);
chars[^padding..].Fill(Padding);
ReplaceNonUrlChars(chars);
if (!Convert.TryFromBase64Chars(chars, output, out outputBytes))
throw new FormatException("Invalid Base64 string.");
return outputBytes;
}
/// Encodes a into an 11-character Base64Url string.
[Pure] public static string Encode(in long id)
{
Span longBytes = stackalloc byte[8];
MemoryMarshal.Write(longBytes, id);
Span chars = stackalloc char[12];
Convert.TryToBase64Chars(longBytes, chars, out int _);
ReplaceUrlChars(chars[..^1]);
return new string(chars[..^1]);
}
/// Encodes a into a 22-character Base64Url string.
[Pure] public static string Encode(in Guid id)
{
Span guidBytes = stackalloc byte[16];
id.TryWriteBytes(guidBytes);
Span chars = stackalloc char[24];
Convert.TryToBase64Chars(guidBytes, chars, out int _);
ReplaceUrlChars(chars[..^2]);
return new string(chars[..^2]);
}
/// Encodes a byte span into a Base64Url string.
[Pure] public static string Encode(ReadOnlySpan input)
{
if (input.IsEmpty) return string.Empty;
int outLength = 4 * ((input.Length + 2) / 3);
Span output = stackalloc char[outLength];
int strlen = Encode(input, output);
return new string(output[..strlen]);
}
/// Encodes a byte span into a pre-allocated char span. Returns the number of characters written.
[Pure] public static int Encode(ReadOnlySpan input, Span output)
{
if (input.IsEmpty) return 0;
var charlen = 4 * ((input.Length + 2) / 3);
Span chars = stackalloc char[charlen];
Convert.TryToBase64Chars(input, chars, out int charsWritten);
int i = ReplaceUrlChars(chars[..charsWritten]);
chars[..i].CopyTo(output);
return i;
}
private static int ReplaceUrlChars(Span chars)
{
int i = 0;
for (; i < chars.Length; i++)
{
switch (chars[i])
{
case '+': chars[i] = '-'; continue;
case '/': chars[i] = '_'; continue;
case Padding: goto break_loop;
default: continue;
}
}
break_loop:
return i;
}
private static int ReplaceNonUrlChars(Span chars)
{
int i = 0;
for (; i < chars.Length; i++)
{
switch (chars[i])
{
case '-': chars[i] = '+'; continue;
case '_': chars[i] = '/'; continue;
case Padding: goto break_loop;
default: continue;
}
}
break_loop:
return i;
}
}