(#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)
This commit is contained in:
Oliver Booth 2021-03-03 14:46:24 +00:00
parent ef9dc29e42
commit 99fd511714
3 changed files with 394 additions and 0 deletions

16
X10D/src/Endianness.cs Normal file
View File

@ -0,0 +1,16 @@
using System.ComponentModel;
namespace X10D
{
/// <summary>
/// Represents an enumeration of endianness values.
/// </summary>
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
}
}

View File

@ -9,6 +9,9 @@ namespace X10D.StreamExtensions
/// </summary>
public static class StreamExtensions
{
private static readonly Endianness DefaultEndianness =
BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian;
/// <summary>
/// Returns the hash of a stream using the specified hash algorithm.
/// </summary>
@ -49,5 +52,359 @@ namespace X10D.StreamExtensions
return crypt.ComputeHash(stream);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <returns>A 2-byte signed integer read from the current stream.</returns>
public static short ReadInt16(this Stream stream)
{
return stream.ReadInt16(DefaultEndianness);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <param name="endianness">The endian encoding to use.</param>
/// <returns>A 2-byte signed integer read from the current stream.</returns>
public static short ReadInt16(this Stream stream, Endianness endianness)
{
var value = ReadInternal<short>(stream, endianness);
return BitConverter.ToInt16(value, 0);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <returns>A 4-byte signed integer read from the current stream.</returns>
public static int ReadInt32(this Stream stream)
{
return stream.ReadInt32(DefaultEndianness);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <param name="endianness">The endian encoding to use.</param>
/// <returns>A 4-byte signed integer read from the current stream.</returns>
public static int ReadInt32(this Stream stream, Endianness endianness)
{
var value = ReadInternal<int>(stream, endianness);
return BitConverter.ToInt32(value, 0);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <returns>An 8-byte signed integer read from the current stream.</returns>
public static long ReadInt64(this Stream stream)
{
return stream.ReadInt64(DefaultEndianness);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <param name="endianness">The endian encoding to use.</param>
/// <returns>An 8-byte signed integer read from the current stream.</returns>
public static long ReadInt64(this Stream stream, Endianness endianness)
{
var value = ReadInternal<long>(stream, endianness);
return BitConverter.ToInt64(value, 0);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <returns>A 2-byte unsigned integer read from the current stream.</returns>
public static ushort ReadUInt16(this Stream stream)
{
return stream.ReadUInt16(DefaultEndianness);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <param name="endianness">The endian encoding to use.</param>
/// <returns>A 2-byte unsigned integer read from the current stream.</returns>
public static ushort ReadUInt16(this Stream stream, Endianness endianness)
{
var value = ReadInternal<ushort>(stream, endianness);
return BitConverter.ToUInt16(value, 0);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <returns>A 4-byte unsigned integer read from the current stream.</returns>
public static uint ReadUInt32(this Stream stream)
{
return stream.ReadUInt32(DefaultEndianness);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <param name="endianness">The endian encoding to use.</param>
/// <returns>A 4-byte unsigned integer read from the current stream.</returns>
public static uint ReadUInt32(this Stream stream, Endianness endianness)
{
var value = ReadInternal<uint>(stream, endianness);
return BitConverter.ToUInt32(value, 0);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <returns>An 8-byte unsigned integer read from the current stream.</returns>
public static ulong ReadUInt64(this Stream stream)
{
return stream.ReadUInt64(DefaultEndianness);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <param name="endianness">The endian encoding to use.</param>
/// <returns>An 8-byte unsigned integer read from the current stream.</returns>
public static ulong ReadUInt64(this Stream stream, Endianness endianness)
{
var value = ReadInternal<ulong>(stream, endianness);
return BitConverter.ToUInt64(value, 0);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to which the value should be written.</param>
/// <param name="value">The two-byte signed integer to write.</param>
/// <returns>The number of bytes written to the stream.</returns>
public static int Write(this Stream stream, short value)
{
return stream.Write(value, DefaultEndianness);
}
/// <summary>
/// Writes a two-byte signed integer to the current stream using the specified endian encoding, and advances the
/// stream position by two bytes.
/// </summary>
/// <param name="stream">The stream to which the value should be written.</param>
/// <param name="value">The two-byte signed integer to write.</param>
/// <param name="endianness">The endian encoding to use.</param>
/// <returns>The number of bytes written to the stream.</returns>
public static int Write(this Stream stream, short value, Endianness endianness)
{
var buffer = BitConverter.GetBytes(value);
return stream.WriteInternal(buffer, endianness);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to which the value should be written.</param>
/// <param name="value">The four-byte signed integer to write.</param>
/// <returns>The number of bytes written to the stream.</returns>
public static int Write(this Stream stream, int value)
{
return stream.Write(value, DefaultEndianness);
}
/// <summary>
/// Writes a four-byte signed integer to the current stream using the specified endian encoding, and advances the
/// stream position by four bytes.
/// </summary>
/// <param name="stream">The stream to which the value should be written.</param>
/// <param name="value">The four-byte signed integer to write.</param>
/// <param name="endianness">The endian encoding to use.</param>
/// <returns>The number of bytes written to the stream.</returns>
public static int Write(this Stream stream, int value, Endianness endianness)
{
var buffer = BitConverter.GetBytes(value);
return stream.WriteInternal(buffer, endianness);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to which the value should be written.</param>
/// <param name="value">The eight-byte signed integer to write.</param>
/// <returns>The number of bytes written to the stream.</returns>
public static int Write(this Stream stream, long value)
{
return stream.Write(value, DefaultEndianness);
}
/// <summary>
/// Writes an eight-byte signed integer to the current stream using the specified endian encoding, and advances the
/// stream position by eight bytes.
/// </summary>
/// <param name="stream">The stream to which the value should be written.</param>
/// <param name="value">The eight-byte signed integer to write.</param>
/// <param name="endianness">The endian encoding to use.</param>
/// <returns>The number of bytes written to the stream.</returns>
public static int Write(this Stream stream, long value, Endianness endianness)
{
var buffer = BitConverter.GetBytes(value);
return stream.WriteInternal(buffer, endianness);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to which the value should be written.</param>
/// <param name="value">The two-byte unsigned integer to write.</param>
/// <returns>The number of bytes written to the stream.</returns>
[CLSCompliant(false)]
public static int Write(this Stream stream, ushort value)
{
return stream.Write(value, DefaultEndianness);
}
/// <summary>
/// Writes a two-byte unsigned integer to the current stream using the specified endian encoding, and advances the
/// stream position by two bytes.
/// </summary>
/// <param name="stream">The stream to which the value should be written.</param>
/// <param name="value">The two-byte unsigned integer to write.</param>
/// <param name="endianness">The endian encoding to use.</param>
/// <returns>The number of bytes written to the stream.</returns>
[CLSCompliant(false)]
public static int Write(this Stream stream, ushort value, Endianness endianness)
{
var buffer = BitConverter.GetBytes(value);
return stream.WriteInternal(buffer, endianness);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to which the value should be written.</param>
/// <param name="value">The four-byte unsigned integer to write.</param>
/// <returns>The number of bytes written to the stream.</returns>
[CLSCompliant(false)]
public static int Write(this Stream stream, uint value)
{
return stream.Write(value, DefaultEndianness);
}
/// <summary>
/// Writes a four-byte unsigned integer to the current stream using the specified endian encoding, and advances the
/// stream position by four bytes.
/// </summary>
/// <param name="stream">The stream to which the value should be written.</param>
/// <param name="value">The four-byte unsigned integer to write.</param>
/// <param name="endianness">The endian encoding to use.</param>
/// <returns>The number of bytes written to the stream.</returns>
[CLSCompliant(false)]
public static int Write(this Stream stream, uint value, Endianness endianness)
{
var buffer = BitConverter.GetBytes(value);
return stream.WriteInternal(buffer, endianness);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The stream to which the value should be written.</param>
/// <param name="value">The eight-byte unsigned integer to write.</param>
/// <returns>The number of bytes written to the stream.</returns>
[CLSCompliant(false)]
public static int Write(this Stream stream, ulong value)
{
return stream.Write(value, DefaultEndianness);
}
/// <summary>
/// Writes an eight-byte signed integer to the current stream using the specified endian encoding, and advances the
/// stream position by eight bytes.
/// </summary>
/// <param name="stream">The stream to which the value should be written.</param>
/// <param name="value">The eight-byte signed integer to write.</param>
/// <param name="endianness">The endian encoding to use.</param>
/// <returns>The number of bytes written to the stream.</returns>
[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<T>(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);
}
}
}

21
X10D/src/Util.cs Normal file
View File

@ -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);
}
}
}
}