/* * Copyright 2018 faddenSoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Diagnostics; using System.IO; using System.Text; namespace CommonUtil { /// /// Utility functions for manipulating streams of primitive data types. /// public static class RawData { /// /// Zero-length array, useful for initializing properties to a non-null value without /// allocating anything. /// public static readonly byte[] EMPTY_BYTE_ARRAY = new byte[0]; public static readonly ushort[] EMPTY_USHORT_ARRAY = new ushort[0]; public static readonly int[] EMPTY_INT_ARRAY = new int[0]; /// /// Extracts a null-terminated ASCII string from byte data. /// /// Data buffer. /// Start offset in data buffer. /// Maximum length of string. /// AND mask to apply to bytes. /// String found. May be empty. public static string GetNullTermString(byte[] data, int offset, int maxLen, byte andMask) { Debug.Assert(offset + maxLen <= data.Length); StringBuilder sb = new StringBuilder(maxLen); for (int i = 0; i < maxLen; i++) { byte val = data[offset + i]; if (val == 0) { break; } sb.Append((char)(val & andMask)); } return sb.ToString(); } /// /// Gets an integer from the data stream. Integers less than 4 bytes wide /// are not sign-extended. /// /// Raw data stream. /// Start offset. /// Word width in bytes (1-4). /// True if word is in big-endian order. /// Value read. public static int GetWord(byte[] data, int offset, int width, bool isBigEndian) { return (int)GetUWord(data, offset, width, isBigEndian); } /// /// Gets an unsigned integer from the data stream. /// /// Raw data stream. /// Start offset. /// Word width in bytes (1-4). /// True if word is in big-endian order. /// Value read. public static uint GetUWord(byte[] data, int offset, int width, bool isBigEndian) { if (width < 1 || width > 4 || offset > data.Length - width) { throw new ArgumentOutOfRangeException(nameof(offset), "GetUWord(offset=" + offset + " width=" + width + "), data.Length=" + data.Length); } if (isBigEndian) { switch (width) { case 1: return data[offset]; case 2: return (uint)((data[offset] << 8) | data[offset + 1]); case 3: return (uint)((data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2]); case 4: return (uint)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]); } } else { switch (width) { case 1: return data[offset]; case 2: return (uint)(data[offset] | (data[offset + 1] << 8)); case 3: return (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16)); case 4: return (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | data[offset + 3] << 24); } } throw new Exception("GetUWord should not be here"); } /// /// Reads an unsigned 8-bit value from a byte stream, advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 1. /// Value retrieved. public static byte ReadU8(byte[] data, ref int offset) { return data[offset++]; } /// /// Writes an unsigned 8-bit value to a byte stream, advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 1. /// Value to write. public static void WriteU8(byte[] data, ref int offset, byte val) { data[offset++] = val; } #region Little-Endian /// /// Reads a little-endian value from a file stream. /// private static int ReadLE(Stream stream, int width, out bool ok) { int result = 0; int okCheck = 0; for (int i = 0; i < width; i++) { int val = stream.ReadByte(); // returns -1 on EOF okCheck |= val; // sign bit set after bad read result |= val << (i * 8); } ok = (okCheck >= 0); return result; } /// /// Writes a little-endian value to a file stream. /// private static void WriteLE(Stream stream, int width, int value) { for (int i = 0; i < width; i++) { stream.WriteByte((byte)value); value >>= 8; } } /// /// Gets an unsigned 16-bit little-endian value from a byte stream. /// /// Byte stream. /// Data offset. /// Value retrieved. public static ushort GetU16LE(byte[] data, int offset) { return (ushort)(data[offset] | (data[offset + 1] << 8)); } /// /// Sets an unsigned 16-bit little-endian value in a byte stream. /// /// Byte stream. /// Data offset. /// Value to write. public static void SetU16LE(byte[] data, int offset, ushort val) { data[offset] = (byte)val; data[offset + 1] = (byte)(val >> 8); } /// /// Reads an unsigned 16-bit little-endian value from a byte stream, /// advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 2. /// Value retrieved. public static ushort ReadU16LE(byte[] data, ref int offset) { ushort val = (ushort)(data[offset] | (data[offset + 1] << 8)); offset += 2; return val; } /// /// Reads an unsigned 16-bit little-endian value from a file stream. /// /// File stream. /// Result: true if file read succeeded. /// Value retrieved. public static ushort ReadU16LE(Stream stream, out bool ok) { return (ushort)ReadLE(stream, sizeof(ushort), out ok); } /// /// Writes an unsigned 16-bit little-endian value to a byte stream, advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 2. /// Value to write. public static void WriteU16LE(byte[] data, ref int offset, ushort val) { data[offset++] = (byte)val; data[offset++] = (byte)(val >> 8); } /// /// Writes an unsigned 16-bit little-endian value to a file stream. /// /// File stream. /// Value to write. public static void WriteU16LE(Stream stream, ushort val) { stream.WriteByte((byte)val); stream.WriteByte((byte)(val >> 8)); } /// /// Gets an unsigned 24-bit little-endian value from a byte stream. /// /// Byte stream. /// Data offset. /// Value retrieved. public static uint GetU24LE(byte[] data, int offset) { uint val = (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16)); return val; } /// /// Writes an unsigned 24-bit little-endian value to a byte stream. /// /// Byte stream. /// Data offset. /// Value to write public static void SetU24LE(byte[] data, int offset, uint val) { data[offset] = (byte)val; data[offset + 1] = (byte)(val >> 8); data[offset + 2] = (byte)(val >> 16); } /// /// Reads an unsigned 24-bit little-endian value from a byte stream, /// advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 3. /// Value retrieved. public static uint ReadU24LE(byte[] data, ref int offset) { uint val = (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16)); offset += 3; return val; } /// /// Writes an unsigned 24-bit little-endian value to a byte stream, advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 3. /// Value to write public static void WriteU24LE(byte[] data, ref int offset, uint val) { data[offset++] = (byte)val; data[offset++] = (byte)(val >> 8); data[offset++] = (byte)(val >> 16); } /// /// Gets an unsigned 32-bit little-endian value from a byte stream. /// /// Byte stream. /// Data offset. /// Value retrieved. public static uint GetU32LE(byte[] data, int offset) { return (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)); } /// /// Sets an unsigned 32-bit little-endian value in a byte stream. /// /// Byte stream. /// Data offset. /// Value to write. public static void SetU32LE(byte[] data, int offset, uint val) { data[offset] = (byte)val; data[offset + 1] = (byte)(val >> 8); data[offset + 2] = (byte)(val >> 16); data[offset + 3] = (byte)(val >> 24); } /// /// Reads an unsigned 32-bit little-endian value from a byte stream, /// advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 4. /// Value retrieved. public static uint ReadU32LE(byte[] data, ref int offset) { uint val = (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)); offset += 4; return val; } /// /// Reads an unsigned 32-bit little-endian value from a file stream. /// /// File stream. /// Result: true if file read succeeded. /// Value retrieved. public static uint ReadU32LE(Stream stream, out bool ok) { return (uint)ReadLE(stream, sizeof(uint), out ok); } /// /// Writes an unsigned 32-bit little-endian value to a byte stream, advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 4. /// Value to write. public static void WriteU32LE(byte[] data, ref int offset, uint val) { data[offset++] = (byte)val; data[offset++] = (byte)(val >> 8); data[offset++] = (byte)(val >> 16); data[offset++] = (byte)(val >> 24); } /// /// Writes an unsigned 32-bit little-endian value to a file stream. /// /// File stream. /// Value to write. public static void WriteU32LE(Stream stream, uint val) { WriteLE(stream, sizeof(uint), (int)val); } /// /// Gets an unsigned 64-bit little-endian value from a byte stream. /// /// Byte stream. /// Data offset. /// Value retrieved. public static ulong GetU64LE(byte[] data, int offset) { ulong val = 0; for (int i = 0; i < 8; i++) { val |= (ulong)data[offset + i] << (i * 8); } return val; } /// /// Sets an unsigned 64-bit little-endian value in a byte stream. /// /// Byte stream. /// Data offset. /// Value to write. public static void SetU64LE(byte[] data, int offset, ulong val) { for (int i = 0; i < 8; i++) { data[offset + i] = (byte)val; val >>= 8; } } /// /// Reads an unsigned 64-bit little-endian value from a byte stream, /// advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 8. /// Value retrieved. public static ulong ReadU64LE(byte[] data, ref int offset) { ulong val = GetU64LE(data, offset); offset += 8; return val; } /// /// Writes an unsigned 64-bit little-endian value to a byte stream, advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 8. /// Value to write. public static void WriteU64LE(byte[] data, ref int offset, ulong val) { SetU64LE(data, offset, val); offset += 8; } #endregion Little-Endian #region Big-Endian /// /// Reads a big-endian value from a file stream. /// private static int ReadBE(Stream stream, int width, out bool ok) { int result = 0; int okCheck = 0; for (int i = 0; i < width; i++) { int val = stream.ReadByte(); // returns -1 on EOF okCheck |= val; // sign bit set after bad read result = (result << 8) | val; } ok = (okCheck >= 0); return result; } /// /// Writes a big-endian value to a file stream. /// private static void WriteBE(Stream stream, int width, int value) { for (int i = 0; i < width; i++) { stream.WriteByte((byte)(value >> 24)); value <<= 8; } } /// /// Gets an unsigned 16-bit big-endian value from a byte stream. /// /// Byte stream. /// Data offset. /// Value retrieved. public static ushort GetU16BE(byte[] data, int offset) { return (ushort)((data[offset] << 8) | data[offset + 1]); } /// /// Sets an unsigned 16-bit big-endian value in a byte stream. /// /// Byte stream. /// Data offset. /// Value to write. public static void SetU16BE(byte[] data, int offset, ushort val) { data[offset] = (byte)(val >> 8); data[offset + 1] = (byte)val; } /// /// Reads an unsigned 16-bit big-endian value from a byte stream, /// advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 2. /// Value retrieved. public static ushort ReadU16BE(byte[] data, ref int offset) { ushort val = (ushort)((data[offset] << 8) | data[offset + 1]); offset += 2; return val; } /// /// Writes an unsigned 16-bit big-endian value to a byte stream, advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 2. /// Value to write. public static void WriteU16BE(byte[] data, ref int offset, ushort val) { data[offset++] = (byte)(val >> 8); data[offset++] = (byte)val; } /// /// Gets an unsigned 24-bit big-endian value from a byte stream. /// /// Byte stream. /// Data offset. /// Value retrieved. public static uint GetU24BE(byte[] data, int offset) { return (uint)((data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2]); } /// /// Writes an unsigned 24-bit big-endian value to a file stream. /// /// File stream. /// Value to write. public static void WriteU24BE(Stream stream, uint val) { WriteBE(stream, 3, (int)(val << 8)); } /// /// Gets an unsigned 32-bit big-endian value from a byte stream. /// /// Byte stream. /// Data offset. /// Value retrieved. public static uint GetU32BE(byte[] data, int offset) { return (uint)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]); } /// /// Sets an unsigned 32-bit big-endian value in a byte stream. /// /// Byte stream. /// Data offset. /// Value to write. public static void SetU32BE(byte[] data, int offset, uint val) { data[offset] = (byte)(val >> 24); data[offset + 1] = (byte)(val >> 16); data[offset + 2] = (byte)(val >> 8); data[offset + 3] = (byte)val; } /// /// Reads an unsigned 32-bit big-endian value from a byte stream, /// advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 4. /// Value retrieved. public static uint ReadU32BE(byte[] data, ref int offset) { uint val = (uint)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]); offset += 4; return val; } /// /// Reads an unsigned 32-bit big-endian value from a file stream. /// /// File stream. /// Result: true if file read succeeded. /// Value retrieved. public static uint ReadU32BE(Stream stream, out bool ok) { return (uint)ReadBE(stream, sizeof(uint), out ok); } /// /// Writes an unsigned 32-bit big-endian value to a byte stream, advancing the offset. /// /// Byte stream. /// Initial offset. Value will be incremented by 4. /// Value to write. public static void WriteU32BE(byte[] data, ref int offset, uint val) { data[offset++] = (byte)(val >> 24); data[offset++] = (byte)(val >> 16); data[offset++] = (byte)(val >> 8); data[offset++] = (byte)val; } /// /// Writes an unsigned 32-bit big-endian value to a file stream. /// /// File stream. /// Value to write. public static void WriteU32BE(Stream stream, uint val) { WriteBE(stream, sizeof(uint), (int)val); } #endregion Big-Endian /// /// Determines whether a region of data is entirely zeroes. /// /// Data buffer. /// Starting offset. /// Length of data to check. /// True if all values are zero. /// Invalid length. public static bool IsAllZeroes(byte[] data, int offset, int length) { return IsAllValue(data, offset, length, 0); } /// /// Determines whether a region of data is entirely one specific value. /// /// Data buffer. /// Starting offset. /// Length of data to check. /// Value to compare to. /// True if all values in the buffer match the argument. /// Invalid length. public static bool IsAllValue(byte[] data, int offset, int length, byte value) { if (length <= 0) { throw new ArgumentOutOfRangeException(nameof(length)); } while (length-- != 0) { if (data[offset++] != value) { return false; } } return true; } /// /// Finds the first zero byte in a region of data. /// /// Data buffer. /// Starting offset. /// Length of data to check. /// Index of first zero byte. /// public static int FirstZero(byte[] data, int offset, int length) { if (length <= 0) { throw new ArgumentOutOfRangeException(nameof(length)); } for (int i = 0; i < length; i++) { if (data[offset + i] == 0) { return i; } } return -1; } /// /// Compares two byte arrays. /// /// True if they match, false if they're different. public static bool CompareBytes(byte[] ar1, byte[] ar2, int count) { if (ar1.Length < count || ar2.Length < count) { return false; } for (int i = 0; i < count; i++) { if (ar1[i] != ar2[i]) { return false; } } return true; } /// /// Compares two byte arrays. /// /// True if they match, false if they're different. public static bool CompareBytes(byte[] ar1, int offset1, byte[] ar2, int offset2, int count) { for (int i = 0; i < count; i++) { if (ar1[offset1 + i] != ar2[offset2 + i]) { return false; } } return true; } /// /// Compares the contents of two byte arrays. /// /// First array. /// Second array. /// Number of bytes to compare. May be zero. /// A value less than, equal to, or greater than zero depending on whether the /// first "count" bytes of ar1 are less then, equal to, or greater than those in /// ar2. public static int MemCmp(byte[] ar1, byte[] ar2, int count) { if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count), "must be positive"); } if (ar1.Length < count || ar2.Length < count) { throw new ArgumentException("invalid count: len1=" + ar1.Length + " len2=" + ar2.Length + " count=" + count); } for (int i = 0; i < count; i++) { if (ar1[i] != ar2[i]) { if (ar1[i] < ar2[i]) { return -1; } else { return 1; } } } return 0; } /// /// Sets a range of bytes to a value. /// /// /// cf. https://stackoverflow.com/q/1897555/294248 /// /// Array to operate on. /// Starting offset. /// Length of region. /// Value to store. public static void MemSet(byte[] array, int offset, int length, byte value) { for (int i = 0; i < length; i++) { array[offset + i] = value; } } /// /// Converts a 32-bit little-endian character constant to a 4-character string. /// /// /// Assumes ASCII; does not restrict characters to printable values. /// /// Constant. /// Four-character string. public static string StringifyU32LE(uint val) { char[] arr = new char[4]; for (int i = 0; i < 4; i++) { arr[i] = (char)((byte)(val >> i * 8)); } return new string(arr); } /// /// Converts a 32-bit big-endian character constant to a 4-character string. /// /// /// Assumes ASCII; does not restrict characters to printable values. /// /// Constant. /// Four-character string. public static string StringifyU32BE(uint val) { uint leVal = (val >> 16) | ((val << 16) & 0xffff0000); leVal = ((leVal >> 8) & 0x00ff00ff) | ((leVal << 8) & 0xff00ff00); return StringifyU32LE(leVal); } /// /// Converts a 4-character ASCII string to a 32-bit integer. /// /// /// Results are undefined if the argument includes non-ASCII chars. /// /// String to convert. /// Integer representation. public static int IntifyASCII(string str) { if (str.Length != 4) { throw new ArgumentException("String must be four characters: '" + str + "'"); } return (str[0] << 24) | (str[1] << 16) | (str[2] << 8) | str[3]; } /// /// Generates a simple hex dump from a byte array. /// /// Data to dump. /// Start offset. /// Length of data. /// Hex dump in string. public static string SimpleHexDump(byte[] array, int offset, int len) { StringBuilder sb = new StringBuilder(); string addrFmt; if (len >= 0x100) { addrFmt = "x4"; } else { addrFmt = "x2"; } for (int i = 0; i < len; i++) { if ((i & 0x0f) == 0) { if (i != 0) { sb.AppendLine(); } sb.Append(i.ToString(addrFmt)); sb.Append(':'); } sb.Append(' '); sb.Append(array[offset + i].ToString("x2")); } sb.AppendLine(); return sb.ToString(); } /// /// Generates a simple hex dump from a byte array. /// /// Data to dump. /// Hex dump in string. public static string SimpleHexDump(byte[] array) { return SimpleHexDump(array, 0, array.Length); } #if false // The compiler is apparently clever enough to figure out what CompareBytes() is // doing and make it very efficient, so there's no value in getting fancy. In fact, // the fancy versions are slightly slower. // cf. https://stackoverflow.com/a/48599119/294248 [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] static extern int memcmp(byte[] b1, byte[] b2, long count); static bool CompareBytesM(byte[] b1, byte[] b2) { // Validate buffers are the same length. // This also ensures that the count does not exceed the length of either buffer. return b1.Length == b2.Length && memcmp(b1, b2, b1.Length) == 0; } public static bool CompareBytesS(byte[] ar1, int offset1, byte[] ar2, int offset2, int count) { ReadOnlySpan span1 = new ReadOnlySpan(ar1, offset1, count); ReadOnlySpan span2 = new ReadOnlySpan(ar2, offset2, count); return span1.SequenceEqual(span2); } public static void TestSpeed() { int count = 1024 * 1024 + 9; // just over 1MB byte[] array1 = new byte[count]; byte[] array2 = new byte[count]; for (int i = 0; i < count; i++) { array1[i] = array2[i] = (byte)i; } Debug.Assert(CompareBytes(array1, array2)); Debug.Assert(CompareBytesM(array1, array2)); Debug.Assert(CompareBytes(array1, 0, array2, 0, array1.Length)); Debug.Assert(CompareBytesS(array1, 0, array2, 0, array1.Length)); const int RUNS = 100; Stopwatch stopWatch = new Stopwatch(); int good; good = 0; stopWatch.Start(); for (int i = 0; i < RUNS; i++) { good += CompareBytes(array1, array2) ? 1 : 0; } stopWatch.Stop(); Debug.WriteLine("Simple (" + good + "): " + stopWatch.ElapsedMilliseconds); good = 0; stopWatch.Start(); for (int i = 0; i < RUNS; i++) { good += CompareBytesM(array1, array2) ? 1 : 0; } stopWatch.Stop(); Debug.WriteLine("Memcmp (" + good + "): " + stopWatch.ElapsedMilliseconds); good = 0; stopWatch.Start(); for (int i = 0; i < RUNS; i++) { good += CompareBytes(array1, 3, array2, 3, array1.Length - 3) ? 1 : 0; } stopWatch.Stop(); Debug.WriteLine("Offsets (" + good + "): " + stopWatch.ElapsedMilliseconds); good = 0; stopWatch.Start(); for (int i = 0; i < RUNS; i++) { good += CompareBytesS(array1, 3, array2, 3, array1.Length - 3) ? 1 : 0; } stopWatch.Stop(); Debug.WriteLine("Spans (" + good + "): " + stopWatch.ElapsedMilliseconds); } #endif } }