using System;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.Assertions;
namespace Unity.Networking.Transport
{
public static class Base64
{
///
/// Decode characters representing a Base64 encoding into bytes. Will throw ArgumentException on any error
///
/// Pointer to first input char in UTF16 string (C# strings)
/// Number of input chars
/// Pointer to location for teh first result byte
/// Max length of the preallocated result buffer
/// Actually written bytes to startDestPtr that is less or equal than destLength
private static unsafe int FromBase64_Decode_UTF16(byte* startInputPtr, int inputLength, byte* startDestPtr, int destLength)
{
if (inputLength == 0)
return 0;
const int sizeCharUTF16 = 2;
// 3 bytes == 4 chars in the input base64 string
if (inputLength % 4 != 0)
throw new ArgumentException("Base64 string's length must be multiple of 4");
if (destLength < inputLength / 4 * 3 - 2)
throw new ArgumentException("Dest array is too small");
var originalStartDestPtr = startDestPtr;
var n = inputLength / 4;
const string table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var lookup = stackalloc byte[256];
UnsafeUtility.MemSet(lookup, 0xFF, 256);
for (byte i = 0; i < table.Length; i++)
lookup[table[i]] = i;
lookup['='] = 0;
// skip last 4 chars
for (var i = 0; i < n - 1; i++)
{
byte a = lookup[startInputPtr[0 * sizeCharUTF16]];
byte b = lookup[startInputPtr[1 * sizeCharUTF16]];
byte c = lookup[startInputPtr[2 * sizeCharUTF16]];
byte d = lookup[startInputPtr[3 * sizeCharUTF16]];
if (a == 0xFF || b == 0xFF || c == 0xFF || d == 0xFF)
throw new ArgumentException("Invalid Base64 symbol");
*startDestPtr++ = (byte) ((a << 2) | (b >> 4));
*startDestPtr++ = (byte) ((b << 4) | (c >> 2));
*startDestPtr++ = (byte) ((c << 6) | d);
startInputPtr += 4 * sizeCharUTF16;
}
// last 4 chars
var cc = startInputPtr[2 * sizeCharUTF16];
var dd = startInputPtr[3 * sizeCharUTF16];
var la = lookup[startInputPtr[0 * sizeCharUTF16]];
var lb = lookup[startInputPtr[1 * sizeCharUTF16]];
var lc = lookup[cc];
var ld = lookup[dd];
if (la == 0xFF || lb == 0xFF || lc == 0xFF || ld == 0xFF)
throw new ArgumentException("Invalid Base64 symbol");
*startDestPtr++ = (byte) ((la << 2) | (lb >> 4));
if (cc != '=') // == means 4 chars == 1 byte, we already wrote that
{
if (dd == '=') // = means 4 chars == 2 bytes, 1 more
{
if (destLength < inputLength / 4 * 3 - 1)
throw new ArgumentException("Dest array is too small");
*startDestPtr++ = (byte) ((lb << 4) | (lc >> 2));
}
else // no padding, 4 chars == 3 bytes, 2 more
{
if (destLength < inputLength / 4 * 3)
throw new ArgumentException("Dest array is too small");
*startDestPtr++ = (byte) ((lb << 4) | (lc >> 2));
*startDestPtr++ = (byte) ((lc << 6) | ld);
}
}
return (int) (startDestPtr - originalStartDestPtr);
}
///
/// Decodes base64 string and writes binary data into dest
///
/// Input base64 string to decode
/// Decoded base64 will be written here
/// Max length that dest can handle. Will throw if not enough
/// Actual length of data that was written to dest. Less or equal than destLength
public static unsafe int FromBase64String(string base64, byte* dest, int destMaxLength)
{
fixed (char* ptr = base64)
{
return FromBase64_Decode_UTF16((byte*)ptr, base64.Length, dest, destMaxLength);
}
}
}
}