/*
* 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
}
}