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, 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 @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) { var mask = ~(0xff << length); var 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, int offset, params byte[] sequence) { if (@this == null) throw new ArgumentNullException(nameof(@this)); if (sequence == null) throw new ArgumentNullException(nameof(sequence)); var seqOffset = offset.Clamp(0, @this.Length - 1); var result = new List(); while (seqOffset < @this.Length) { var separatorStartIndex = @this.GetIndexOf(sequence, seqOffset); if (separatorStartIndex >= 0) { var item = new byte[separatorStartIndex - seqOffset + sequence.Length]; Array.Copy(@this, seqOffset, item, 0, item.Length); result.Add(item); seqOffset += item.Length; } else { var 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)); var 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(); 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 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 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); /// /// 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 cancellationToken = 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, 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, int length, CancellationToken cancellationToken = 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, 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, 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}"; } } }