namespace Unosquare.Swan { 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; /// /// Provides various extension methods for byte arrays and streams. /// public static class ByteArrayExtensions { /// /// Converts an array of bytes to its lower-case, hexadecimal representation. /// /// The bytes. /// if set to true add the 0x prefix tot he output. /// /// The specified string instance; no actual conversion is performed. /// /// bytes. public static string ToLowerHex(this byte[] bytes, bool addPrefix = false) => ToHex(bytes, addPrefix, "x2"); /// /// Converts an array of bytes to its upper-case, hexadecimal representation. /// /// The bytes. /// if set to true [add prefix]. /// /// The specified string instance; no actual conversion is performed. /// /// bytes. public static string ToUpperHex(this byte[] bytes, bool addPrefix = false) => ToHex(bytes, addPrefix, "X2"); /// /// Converts an array of bytes to a sequence of dash-separated, hexadecimal, /// uppercase characters. /// /// The bytes. /// /// A string of hexadecimal pairs separated by hyphens, where each pair represents /// the corresponding element in value; for example, "7F-2C-4A-00". /// public static string ToDashedHex(this byte[] bytes) => BitConverter.ToString(bytes); /// /// Converts an array of bytes to a base-64 encoded string. /// /// The bytes. /// A converted from an array of bytes. public static string ToBase64(this byte[] bytes) => Convert.ToBase64String(bytes); /// /// 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. /// /// The hexadecimal. /// /// A byte array containing the results of encoding the specified set of characters. /// /// hex. 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(); } /// /// Gets the bit value at the given offset. /// /// The b. /// The offset. /// The length. /// /// Bit value at the given offset. /// public static byte GetBitValueAt(this byte b, byte offset, byte length = 1) => (byte)((b >> offset) & ~(0xff << length)); /// /// Sets the bit value at the given offset. /// /// The b. /// The offset. /// The length. /// The value. /// Bit value at the given offset. public static byte SetBitValueAt(this byte b, byte offset, byte length, byte value) { var mask = ~(0xff << length); var valueAt = (byte)(value & mask); return (byte)((valueAt << offset) | (b & ~(mask << offset))); } /// /// Sets the bit value at the given offset. /// /// The b. /// The offset. /// The value. /// Bit value at the given offset. public static byte SetBitValueAt(this byte b, byte offset, byte value) => b.SetBitValueAt(offset, 1, value); /// /// 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. /// /// The buffer. /// The offset at which to start splitting bytes. Any bytes before this will be discarded. /// The sequence. /// /// A byte array containing the results the specified sequence of bytes. /// /// /// buffer /// or /// sequence. /// public static List Split(this byte[] buffer, int offset, params byte[] sequence) { if (buffer == null) throw new ArgumentNullException(nameof(buffer)); if (sequence == null) throw new ArgumentNullException(nameof(sequence)); var seqOffset = offset.Clamp(0, buffer.Length - 1); var result = new List(); while (seqOffset < buffer.Length) { var separatorStartIndex = buffer.GetIndexOf(sequence, seqOffset); if (separatorStartIndex >= 0) { var item = new byte[separatorStartIndex - seqOffset + sequence.Length]; Array.Copy(buffer, seqOffset, item, 0, item.Length); result.Add(item); seqOffset += item.Length; } else { var item = new byte[buffer.Length - seqOffset]; Array.Copy(buffer, seqOffset, item, 0, item.Length); result.Add(item); break; } } return result; } /// /// Clones the specified buffer, byte by byte. /// /// The buffer. /// A byte array containing the results of encoding the specified set of characters. public static byte[] DeepClone(this byte[] buffer) { if (buffer == null) return null; var result = new byte[buffer.Length]; Array.Copy(buffer, result, buffer.Length); return result; } /// /// Removes the specified sequence from the start of the buffer if the buffer begins with such sequence. /// /// The buffer. /// The sequence. /// /// A new trimmed byte array. /// /// buffer. 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(); var result = new byte[buffer.Length - sequence.Length]; Array.Copy(buffer, sequence.Length, result, 0, result.Length); return result; } /// /// Removes the specified sequence from the end of the buffer if the buffer ends with such sequence. /// /// The buffer. /// The sequence. /// /// A byte array containing the results of encoding the specified set of characters. /// /// buffer. 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(); var result = new byte[buffer.Length - sequence.Length]; Array.Copy(buffer, 0, result, 0, result.Length); return result; } /// /// Removes the specified sequence from the end and the start of the buffer /// if the buffer ends and/or starts with such sequence. /// /// The buffer. /// The sequence. /// A byte array containing the results of encoding the specified set of characters. public static byte[] Trim(this byte[] buffer, params byte[] sequence) { var trimStart = buffer.TrimStart(sequence); return trimStart.TrimEnd(sequence); } /// /// Determines if the specified buffer ends with the given sequence of bytes. /// /// The buffer. /// The sequence. /// /// True if the specified buffer is ends; otherwise, false. /// /// buffer. public static bool EndsWith(this byte[] buffer, params byte[] sequence) { if (buffer == null) throw new ArgumentNullException(nameof(buffer)); var startIndex = buffer.Length - sequence.Length; return buffer.GetIndexOf(sequence, startIndex) == startIndex; } /// /// Determines if the specified buffer starts with the given sequence of bytes. /// /// The buffer. /// The sequence. /// true if the specified buffer starts; otherwise, false. public static bool StartsWith(this byte[] buffer, params byte[] sequence) => buffer.GetIndexOf(sequence) == 0; /// /// Determines whether the buffer contains the specified sequence. /// /// The buffer. /// The sequence. /// /// true if [contains] [the specified sequence]; otherwise, false. /// public static bool Contains(this byte[] buffer, params byte[] sequence) => buffer.GetIndexOf(sequence) >= 0; /// /// Determines whether the buffer exactly matches, byte by byte the specified sequence. /// /// The buffer. /// The sequence. /// /// true if [is equal to] [the specified sequence]; otherwise, false. /// /// buffer. public static bool 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; } /// /// Returns the first instance of the matched sequence based on the given offset. /// If nomatches are found then this method returns -1. /// /// The buffer. /// The sequence. /// The offset. /// The index of the sequence. /// /// buffer /// or /// sequence. /// public static int GetIndexOf(this byte[] buffer, byte[] sequence, int 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; var seqOffset = offset < 0 ? 0 : offset; var matchedCount = 0; for (var 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; } /// /// Appends the Memory Stream with the specified buffer. /// /// The stream. /// The buffer. /// /// The same MemoryStream instance. /// /// /// stream /// or /// buffer. /// 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; } /// /// Appends the Memory Stream with the specified buffer. /// /// The stream. /// The buffer. /// /// Block of bytes to the current stream using data read from a buffer. /// /// buffer. public static MemoryStream Append(this MemoryStream stream, IEnumerable buffer) => Append(stream, buffer?.ToArray()); /// /// Appends the Memory Stream with the specified set of buffers. /// /// The stream. /// The buffers. /// /// Block of bytes to the current stream using data read from a buffer. /// /// buffers. public static MemoryStream Append(this MemoryStream stream, IEnumerable buffers) { if (buffers == null) throw new ArgumentNullException(nameof(buffers)); foreach (var buffer in buffers) Append(stream, buffer); return stream; } /// /// Converts an array of bytes into text with the specified encoding. /// /// The buffer. /// The encoding. /// A that contains the results of decoding the specified sequence of bytes. public static string ToText(this IEnumerable buffer, Encoding encoding) => encoding.GetString(buffer.ToArray()); /// /// Converts an array of bytes into text with UTF8 encoding. /// /// The buffer. /// A that contains the results of decoding the specified sequence of bytes. public static string ToText(this IEnumerable buffer) => buffer.ToText(Encoding.UTF8); /// /// Retrieves a sub-array from the specified . A sub-array starts at /// the specified element position in . /// /// /// An array of T that receives a sub-array, or an empty array of T if any problems with /// the parameters. /// /// /// An array of T from which to retrieve a sub-array. /// /// /// An that represents the zero-based starting position of /// a sub-array in . /// /// /// An that represents the number of elements to retrieve. /// /// /// The type of elements in . /// public static T[] SubArray(this T[] array, int startIndex, int length) { int 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; var subArray = new T[length]; Array.Copy(array, startIndex, subArray, 0, length); return subArray; } /// /// Retrieves a sub-array from the specified . A sub-array starts at /// the specified element position in . /// /// /// An array of T that receives a sub-array, or an empty array of T if any problems with /// the parameters. /// /// /// An array of T from which to retrieve a sub-array. /// /// /// A that represents the zero-based starting position of /// a sub-array in . /// /// /// A that represents the number of elements to retrieve. /// /// /// The type of elements in . /// public static T[] SubArray(this T[] array, long startIndex, long length) => array.SubArray((int)startIndex, (int)length); /// /// Reads the bytes asynchronous. /// /// The stream. /// The length. /// Length of the buffer. /// The cancellation token. /// /// A byte array containing the results of encoding the specified set of characters. /// /// stream. public static async Task ReadBytesAsync(this Stream stream, long length, int bufferLength, CancellationToken ct = default) { if (stream == null) throw new ArgumentNullException(nameof(stream)); using (var dest = new MemoryStream()) { try { var buff = new byte[bufferLength]; while (length > 0) { if (length < bufferLength) bufferLength = (int)length; var 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(); } } /// /// Reads the bytes asynchronous. /// /// The stream. /// The length. /// The cancellation token. /// /// A byte array containing the results of encoding the specified set of characters. /// /// stream. public static async Task ReadBytesAsync(this Stream stream, int length, CancellationToken ct = default) { if (stream == null) throw new ArgumentNullException(nameof(stream)); var buff = new byte[length]; var offset = 0; try { while (length > 0) { var 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); } /// /// Converts an array of sbytes to an array of bytes. /// /// The sbyte array. /// /// The byte array from conversion. /// /// sbyteArray. public static byte[] ToByteArray(this sbyte[] sbyteArray) { if (sbyteArray == null) throw new ArgumentNullException(nameof(sbyteArray)); var byteArray = new byte[sbyteArray.Length]; for (var index = 0; index < sbyteArray.Length; index++) byteArray[index] = (byte)sbyteArray[index]; return byteArray; } /// /// Receives a byte array and returns it transformed in an sbyte array. /// /// The byte array. /// /// The sbyte array from conversion. /// /// byteArray. public static sbyte[] ToSByteArray(this byte[] byteArray) { if (byteArray == null) throw new ArgumentNullException(nameof(byteArray)); var sbyteArray = new sbyte[byteArray.Length]; for (var index = 0; index < byteArray.Length; index++) sbyteArray[index] = (sbyte)byteArray[index]; return sbyteArray; } /// /// Gets the sbytes from a string. /// /// The encoding. /// The s. /// The sbyte array from string. public static sbyte[] GetSBytes(this Encoding encoding, string s) => encoding.GetBytes(s).ToSByteArray(); /// /// Gets the string from a sbyte array. /// /// The encoding. /// The data. /// The string. public static string GetString(this Encoding encoding, sbyte[] data) => encoding.GetString(data.ToByteArray()); /// /// Reads a number of characters from the current source Stream and writes the data to the target array at the /// specified index. /// /// The source stream. /// The target. /// The start. /// The count. /// /// The number of bytes read. /// /// /// sourceStream /// or /// target. /// public static int ReadInput(this Stream sourceStream, ref sbyte[] target, int start, int 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; var receiver = new byte[target.Length]; var bytesRead = 0; var startIndex = start; var bytesToRead = count; while (bytesToRead > 0) { var 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 (var i = start; i < start + bytesRead; i++) target[i] = (sbyte)receiver[i]; return bytesRead; } private static string ToHex(byte[] bytes, bool addPrefix, string format) { if (bytes == null) throw new ArgumentNullException(nameof(bytes)); var sb = new StringBuilder(bytes.Length * 2); foreach (var item in bytes) sb.Append(item.ToString(format, CultureInfo.InvariantCulture)); return $"{(addPrefix ? "0x" : string.Empty)}{sb}"; } } }