From 99fd511714bdb9b6d1cec181173b92df792998bd Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 3 Mar 2021 14:46:24 +0000 Subject: [PATCH] (#28) (#29) BinaryReader/Writer Stream extensions Introduces type: - Endianness Introduces Read methods: - ReadInt16 - ReadInt32 - ReadInt64 - ReadUInt16 - ReadUInt32 - ReadUInt64 Introduces Write methods: - Write(short) - Write(int) - Write(long) - Write(ushort) - Write(uint) - Write(ulong) Introduces internal "Util" class to provide internal-only helper methods - SwapIfNeeded (reverse a buffer on Endianness mismatch) --- X10D/src/Endianness.cs | 16 + X10D/src/StreamExtensions/StreamExtensions.cs | 357 ++++++++++++++++++ X10D/src/Util.cs | 21 ++ 3 files changed, 394 insertions(+) create mode 100644 X10D/src/Endianness.cs create mode 100644 X10D/src/Util.cs diff --git a/X10D/src/Endianness.cs b/X10D/src/Endianness.cs new file mode 100644 index 0000000..8b7202e --- /dev/null +++ b/X10D/src/Endianness.cs @@ -0,0 +1,16 @@ +using System.ComponentModel; + +namespace X10D +{ + /// + /// Represents an enumeration of endianness values. + /// + public enum Endianness + { + [Description("The value should be read as though it uses little endian encoding.")] + LittleEndian, + + [Description("The value should be read as though it uses big endian encoding.")] + BigEndian + } +} diff --git a/X10D/src/StreamExtensions/StreamExtensions.cs b/X10D/src/StreamExtensions/StreamExtensions.cs index 18dc5ad..20e4e48 100644 --- a/X10D/src/StreamExtensions/StreamExtensions.cs +++ b/X10D/src/StreamExtensions/StreamExtensions.cs @@ -9,6 +9,9 @@ namespace X10D.StreamExtensions /// public static class StreamExtensions { + private static readonly Endianness DefaultEndianness = + BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; + /// /// Returns the hash of a stream using the specified hash algorithm. /// @@ -49,5 +52,359 @@ namespace X10D.StreamExtensions return crypt.ComputeHash(stream); } + + /// + /// Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two + /// bytes using the default endian encoding. + /// + /// The stream to read. + /// A 2-byte signed integer read from the current stream. + public static short ReadInt16(this Stream stream) + { + return stream.ReadInt16(DefaultEndianness); + } + + /// + /// Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two + /// bytes using a specified endian encoding. + /// + /// The stream to read. + /// The endian encoding to use. + /// A 2-byte signed integer read from the current stream. + public static short ReadInt16(this Stream stream, Endianness endianness) + { + var value = ReadInternal(stream, endianness); + return BitConverter.ToInt16(value, 0); + } + + /// + /// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four + /// bytes using the default endian encoding. + /// + /// The stream to read. + /// A 4-byte signed integer read from the current stream. + public static int ReadInt32(this Stream stream) + { + return stream.ReadInt32(DefaultEndianness); + } + + /// + /// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four + /// bytes using a specified endian encoding. + /// + /// The stream to read. + /// The endian encoding to use. + /// A 4-byte signed integer read from the current stream. + public static int ReadInt32(this Stream stream, Endianness endianness) + { + var value = ReadInternal(stream, endianness); + return BitConverter.ToInt32(value, 0); + } + + /// + /// Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight + /// bytes using the default endian encoding. + /// + /// The stream to read. + /// An 8-byte signed integer read from the current stream. + public static long ReadInt64(this Stream stream) + { + return stream.ReadInt64(DefaultEndianness); + } + + /// + /// Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight + /// bytes using a specified endian encoding. + /// + /// The stream to read. + /// The endian encoding to use. + /// An 8-byte signed integer read from the current stream. + public static long ReadInt64(this Stream stream, Endianness endianness) + { + var value = ReadInternal(stream, endianness); + return BitConverter.ToInt64(value, 0); + } + + /// + /// Reads a 2-byte unsigned integer from the current stream and advances the current position of the stream by two + /// bytes using the default endian encoding. + /// + /// The stream to read. + /// A 2-byte unsigned integer read from the current stream. + public static ushort ReadUInt16(this Stream stream) + { + return stream.ReadUInt16(DefaultEndianness); + } + + /// + /// Reads a 2-byte unsigned integer from the current stream and advances the current position of the stream by two + /// bytes using a specified endian encoding. + /// + /// The stream to read. + /// The endian encoding to use. + /// A 2-byte unsigned integer read from the current stream. + public static ushort ReadUInt16(this Stream stream, Endianness endianness) + { + var value = ReadInternal(stream, endianness); + return BitConverter.ToUInt16(value, 0); + } + + /// + /// Reads a 4-byte unsigned integer from the current stream and advances the current position of the stream by four + /// bytes using the default endian encoding. + /// + /// The stream to read. + /// A 4-byte unsigned integer read from the current stream. + public static uint ReadUInt32(this Stream stream) + { + return stream.ReadUInt32(DefaultEndianness); + } + + /// + /// Reads a 4-byte unsigned integer from the current stream and advances the current position of the stream by four + /// bytes using a specified endian encoding. + /// + /// The stream to read. + /// The endian encoding to use. + /// A 4-byte unsigned integer read from the current stream. + public static uint ReadUInt32(this Stream stream, Endianness endianness) + { + var value = ReadInternal(stream, endianness); + return BitConverter.ToUInt32(value, 0); + } + + /// + /// Reads an 8-byte unsigned integer from the current stream and advances the current position of the stream by eight + /// bytes using the default endian encoding. + /// + /// The stream to read. + /// An 8-byte unsigned integer read from the current stream. + public static ulong ReadUInt64(this Stream stream) + { + return stream.ReadUInt64(DefaultEndianness); + } + + /// + /// Reads an 8-byte unsigned integer from the current stream and advances the current position of the stream by eight + /// bytes using a specified endian encoding. + /// + /// The stream to read. + /// The endian encoding to use. + /// An 8-byte unsigned integer read from the current stream. + public static ulong ReadUInt64(this Stream stream, Endianness endianness) + { + var value = ReadInternal(stream, endianness); + return BitConverter.ToUInt64(value, 0); + } + + /// + /// Writes a two-byte signed integer to the current stream using the system's default endian encoding, and advances + /// the stream position by two bytes. + /// + /// The stream to which the value should be written. + /// The two-byte signed integer to write. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, short value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes a two-byte signed integer to the current stream using the specified endian encoding, and advances the + /// stream position by two bytes. + /// + /// The stream to which the value should be written. + /// The two-byte signed integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, short value, Endianness endianness) + { + var buffer = BitConverter.GetBytes(value); + return stream.WriteInternal(buffer, endianness); + } + + /// + /// Writes a four-byte signed integer to the current stream using the system's default endian encoding, and advances + /// the stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The four-byte signed integer to write. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, int value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes a four-byte signed integer to the current stream using the specified endian encoding, and advances the + /// stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The four-byte signed integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, int value, Endianness endianness) + { + var buffer = BitConverter.GetBytes(value); + return stream.WriteInternal(buffer, endianness); + } + + /// + /// Writes an eight-byte signed integer to the current stream using the system's default endian encoding, and advances + /// the stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The eight-byte signed integer to write. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, long value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes an eight-byte signed integer to the current stream using the specified endian encoding, and advances the + /// stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The eight-byte signed integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, long value, Endianness endianness) + { + var buffer = BitConverter.GetBytes(value); + return stream.WriteInternal(buffer, endianness); + } + + /// + /// Writes a two-byte unsigned integer to the current stream using the system's default endian encoding, and advances + /// the stream position by two bytes. + /// + /// The stream to which the value should be written. + /// The two-byte unsigned integer to write. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, ushort value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes a two-byte unsigned integer to the current stream using the specified endian encoding, and advances the + /// stream position by two bytes. + /// + /// The stream to which the value should be written. + /// The two-byte unsigned integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, ushort value, Endianness endianness) + { + var buffer = BitConverter.GetBytes(value); + return stream.WriteInternal(buffer, endianness); + } + + /// + /// Writes a four-byte unsigned integer to the current stream using the system's default endian encoding, and advances + /// the stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The four-byte unsigned integer to write. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, uint value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes a four-byte unsigned integer to the current stream using the specified endian encoding, and advances the + /// stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The four-byte unsigned integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, uint value, Endianness endianness) + { + var buffer = BitConverter.GetBytes(value); + return stream.WriteInternal(buffer, endianness); + } + + /// + /// Writes an eight-byte unsigned integer to the current stream using the system's default endian encoding, and + /// advances the stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The eight-byte unsigned integer to write. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, ulong value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes an eight-byte signed integer to the current stream using the specified endian encoding, and advances the + /// stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The eight-byte signed integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, ulong value, Endianness endianness) + { + var buffer = BitConverter.GetBytes(value); + return stream.WriteInternal(buffer, endianness); + } + + private static unsafe byte[] ReadInternal(this Stream stream, Endianness endianness) + where T : unmanaged + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!stream.CanRead) + { + throw new ArgumentException(ExceptionMessages.StreamDoesNotSupportReading, nameof(stream)); + } + + if (!Enum.IsDefined(typeof(Endianness), endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + var buffer = new byte[sizeof(T)]; + stream.Read(buffer, 0, buffer.Length); + Util.SwapIfNeeded(ref buffer, endianness); + return buffer; + } + + private static int WriteInternal(this Stream stream, byte[] value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!stream.CanWrite) + { + throw new ArgumentException(ExceptionMessages.StreamDoesNotSupportWriting, nameof(stream)); + } + + if (!Enum.IsDefined(typeof(Endianness), endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + byte[] clone = (byte[])value.Clone(); + Util.SwapIfNeeded(ref clone, endianness); + var preWritePosition = stream.Position; + stream.Write(clone, 0, clone.Length); + return (int)(stream.Position - preWritePosition); + } } } diff --git a/X10D/src/Util.cs b/X10D/src/Util.cs new file mode 100644 index 0000000..e7cf77d --- /dev/null +++ b/X10D/src/Util.cs @@ -0,0 +1,21 @@ +using System; + +namespace X10D +{ + internal static class Util + { + public static void SwapIfNeeded(ref byte[] buffer, Endianness endianness) + { + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + var swapNeeded = BitConverter.IsLittleEndian == (endianness == Endianness.BigEndian); + if (swapNeeded) + { + Array.Reverse(buffer); + } + } + } +}