RaspberryIO/Unosquare.Swan.Lite/Extensions.ByteArrays.cs
2019-12-04 17:10:06 +01:00

682 lines
24 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Unosquare.Swan {
/// <summary>
/// Provides various extension methods for byte arrays and streams.
/// </summary>
public static class ByteArrayExtensions {
/// <summary>
/// Converts an array of bytes to its lower-case, hexadecimal representation.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="addPrefix">if set to <c>true</c> add the 0x prefix tot he output.</param>
/// <returns>
/// The specified string instance; no actual conversion is performed.
/// </returns>
/// <exception cref="ArgumentNullException">bytes.</exception>
public static String ToLowerHex(this Byte[] bytes, Boolean addPrefix = false)
=> ToHex(bytes, addPrefix, "x2");
/// <summary>
/// Converts an array of bytes to its upper-case, hexadecimal representation.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="addPrefix">if set to <c>true</c> [add prefix].</param>
/// <returns>
/// The specified string instance; no actual conversion is performed.
/// </returns>
/// <exception cref="ArgumentNullException">bytes.</exception>
public static String ToUpperHex(this Byte[] bytes, Boolean addPrefix = false)
=> ToHex(bytes, addPrefix, "X2");
/// <summary>
/// Converts an array of bytes to a sequence of dash-separated, hexadecimal,
/// uppercase characters.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <returns>
/// A string of hexadecimal pairs separated by hyphens, where each pair represents
/// the corresponding element in value; for example, "7F-2C-4A-00".
/// </returns>
public static String ToDashedHex(this Byte[] bytes) => BitConverter.ToString(bytes);
/// <summary>
/// Converts an array of bytes to a base-64 encoded string.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <returns>A <see cref="System.String" /> converted from an array of bytes.</returns>
public static String ToBase64(this Byte[] bytes) => Convert.ToBase64String(bytes);
/// <summary>
/// Converts a set of hexadecimal characters (uppercase or lowercase)
/// to a byte array. String length must be a multiple of 2 and
/// any prefix (such as 0x) has to be avoided for this to work properly.
/// </summary>
/// <param name="hex">The hexadecimal.</param>
/// <returns>
/// A byte array containing the results of encoding the specified set of characters.
/// </returns>
/// <exception cref="ArgumentNullException">hex.</exception>
public static Byte[] ConvertHexadecimalToBytes(this String hex) {
if(String.IsNullOrWhiteSpace(hex)) {
throw new ArgumentNullException(nameof(hex));
}
return Enumerable
.Range(0, hex.Length / 2)
.Select(x => Convert.ToByte(hex.Substring(x * 2, 2), 16))
.ToArray();
}
/// <summary>
/// Gets the bit value at the given offset.
/// </summary>
/// <param name="b">The b.</param>
/// <param name="offset">The offset.</param>
/// <param name="length">The length.</param>
/// <returns>
/// Bit value at the given offset.
/// </returns>
public static Byte GetBitValueAt(this Byte b, Byte offset, Byte length = 1) => (Byte)((b >> offset) & ~(0xff << length));
/// <summary>
/// Sets the bit value at the given offset.
/// </summary>
/// <param name="b">The b.</param>
/// <param name="offset">The offset.</param>
/// <param name="length">The length.</param>
/// <param name="value">The value.</param>
/// <returns>Bit value at the given offset.</returns>
public static Byte SetBitValueAt(this Byte b, Byte offset, Byte length, Byte value) {
Int32 mask = ~(0xff << length);
Byte valueAt = (Byte)(value & mask);
return (Byte)((valueAt << offset) | (b & ~(mask << offset)));
}
/// <summary>
/// Sets the bit value at the given offset.
/// </summary>
/// <param name="b">The b.</param>
/// <param name="offset">The offset.</param>
/// <param name="value">The value.</param>
/// <returns>Bit value at the given offset.</returns>
public static Byte SetBitValueAt(this Byte b, Byte offset, Byte value) => b.SetBitValueAt(offset, 1, value);
/// <summary>
/// Splits a byte array delimited by the specified sequence of bytes.
/// Each individual element in the result will contain the split sequence terminator if it is found to be delimited by it.
/// For example if you split [1,2,3,4] by a sequence of [2,3] this method will return a list with 2 byte arrays, one containing [1,2,3] and the
/// second one containing 4. Use the Trim extension methods to remove terminator sequences.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="offset">The offset at which to start splitting bytes. Any bytes before this will be discarded.</param>
/// <param name="sequence">The sequence.</param>
/// <returns>
/// A byte array containing the results the specified sequence of bytes.
/// </returns>
/// <exception cref="System.ArgumentNullException">
/// buffer
/// or
/// sequence.
/// </exception>
public static List<Byte[]> Split(this Byte[] buffer, Int32 offset, params Byte[] sequence) {
if(buffer == null) {
throw new ArgumentNullException(nameof(buffer));
}
if(sequence == null) {
throw new ArgumentNullException(nameof(sequence));
}
Int32 seqOffset = offset.Clamp(0, buffer.Length - 1);
List<Byte[]> result = new List<Byte[]>();
while(seqOffset < buffer.Length) {
Int32 separatorStartIndex = buffer.GetIndexOf(sequence, seqOffset);
if(separatorStartIndex >= 0) {
Byte[] item = new Byte[separatorStartIndex - seqOffset + sequence.Length];
Array.Copy(buffer, seqOffset, item, 0, item.Length);
result.Add(item);
seqOffset += item.Length;
} else {
Byte[] item = new Byte[buffer.Length - seqOffset];
Array.Copy(buffer, seqOffset, item, 0, item.Length);
result.Add(item);
break;
}
}
return result;
}
/// <summary>
/// Clones the specified buffer, byte by byte.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
public static Byte[] DeepClone(this Byte[] buffer) {
if(buffer == null) {
return null;
}
Byte[] result = new Byte[buffer.Length];
Array.Copy(buffer, result, buffer.Length);
return result;
}
/// <summary>
/// Removes the specified sequence from the start of the buffer if the buffer begins with such sequence.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param>
/// <returns>
/// A new trimmed byte array.
/// </returns>
/// <exception cref="ArgumentNullException">buffer.</exception>
public static Byte[] TrimStart(this Byte[] buffer, params Byte[] sequence) {
if(buffer == null) {
throw new ArgumentNullException(nameof(buffer));
}
if(buffer.StartsWith(sequence) == false) {
return buffer.DeepClone();
}
Byte[] result = new Byte[buffer.Length - sequence.Length];
Array.Copy(buffer, sequence.Length, result, 0, result.Length);
return result;
}
/// <summary>
/// Removes the specified sequence from the end of the buffer if the buffer ends with such sequence.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param>
/// <returns>
/// A byte array containing the results of encoding the specified set of characters.
/// </returns>
/// <exception cref="ArgumentNullException">buffer.</exception>
public static Byte[] TrimEnd(this Byte[] buffer, params Byte[] sequence) {
if(buffer == null) {
throw new ArgumentNullException(nameof(buffer));
}
if(buffer.EndsWith(sequence) == false) {
return buffer.DeepClone();
}
Byte[] result = new Byte[buffer.Length - sequence.Length];
Array.Copy(buffer, 0, result, 0, result.Length);
return result;
}
/// <summary>
/// Removes the specified sequence from the end and the start of the buffer
/// if the buffer ends and/or starts with such sequence.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param>
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
public static Byte[] Trim(this Byte[] buffer, params Byte[] sequence) {
Byte[] trimStart = buffer.TrimStart(sequence);
return trimStart.TrimEnd(sequence);
}
/// <summary>
/// Determines if the specified buffer ends with the given sequence of bytes.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param>
/// <returns>
/// True if the specified buffer is ends; otherwise, false.
/// </returns>
/// <exception cref="ArgumentNullException">buffer.</exception>
public static Boolean EndsWith(this Byte[] buffer, params Byte[] sequence) {
if(buffer == null) {
throw new ArgumentNullException(nameof(buffer));
}
Int32 startIndex = buffer.Length - sequence.Length;
return buffer.GetIndexOf(sequence, startIndex) == startIndex;
}
/// <summary>
/// Determines if the specified buffer starts with the given sequence of bytes.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param>
/// <returns><c>true</c> if the specified buffer starts; otherwise, <c>false</c>.</returns>
public static Boolean StartsWith(this Byte[] buffer, params Byte[] sequence) => buffer.GetIndexOf(sequence) == 0;
/// <summary>
/// Determines whether the buffer contains the specified sequence.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param>
/// <returns>
/// <c>true</c> if [contains] [the specified sequence]; otherwise, <c>false</c>.
/// </returns>
public static Boolean Contains(this Byte[] buffer, params Byte[] sequence) => buffer.GetIndexOf(sequence) >= 0;
/// <summary>
/// Determines whether the buffer exactly matches, byte by byte the specified sequence.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param>
/// <returns>
/// <c>true</c> if [is equal to] [the specified sequence]; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">buffer.</exception>
public static Boolean IsEqualTo(this Byte[] buffer, params Byte[] sequence) {
if(ReferenceEquals(buffer, sequence)) {
return true;
}
if(buffer == null) {
throw new ArgumentNullException(nameof(buffer));
}
return buffer.Length == sequence.Length && buffer.GetIndexOf(sequence) == 0;
}
/// <summary>
/// Returns the first instance of the matched sequence based on the given offset.
/// If nomatches are found then this method returns -1.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param>
/// <param name="offset">The offset.</param>
/// <returns>The index of the sequence.</returns>
/// <exception cref="ArgumentNullException">
/// buffer
/// or
/// sequence.
/// </exception>
public static Int32 GetIndexOf(this Byte[] buffer, Byte[] sequence, Int32 offset = 0) {
if(buffer == null) {
throw new ArgumentNullException(nameof(buffer));
}
if(sequence == null) {
throw new ArgumentNullException(nameof(sequence));
}
if(sequence.Length == 0) {
return -1;
}
if(sequence.Length > buffer.Length) {
return -1;
}
Int32 seqOffset = offset < 0 ? 0 : offset;
Int32 matchedCount = 0;
for(Int32 i = seqOffset; i < buffer.Length; i++) {
if(buffer[i] == sequence[matchedCount]) {
matchedCount++;
} else {
matchedCount = 0;
}
if(matchedCount == sequence.Length) {
return i - (matchedCount - 1);
}
}
return -1;
}
/// <summary>
/// Appends the Memory Stream with the specified buffer.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="buffer">The buffer.</param>
/// <returns>
/// The same MemoryStream instance.
/// </returns>
/// <exception cref="ArgumentNullException">
/// stream
/// or
/// buffer.
/// </exception>
public static MemoryStream Append(this MemoryStream stream, Byte[] buffer) {
if(stream == null) {
throw new ArgumentNullException(nameof(stream));
}
if(buffer == null) {
throw new ArgumentNullException(nameof(buffer));
}
stream.Write(buffer, 0, buffer.Length);
return stream;
}
/// <summary>
/// Appends the Memory Stream with the specified buffer.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="buffer">The buffer.</param>
/// <returns>
/// Block of bytes to the current stream using data read from a buffer.
/// </returns>
/// <exception cref="ArgumentNullException">buffer.</exception>
public static MemoryStream Append(this MemoryStream stream, IEnumerable<Byte> buffer) => Append(stream, buffer?.ToArray());
/// <summary>
/// Appends the Memory Stream with the specified set of buffers.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="buffers">The buffers.</param>
/// <returns>
/// Block of bytes to the current stream using data read from a buffer.
/// </returns>
/// <exception cref="ArgumentNullException">buffers.</exception>
public static MemoryStream Append(this MemoryStream stream, IEnumerable<Byte[]> buffers) {
if(buffers == null) {
throw new ArgumentNullException(nameof(buffers));
}
foreach(Byte[] buffer in buffers) {
_ = Append(stream, buffer);
}
return stream;
}
/// <summary>
/// Converts an array of bytes into text with the specified encoding.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="encoding">The encoding.</param>
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
public static String ToText(this IEnumerable<Byte> buffer, Encoding encoding) => encoding.GetString(buffer.ToArray());
/// <summary>
/// Converts an array of bytes into text with UTF8 encoding.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
public static String ToText(this IEnumerable<Byte> buffer) => buffer.ToText(Encoding.UTF8);
/// <summary>
/// Retrieves a sub-array from the specified <paramref name="array"/>. A sub-array starts at
/// the specified element position in <paramref name="array"/>.
/// </summary>
/// <returns>
/// An array of T that receives a sub-array, or an empty array of T if any problems with
/// the parameters.
/// </returns>
/// <param name="array">
/// An array of T from which to retrieve a sub-array.
/// </param>
/// <param name="startIndex">
/// An <see cref="Int32"/> that represents the zero-based starting position of
/// a sub-array in <paramref name="array"/>.
/// </param>
/// <param name="length">
/// An <see cref="Int32"/> that represents the number of elements to retrieve.
/// </param>
/// <typeparam name="T">
/// The type of elements in <paramref name="array"/>.
/// </typeparam>
public static T[] SubArray<T>(this T[] array, Int32 startIndex, Int32 length) {
Int32 len;
if(array == null || (len = array.Length) == 0) {
return new T[0];
}
if(startIndex < 0 || length <= 0 || startIndex + length > len) {
return new T[0];
}
if(startIndex == 0 && length == len) {
return array;
}
T[] subArray = new T[length];
Array.Copy(array, startIndex, subArray, 0, length);
return subArray;
}
/// <summary>
/// Retrieves a sub-array from the specified <paramref name="array"/>. A sub-array starts at
/// the specified element position in <paramref name="array"/>.
/// </summary>
/// <returns>
/// An array of T that receives a sub-array, or an empty array of T if any problems with
/// the parameters.
/// </returns>
/// <param name="array">
/// An array of T from which to retrieve a sub-array.
/// </param>
/// <param name="startIndex">
/// A <see cref="Int64"/> that represents the zero-based starting position of
/// a sub-array in <paramref name="array"/>.
/// </param>
/// <param name="length">
/// A <see cref="Int64"/> that represents the number of elements to retrieve.
/// </param>
/// <typeparam name="T">
/// The type of elements in <paramref name="array"/>.
/// </typeparam>
public static T[] SubArray<T>(this T[] array, Int64 startIndex, Int64 length) => array.SubArray((Int32)startIndex, (Int32)length);
/// <summary>
/// Reads the bytes asynchronous.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="length">The length.</param>
/// <param name="bufferLength">Length of the buffer.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// A byte array containing the results of encoding the specified set of characters.
/// </returns>
/// <exception cref="ArgumentNullException">stream.</exception>
public static async Task<Byte[]> ReadBytesAsync(this Stream stream, Int64 length, Int32 bufferLength, CancellationToken ct = default) {
if(stream == null) {
throw new ArgumentNullException(nameof(stream));
}
using(MemoryStream dest = new MemoryStream()) {
try {
Byte[] buff = new Byte[bufferLength];
while(length > 0) {
if(length < bufferLength) {
bufferLength = (Int32)length;
}
Int32 nread = await stream.ReadAsync(buff, 0, bufferLength, ct).ConfigureAwait(false);
if(nread == 0) {
break;
}
dest.Write(buff, 0, nread);
length -= nread;
}
} catch {
// ignored
}
return dest.ToArray();
}
}
/// <summary>
/// Reads the bytes asynchronous.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="length">The length.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// A byte array containing the results of encoding the specified set of characters.
/// </returns>
/// <exception cref="ArgumentNullException">stream.</exception>
public static async Task<Byte[]> ReadBytesAsync(this Stream stream, Int32 length, CancellationToken ct = default) {
if(stream == null) {
throw new ArgumentNullException(nameof(stream));
}
Byte[] buff = new Byte[length];
Int32 offset = 0;
try {
while(length > 0) {
Int32 nread = await stream.ReadAsync(buff, offset, length, ct).ConfigureAwait(false);
if(nread == 0) {
break;
}
offset += nread;
length -= nread;
}
} catch {
// ignored
}
return buff.SubArray(0, offset);
}
/// <summary>
/// Converts an array of sbytes to an array of bytes.
/// </summary>
/// <param name="sbyteArray">The sbyte array.</param>
/// <returns>
/// The byte array from conversion.
/// </returns>
/// <exception cref="ArgumentNullException">sbyteArray.</exception>
public static Byte[] ToByteArray(this SByte[] sbyteArray) {
if(sbyteArray == null) {
throw new ArgumentNullException(nameof(sbyteArray));
}
Byte[] byteArray = new Byte[sbyteArray.Length];
for(Int32 index = 0; index < sbyteArray.Length; index++) {
byteArray[index] = (Byte)sbyteArray[index];
}
return byteArray;
}
/// <summary>
/// Receives a byte array and returns it transformed in an sbyte array.
/// </summary>
/// <param name="byteArray">The byte array.</param>
/// <returns>
/// The sbyte array from conversion.
/// </returns>
/// <exception cref="ArgumentNullException">byteArray.</exception>
public static SByte[] ToSByteArray(this Byte[] byteArray) {
if(byteArray == null) {
throw new ArgumentNullException(nameof(byteArray));
}
SByte[] sbyteArray = new SByte[byteArray.Length];
for(Int32 index = 0; index < byteArray.Length; index++) {
sbyteArray[index] = (SByte)byteArray[index];
}
return sbyteArray;
}
/// <summary>
/// Gets the sbytes from a string.
/// </summary>
/// <param name="encoding">The encoding.</param>
/// <param name="s">The s.</param>
/// <returns>The sbyte array from string.</returns>
public static SByte[] GetSBytes(this Encoding encoding, String s)
=> encoding.GetBytes(s).ToSByteArray();
/// <summary>
/// Gets the string from a sbyte array.
/// </summary>
/// <param name="encoding">The encoding.</param>
/// <param name="data">The data.</param>
/// <returns>The string.</returns>
public static String GetString(this Encoding encoding, SByte[] data)
=> encoding.GetString(data.ToByteArray());
/// <summary>
/// Reads a number of characters from the current source Stream and writes the data to the target array at the
/// specified index.
/// </summary>
/// <param name="sourceStream">The source stream.</param>
/// <param name="target">The target.</param>
/// <param name="start">The start.</param>
/// <param name="count">The count.</param>
/// <returns>
/// The number of bytes read.
/// </returns>
/// <exception cref="ArgumentNullException">
/// sourceStream
/// or
/// target.
/// </exception>
public static Int32 ReadInput(this Stream sourceStream, ref SByte[] target, Int32 start, Int32 count) {
if(sourceStream == null) {
throw new ArgumentNullException(nameof(sourceStream));
}
if(target == null) {
throw new ArgumentNullException(nameof(target));
}
// Returns 0 bytes if not enough space in target
if(target.Length == 0) {
return 0;
}
Byte[] receiver = new Byte[target.Length];
Int32 bytesRead = 0;
Int32 startIndex = start;
Int32 bytesToRead = count;
while(bytesToRead > 0) {
Int32 n = sourceStream.Read(receiver, startIndex, bytesToRead);
if(n == 0) {
break;
}
bytesRead += n;
startIndex += n;
bytesToRead -= n;
}
// Returns -1 if EOF
if(bytesRead == 0) {
return -1;
}
for(Int32 i = start; i < start + bytesRead; i++) {
target[i] = (SByte)receiver[i];
}
return bytesRead;
}
private static String ToHex(Byte[] bytes, Boolean addPrefix, String format) {
if(bytes == null) {
throw new ArgumentNullException(nameof(bytes));
}
StringBuilder sb = new StringBuilder(bytes.Length * 2);
foreach(Byte item in bytes) {
_ = sb.Append(item.ToString(format, CultureInfo.InvariantCulture));
}
return $"{(addPrefix ? "0x" : String.Empty)}{sb}";
}
}
}