using System.Buffers;
using System.Text;
namespace SpaceWizards.Sodium;
using static Interop.Libsodium;
public static class SodiumHelpers
{
static SodiumHelpers()
{
SodiumCore.EnsureInit();
}
// TODO: hex2bin and base642bin
///
/// sodium_memcmp
///
public static unsafe bool MemoryCompare(ReadOnlySpan b1, ReadOnlySpan b2)
{
if (b1.Length != b2.Length)
throw new ArgumentException("Both input spans must be the same length.");
fixed (byte* b1Ptr = b1)
fixed (byte* b2Ptr = b2)
{
return sodium_memcmp(b1Ptr, b2Ptr, (nuint)b1.Length) == 0;
}
}
///
/// sodium_bin2hex
///
/// The subsection of that was filled, EXCLUDING null terminator.
///
/// If you need a version that gives back a string, just use the BCL instead.
///
public static unsafe Span Bin2Hex(Span hex, ReadOnlySpan bin)
{
var needSize = (long)bin.Length * 2 + 1;
if (hex.Length < needSize)
throw new ArgumentException("Hex must be at least (bin.Length * 2) + 1 bytes.");
fixed (byte* hexPtr = hex)
fixed (byte* binPtr = bin)
{
sodium_bin2hex((sbyte*)hexPtr, (nuint)hex.Length, binPtr, (nuint)bin.Length);
}
return hex[..(int)(needSize - 1)];
}
///
/// sodium_bin2hex, returns string.
///
public static string Bin2Hex(ReadOnlySpan bin)
{
var buffer = ArrayPool.Shared.Rent(bin.Length * 2 + 1);
try
{
var data = Bin2Hex(buffer, bin);
return Encoding.ASCII.GetString(data);
}
finally
{
ArrayPool.Shared.Return(buffer);
}
}
public static int Base64EncodedLength(int length, SodiumBase64Variant variant) =>
checked((int)sodium_base64_ENCODED_LEN((ulong)length, (ulong)variant));
///
/// sodium_bin2base64
///
/// The subsection of that was filled, EXCLUDING null terminator.
public static unsafe Span Bin2Base64(Span b64, ReadOnlySpan bin, SodiumBase64Variant variant)
{
var needSize = sodium_base64_ENCODED_LEN((ulong)bin.Length, (ulong)variant);
if ((ulong)b64.Length < needSize)
throw new ArgumentException("B64 is too short for encoded data");
fixed (byte* b64Ptr = b64)
fixed (byte* binPtr = bin)
{
sodium_bin2base64((sbyte*)b64Ptr, (nuint)b64.Length, binPtr, (nuint)bin.Length, (int)variant);
}
return b64[..(int)(needSize - 1)];
}
///
/// sodium_bin2hex, returns string.
///
public static string Bin2Base64(ReadOnlySpan bin, SodiumBase64Variant variant)
{
var buffer = ArrayPool.Shared.Rent(Base64EncodedLength(bin.Length, variant));
try
{
var data = Bin2Base64(buffer, bin, variant);
return Encoding.ASCII.GetString(data);
}
finally
{
ArrayPool.Shared.Return(buffer);
}
}
///
/// sodium_increment
///
public static unsafe void Increment(Span n)
{
fixed (byte* nPtr = n)
{
sodium_increment(nPtr, (nuint)n.Length);
}
}
///
/// sodium_add
///
public static unsafe void Add(Span a, ReadOnlySpan b)
{
if (a.Length != b.Length)
throw new ArgumentException("Both input numbers must be the same length.");
fixed (byte* aPtr = a)
fixed (byte* bPtr = b)
{
sodium_add(aPtr, bPtr, (nuint)a.Length);
}
}
///
/// sodium_sub
///
public static unsafe void Sub(Span a, ReadOnlySpan b)
{
if (a.Length != b.Length)
throw new ArgumentException("Both input numbers must be the same length.");
fixed (byte* aPtr = a)
fixed (byte* bPtr = b)
{
sodium_sub(aPtr, bPtr, (nuint)a.Length);
}
}
///
/// sodium_compare
///
public static unsafe int Compare(ReadOnlySpan b1, ReadOnlySpan b2)
{
if (b1.Length != b2.Length)
throw new ArgumentException("Both input numbers must be the same length.");
fixed (byte* b1Ptr = b1)
fixed (byte* b2Ptr = b2)
{
return sodium_compare(b1Ptr, b2Ptr, (nuint)b1.Length);
}
}
///
/// sodium_is_zero
///
public static unsafe bool IsZero(ReadOnlySpan n)
{
fixed (byte* nPtr = n)
{
return sodium_is_zero(nPtr, (nuint)n.Length) == 1;
}
}
///
/// sodium_stackzero
///
public static void StackZero(int len)
{
sodium_stackzero((nuint)len);
}
}
public enum SodiumBase64Variant
{
Original = sodium_base64_VARIANT_ORIGINAL,
OriginalNoPadding = sodium_base64_VARIANT_ORIGINAL_NO_PADDING,
OriginalUrlSafe = sodium_base64_VARIANT_URLSAFE,
OriginalUrlSafeNoPadding = sodium_base64_VARIANT_URLSAFE_NO_PADDING,
}