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 { /// /// 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, Boolean 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, Boolean 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) { Int32 mask = ~(0xff << length); Byte 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, 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 result = new List(); 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; } /// /// 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; } Byte[] 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(); } Byte[] 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(); } Byte[] 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) { Byte[] 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 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; } /// /// 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 Boolean 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 Boolean 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 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; } /// /// 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 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; } /// /// 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(Byte[] 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, 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; } /// /// 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, Int64 startIndex, Int64 length) => array.SubArray((Int32)startIndex, (Int32)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, 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(); } } /// /// 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, 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); } /// /// 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)); } Byte[] byteArray = new Byte[sbyteArray.Length]; for(Int32 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)); } SByte[] sbyteArray = new SByte[byteArray.Length]; for(Int32 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 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}"; } } }