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 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 @this) { if(String.IsNullOrWhiteSpace(@this)) { throw new ArgumentNullException(nameof(@this)); } return Enumerable.Range(0, @this.Length / 2).Select(x => Convert.ToByte(@this.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 @this, Byte offset, Byte length = 1) => (Byte)((@this >> 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 @this, Byte offset, Byte length, Byte value) { Int32 mask = ~(0xff << length); Byte valueAt = (Byte)(value & mask); return (Byte)((valueAt << offset) | (@this & ~(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 @this, Byte offset, Byte value) => @this.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[] @this, Int32 offset, params Byte[] sequence) { if(@this == null) { throw new ArgumentNullException(nameof(@this)); } if(sequence == null) { throw new ArgumentNullException(nameof(sequence)); } Int32 seqOffset = offset.Clamp(0, @this.Length - 1); List result = new List(); while(seqOffset < @this.Length) { Int32 separatorStartIndex = @this.GetIndexOf(sequence, seqOffset); if(separatorStartIndex >= 0) { Byte[] item = new Byte[separatorStartIndex - seqOffset + sequence.Length]; Array.Copy(@this, seqOffset, item, 0, item.Length); result.Add(item); seqOffset += item.Length; } else { Byte[] item = new Byte[@this.Length - seqOffset]; Array.Copy(@this, 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. /// /// this public static Byte[] DeepClone(this Byte[] @this) { if(@this == null) { throw new ArgumentNullException(nameof(@this)); } Byte[] result = new Byte[@this.Length]; Array.Copy(@this, result, @this.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 no matches 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); /// /// 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 cancellationToken = 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, cancellationToken).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 cancellationToken = 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, cancellationToken).ConfigureAwait(false); if(nread == 0) { break; } offset += nread; length -= nread; } } catch { // ignored } return new ArraySegment(buff, 0, offset).ToArray(); } 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}"; } } }