/// <summary> /// Allocates a new byte array, containing the data represented by the given Base62 string. /// </summary> public static byte[] FromBase62String(string base62String, Base62Alphabet alphabet = null) { // Prefer stackalloc over renting byte[] charBytes = null; var chars = base62String.Length <= 1024 ? stackalloc byte[base62String.Length] : (charBytes = ArrayPool <byte> .Shared.Rent(base62String.Length)); try { Encoding.ASCII.GetBytes(base62String, chars); var bytes = new byte[GetByteLength(chars.Length)]; if (!TryFromBase62Chars(chars, bytes, out _, alphabet)) { throw new ArgumentException("The input is not valid Base62."); } return(bytes); } finally { if (charBytes != null) { ArrayPool <byte> .Shared.Return(charBytes); } } }
/// <summary> /// Allocates a new string, encoding the given bytes as alphanumeric Base62 characters. /// </summary> public static string ToBase62String(ReadOnlySpan <byte> bytes, Base62Alphabet alphabet = null) { // We need a temporary place to store bytes, since we get UTF8 but want a string // That is alright: // We cannot use String.Create with span input anyway, so we need to make a full copy somewhere - it might as well be here // As an alternative to filling a byte[] and using ASCII.GetString(), we could fill a char[] and use new string(chars) // Filling the chars could be slower, yet creating the string could be faster // The overall performance is similar, so we prefer our own simplest implementation // Prefer stackalloc over renting var outputLength = GetBase62Length(bytes.Length); byte[] charArray = null; var chars = outputLength <= 1024 ? stackalloc byte[outputLength] : (charArray = ArrayPool <byte> .Shared.Rent(outputLength)); try { if (!TryToBase62Chars(bytes, chars, out _, alphabet)) { throw new ArgumentException("The output span is too short."); } return(Encoding.ASCII.GetString(chars)); } finally { if (charArray != null) { ArrayPool <byte> .Shared.Return(charArray); } } }
public static string ToBase62String(ReadOnlySpan <byte> bytes, Base62Alphabet alphabet = null) { // We need a temporary place to store bytes, since we get UTF8 but want a string // That is alright: // We cannot use String.Create with span input anyway, so we need to make a full copy somewhere - it might as well be here // Prefer stackalloc over renting var outputLength = GetBase62Length(bytes.Length); byte[] charArray = null; var chars = outputLength <= 1024 ? stackalloc byte[outputLength] : (charArray = ArrayPool <byte> .Shared.Rent(outputLength)); try { TryToBase62Chars(bytes, chars, out _, alphabet); // Output is always true return(Encoding.ASCII.GetString(chars)); } finally { if (charArray != null) { ArrayPool <byte> .Shared.Return(charArray); } } }
/// <summary> /// Allocates a new string, encoding a subset of the given bytes as alphanumeric Base62 characters. /// </summary> public static string ToBase62String(byte[] bytes, int offset, int length, Base62Alphabet alphabet = null) { return(ToBase62String(bytes.AsSpan(offset, length), alphabet)); }
/// <summary> /// Allocates a new string, encoding the given bytes as alphanumeric Base62 characters. /// </summary> public static string ToBase62String(byte[] bytes, Base62Alphabet alphabet = null) { return(ToBase62String(bytes.AsSpan(), alphabet)); }
/// <summary> /// Core implementation. /// Returns true on success, or false if the output span was too short or the input was not valid Base62. /// </summary> /// <param name="shiftCount">When reading bytes, left shift the index by 0 (no change). When reading chars, left shift it by 1 (doubling it).</param> private static bool TryFromBase62CharsCore(ReadOnlySpan <byte> chars, int shiftCount, Span <byte> bytes, out int bytesWritten, Base62Alphabet alphabet = null) { System.Diagnostics.Debug.Assert(shiftCount == 0 || shiftCount == 1); if (alphabet is null) { alphabet = DefaultAlphabet; } var reverseAlphabet = alphabet.ReverseAlphabet; try { var outputLength = GetByteLength(chars.Length >> shiftCount); if (bytes.Length < outputLength) { throw new ArgumentException("The output span is too small."); } bytes = bytes.Slice(0, outputLength); bytesWritten = outputLength; // Decode complete blocks while (chars.Length >= 11 << shiftCount) { DecodeBlock(reverseAlphabet, chars, shiftCount, bytes); chars = chars.Slice(11 << shiftCount); bytes = bytes.Slice(8); } if (chars.IsEmpty) { return(true); } // Decode the final (incomplete) block { DecodeBlock(reverseAlphabet, chars, shiftCount, bytes); return(true); } } catch (ArgumentException) { bytesWritten = 0; return(false); } }
/// <summary> /// <para> /// Decodes the given Base62 characters back into the represented bytes, writing them to the output span. /// </para> /// <para> /// Returns false if the output span is too short or the input is not valid Base62. /// If false is returned, any number of bytes may have been written, and the output value of the number of bytes written has no meaning. /// </para> /// </summary> public static bool TryFromBase62Chars(ReadOnlySpan <char> chars, Span <byte> bytes, out int bytesWritten, Base62Alphabet alphabet = null) { bytesWritten = 0; // Since we will only be processing the lower byte of each char, make sure that no char has data in its higher byte // Otherwise, invalid input could get through { var vectors = MemoryMarshal.Cast <char, Vector <ushort> >(chars); // Takes only full vector blocks of the input span var hasNonAsciiByte = false; foreach (var vector in vectors) { hasNonAsciiByte |= Vector.GreaterThanAny(vector, VectorOf127s); // Generally compares 16 chars at a time } for (var i = vectors.Length * Vector <ushort> .Count; i < chars.Length; i++) { hasNonAsciiByte |= chars[i] > 127; // Compare the remaining chars } if (hasNonAsciiByte) { return(false); } } var charsAsBytePairs = MemoryMarshal.AsBytes(chars); return(TryFromBase62CharsCore(charsAsBytePairs, shiftCount: 1, bytes, out bytesWritten, alphabet)); }
/// <summary> /// <para> /// Decodes the given ASCII-encoded Base62 characters back into the represented bytes, writing them to the output span. /// </para> /// <para> /// Returns false if the output span is too short or the input is not valid Base62. /// If false is returned, any number of bytes may have been written, and the output value of the number of bytes written has no meaning. /// </para> /// </summary> public static bool TryFromBase62Chars(ReadOnlySpan <byte> chars, Span <byte> bytes, out int bytesWritten, Base62Alphabet alphabet = null) { return(TryFromBase62CharsCore(chars, shiftCount: 0, bytes, out bytesWritten, alphabet)); }
/// <summary> /// Allocates a new byte array, decoding the data represented by the given Base62 string. /// </summary> public static bool TryFromBase62String(string base62String, Span <byte> bytes, out int bytesWritten, Base62Alphabet alphabet = null) { return(TryFromBase62Chars(base62String.AsSpan(), bytes, out bytesWritten, alphabet)); }
/// <summary> /// Core implementation. /// Returns true on success or false if the output span was too short. /// </summary> /// <param name="shiftCount">When reading bytes, left shift the index by 0 (no change). When reading chars, left shift it by 1 (doubling it).</param> private static bool TryToBase62CharsCore(ReadOnlySpan <byte> bytes, Span <byte> chars, int shiftCount, out int charsWritten, Base62Alphabet alphabet = null) { System.Diagnostics.Debug.Assert(shiftCount == 0 || shiftCount == 1); var forwardAlphabet = (alphabet ?? DefaultAlphabet).ForwardAlphabet; charsWritten = 0; var outputLength = GetBase62Length(bytes.Length); if (chars.Length < outputLength << shiftCount) { return(false); } chars = chars.Slice(0, outputLength << shiftCount); charsWritten = outputLength; // Encode complete blocks while (bytes.Length >= 8) { EncodeBlock(forwardAlphabet, bytes, chars, shiftCount); bytes = bytes.Slice(8); chars = chars.Slice(11 << shiftCount); } if (bytes.IsEmpty) { return(true); } // Encode the final (incomplete) block { EncodeBlock(forwardAlphabet, bytes, chars, shiftCount); return(true); } }
/// <summary> /// <para> /// Represents the given bytes as alphanumeric Base62 characters, writing them to the output span as UTF-16 characters. /// </para> /// <para> /// Returns false if the output span is too short. /// </para> /// </summary> public static bool TryToBase62Chars(ReadOnlySpan <byte> bytes, Span <char> chars, out int charsWritten, Base62Alphabet alphabet = null) { var charsAsBytes = MemoryMarshal.AsBytes(chars); return(TryToBase62CharsCore(bytes, charsAsBytes, shiftCount: 1, out charsWritten, alphabet)); }
/// <param name="shiftCount">When reading bytes, left shift the index by 0 (no change). When reading chars, left shift it by 1 (doubling it).</param> private static bool TryFromBase62Chars(ReadOnlySpan <byte> chars, int shiftCount, Span <byte> bytes, out int bytesWritten, Base62Alphabet alphabet = null) { System.Diagnostics.Debug.Assert(shiftCount == 0 || shiftCount == 1); if (alphabet is null) { alphabet = DefaultAlphabet; } var reverseAlphabet = alphabet.ReverseAlphabet; try { var outputLength = GetByteLength(chars.Length >> shiftCount); if (bytes.Length < outputLength) { throw new ArgumentException("The output span is too small."); } bytes = bytes.Slice(0, outputLength); bytesWritten = outputLength; Span <uint> workspace = stackalloc uint[8]; var workspaceBytes = MemoryMarshal.AsBytes(workspace); // Decode complete blocks while (chars.Length >= 43 << shiftCount) { DecodeBlock(reverseAlphabet, workspace, workspaceBytes, chars, shiftCount, bytes); workspaceBytes.Clear(); chars = chars.Slice(43 << shiftCount); bytes = bytes.Slice(32); } if (chars.IsEmpty) { return(true); } // Decode the final (incomplete) block { Span <byte> finalInputBlock = stackalloc byte[43 << shiftCount]; Span <byte> finalOutputBlock = stackalloc byte[32]; finalInputBlock.Fill(alphabet.ForwardAlphabet[0]); // Prefill with the character that represents 0 chars.CopyTo(finalInputBlock); DecodeBlock(reverseAlphabet, workspace, workspaceBytes, finalInputBlock, shiftCount, finalOutputBlock); finalOutputBlock.Slice(0, bytes.Length).CopyTo(bytes); return(true); } } catch (ArgumentException) { bytesWritten = 0; return(false); } }
/// <param name="shiftCount">When reading bytes, left shift the index by 0 (no change). When reading chars, left shift it by 1 (doubling it).</param> private static bool TryToBase62Chars(ReadOnlySpan <byte> bytes, Span <byte> chars, int shiftCount, out int charsWritten, Base62Alphabet alphabet = null) { System.Diagnostics.Debug.Assert(shiftCount == 0 || shiftCount == 1); var forwardAlphabet = (alphabet ?? DefaultAlphabet).ForwardAlphabet; charsWritten = 0; var outputLength = GetBase62Length(bytes.Length); if (chars.Length < outputLength << shiftCount) { return(false); } chars = chars.Slice(0, outputLength << shiftCount); charsWritten = outputLength; Span <uint> workspace = stackalloc uint[8]; var workspaceBytes = MemoryMarshal.AsBytes(workspace); // Encode complete blocks while (bytes.Length >= 32) { EncodeBlock(forwardAlphabet, workspace, workspaceBytes, bytes, chars, shiftCount); bytes = bytes.Slice(32); chars = chars.Slice(43 << shiftCount); } if (bytes.IsEmpty) { return(true); } // Encode the final (incomplete) block { Span <byte> finalInputBlock = stackalloc byte[32]; Span <byte> finalOutputBlock = stackalloc byte[43 << shiftCount]; bytes.CopyTo(finalInputBlock); EncodeBlock(forwardAlphabet, workspace, workspaceBytes, finalInputBlock, finalOutputBlock, shiftCount); finalOutputBlock.Slice(0, chars.Length).CopyTo(chars); return(true); } }
public static bool TryToBase62Chars(ReadOnlySpan <byte> bytes, Span <byte> chars, out int charsWritten, Base62Alphabet alphabet = null) { return(TryToBase62Chars(bytes, chars, shiftCount: 0, out charsWritten, alphabet)); }