diff --git a/README.md b/README.md index 4a814bf..e2a5414 100644 --- a/README.md +++ b/README.md @@ -63,32 +63,32 @@ class MyBasicObj And assume these are our input bytes (in little endian): ### Input Bytes (Little Endian): ```cs -0x00, 0x08, -0xFF, 0x01, -0x00, 0x00, 0x4A, 0x7A, 0x9E, 0x01, 0xC0, 0x08, - -0x00, 0x00, 0x00, 0x00, -0x01, 0x00, 0x00, 0x00, -0x02, 0x00, 0x00, 0x00, -0x03, 0x00, 0x00, 0x00, -0x04, 0x00, 0x00, 0x00, -0x05, 0x00, 0x00, 0x00, -0x06, 0x00, 0x00, 0x00, -0x07, 0x00, 0x00, 0x00, -0x08, 0x00, 0x00, 0x00, -0x09, 0x00, 0x00, 0x00, -0x0A, 0x00, 0x00, 0x00, -0x0B, 0x00, 0x00, 0x00, -0x0C, 0x00, 0x00, 0x00, -0x0D, 0x00, 0x00, 0x00, -0x0E, 0x00, 0x00, 0x00, -0x0F, 0x00, 0x00, 0x00, - -0x00, 0x00, 0x00, 0x00, - -0x45, 0x6E, 0x64, 0x69, 0x61, 0x6E, 0x42, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x49, 0x4F, 0x00, - -0x4B, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6D, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00 +0x00, 0x08, // ShortSizedEnum.Val2 +0xFF, 0x01, // (short)0x1FF +0x00, 0x00, 0x4A, 0x7A, 0x9E, 0x01, 0xC0, 0x08, // (DateTime)Dec. 30, 1998 + +0x00, 0x00, 0x00, 0x00, // (uint)0 +0x01, 0x00, 0x00, 0x00, // (uint)1 +0x02, 0x00, 0x00, 0x00, // (uint)2 +0x03, 0x00, 0x00, 0x00, // (uint)3 +0x04, 0x00, 0x00, 0x00, // (uint)4 +0x05, 0x00, 0x00, 0x00, // (uint)5 +0x06, 0x00, 0x00, 0x00, // (uint)6 +0x07, 0x00, 0x00, 0x00, // (uint)7 +0x08, 0x00, 0x00, 0x00, // (uint)8 +0x09, 0x00, 0x00, 0x00, // (uint)9 +0x0A, 0x00, 0x00, 0x00, // (uint)10 +0x0B, 0x00, 0x00, 0x00, // (uint)11 +0x0C, 0x00, 0x00, 0x00, // (uint)12 +0x0D, 0x00, 0x00, 0x00, // (uint)13 +0x0E, 0x00, 0x00, 0x00, // (uint)14 +0x0F, 0x00, 0x00, 0x00, // (uint)15 + +0x00, 0x00, 0x00, 0x00, // (bool32)false + +0x45, 0x6E, 0x64, 0x69, 0x61, 0x6E, 0x42, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x49, 0x4F, 0x00, // "EndianBinaryIO\0" + +0x4B, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6D, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, // "Kermalis\0\0" ``` We can read/write the object manually or automatically (with reflection): diff --git a/Source/Attributes.cs b/Source/Attributes.cs index 092e936..74cd135 100644 --- a/Source/Attributes.cs +++ b/Source/Attributes.cs @@ -49,7 +49,7 @@ public sealed class BinaryArrayFixedLengthAttribute : Attribute public BinaryArrayFixedLengthAttribute(int length) { - if (length <= 0) + if (length < 0) { throw new ArgumentOutOfRangeException(nameof(length)); } diff --git a/Source/EndianBinaryIO.csproj b/Source/EndianBinaryIO.csproj index ca31666..ad29e8f 100644 --- a/Source/EndianBinaryIO.csproj +++ b/Source/EndianBinaryIO.csproj @@ -9,7 +9,7 @@ EndianBinaryIO EndianBinaryIO EndianBinaryIO - 1.0.2.0 + 1.0.3.0 https://github.com/Kermalis/EndianBinaryIO git diff --git a/Source/EndianBinaryReader.cs b/Source/EndianBinaryReader.cs index 3f73d1e..886a3fb 100644 --- a/Source/EndianBinaryReader.cs +++ b/Source/EndianBinaryReader.cs @@ -40,6 +40,21 @@ public void Dispose() } } + // Returns true if count is 0 + private static bool ValidateArraySize(int count, out T[] array) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException($"Invalid array length ({count})"); + } + if (count == 0) + { + array = Array.Empty(); + return true; + } + array = null; + return false; + } private void ReadBytesIntoBuffer(int primitiveCount, int primitiveSize) { int byteCount = primitiveCount * primitiveSize; @@ -150,10 +165,13 @@ public bool[] ReadBooleans(int count, long offset) } public bool[] ReadBooleans(int count, BooleanSize size) { - bool[] array = new bool[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out bool[] array)) { - array[i] = ReadBoolean(size); + array = new bool[count]; + for (int i = 0; i < count; i++) + { + array[i] = ReadBoolean(size); + } } return array; } @@ -174,11 +192,14 @@ public byte ReadByte(long offset) } public byte[] ReadBytes(int count) { - ReadBytesIntoBuffer(count, 1); - byte[] array = new byte[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out byte[] array)) { - array[i] = _buffer[i]; + ReadBytesIntoBuffer(count, 1); + array = new byte[count]; + for (int i = 0; i < count; i++) + { + array[i] = _buffer[i]; + } } return array; } @@ -199,11 +220,14 @@ public sbyte ReadSByte(long offset) } public sbyte[] ReadSBytes(int count) { - ReadBytesIntoBuffer(count, 1); - sbyte[] array = new sbyte[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out sbyte[] array)) { - array[i] = (sbyte)_buffer[i]; + ReadBytesIntoBuffer(count, 1); + array = new sbyte[count]; + for (int i = 0; i < count; i++) + { + array[i] = (sbyte)_buffer[i]; + } } return array; } @@ -244,6 +268,10 @@ public char[] ReadChars(int count, long offset) } public char[] ReadChars(int count, EncodingType encodingType) { + if (ValidateArraySize(count, out char[] array)) + { + return array; + } Encoding encoding = Utils.EncodingFromEnum(encodingType); int encodingSize = Utils.EncodingSize(encoding); ReadBytesIntoBuffer(count, encodingSize); @@ -300,6 +328,58 @@ public string ReadString(int charCount, EncodingType encodingType, long offset) BaseStream.Position = offset; return ReadString(charCount, encodingType); } + public string[] ReadStringsNullTerminated(int count) + { + return ReadStringsNullTerminated(count, Encoding); + } + public string[] ReadStringsNullTerminated(int count, long offset) + { + BaseStream.Position = offset; + return ReadStringsNullTerminated(count); + } + public string[] ReadStringsNullTerminated(int count, EncodingType encodingType) + { + if (!ValidateArraySize(count, out string[] array)) + { + array = new string[count]; + for (int i = 0; i < count; i++) + { + array[i] = ReadStringNullTerminated(encodingType); + } + } + return array; + } + public string[] ReadStringsNullTerminated(int count, EncodingType encodingType, long offset) + { + BaseStream.Position = offset; + return ReadStringsNullTerminated(count, encodingType); + } + public string[] ReadStrings(int count, int charCount) + { + return ReadStrings(count, charCount, Encoding); + } + public string[] ReadStrings(int count, int charCount, long offset) + { + BaseStream.Position = offset; + return ReadStrings(count, charCount); + } + public string[] ReadStrings(int count, int charCount, EncodingType encodingType) + { + if (!ValidateArraySize(count, out string[] array)) + { + array = new string[count]; + for (int i = 0; i < count; i++) + { + array[i] = ReadString(charCount, encodingType); + } + } + return array; + } + public string[] ReadStrings(int count, int charCount, EncodingType encodingType, long offset) + { + BaseStream.Position = offset; + return ReadStrings(count, charCount, encodingType); + } public short ReadInt16() { ReadBytesIntoBuffer(1, 2); @@ -312,11 +392,14 @@ public short ReadInt16(long offset) } public short[] ReadInt16s(int count) { - ReadBytesIntoBuffer(count, 2); - short[] array = new short[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out short[] array)) { - array[i] = Utils.BytesToInt16(_buffer, 2 * i); + ReadBytesIntoBuffer(count, 2); + array = new short[count]; + for (int i = 0; i < count; i++) + { + array[i] = Utils.BytesToInt16(_buffer, 2 * i); + } } return array; } @@ -337,11 +420,14 @@ public ushort ReadUInt16(long offset) } public ushort[] ReadUInt16s(int count) { - ReadBytesIntoBuffer(count, 2); - ushort[] array = new ushort[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out ushort[] array)) { - array[i] = (ushort)Utils.BytesToInt16(_buffer, 2 * i); + ReadBytesIntoBuffer(count, 2); + array = new ushort[count]; + for (int i = 0; i < count; i++) + { + array[i] = (ushort)Utils.BytesToInt16(_buffer, 2 * i); + } } return array; } @@ -362,11 +448,14 @@ public int ReadInt32(long offset) } public int[] ReadInt32s(int count) { - ReadBytesIntoBuffer(count, 4); - int[] array = new int[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out int[] array)) { - array[i] = Utils.BytesToInt32(_buffer, 4 * i); + ReadBytesIntoBuffer(count, 4); + array = new int[count]; + for (int i = 0; i < count; i++) + { + array[i] = Utils.BytesToInt32(_buffer, 4 * i); + } } return array; } @@ -387,11 +476,14 @@ public uint ReadUInt32(long offset) } public uint[] ReadUInt32s(int count) { - ReadBytesIntoBuffer(count, 4); - uint[] array = new uint[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out uint[] array)) { - array[i] = (uint)Utils.BytesToInt32(_buffer, 4 * i); + ReadBytesIntoBuffer(count, 4); + array = new uint[count]; + for (int i = 0; i < count; i++) + { + array[i] = (uint)Utils.BytesToInt32(_buffer, 4 * i); + } } return array; } @@ -412,11 +504,14 @@ public long ReadInt64(long offset) } public long[] ReadInt64s(int count) { - ReadBytesIntoBuffer(count, 8); - long[] array = new long[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out long[] array)) { - array[i] = Utils.BytesToInt64(_buffer, 8 * i); + ReadBytesIntoBuffer(count, 8); + array = new long[count]; + for (int i = 0; i < count; i++) + { + array[i] = Utils.BytesToInt64(_buffer, 8 * i); + } } return array; } @@ -437,11 +532,14 @@ public ulong ReadUInt64(long offset) } public ulong[] ReadUInt64s(int count) { - ReadBytesIntoBuffer(count, 8); - ulong[] array = new ulong[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out ulong[] array)) { - array[i] = (ulong)Utils.BytesToInt64(_buffer, 8 * i); + ReadBytesIntoBuffer(count, 8); + array = new ulong[count]; + for (int i = 0; i < count; i++) + { + array[i] = (ulong)Utils.BytesToInt64(_buffer, 8 * i); + } } return array; } @@ -462,11 +560,14 @@ public float ReadSingle(long offset) } public float[] ReadSingles(int count) { - ReadBytesIntoBuffer(count, 4); - float[] array = new float[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out float[] array)) { - array[i] = Utils.BytesToSingle(_buffer, 4 * i); + ReadBytesIntoBuffer(count, 4); + array = new float[count]; + for (int i = 0; i < count; i++) + { + array[i] = Utils.BytesToSingle(_buffer, 4 * i); + } } return array; } @@ -487,11 +588,14 @@ public double ReadDouble(long offset) } public double[] ReadDoubles(int count) { - ReadBytesIntoBuffer(count, 8); - double[] array = new double[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out double[] array)) { - array[i] = Utils.BytesToDouble(_buffer, 8 * i); + ReadBytesIntoBuffer(count, 8); + array = new double[count]; + for (int i = 0; i < count; i++) + { + array[i] = Utils.BytesToDouble(_buffer, 8 * i); + } } return array; } @@ -512,11 +616,14 @@ public decimal ReadDecimal(long offset) } public decimal[] ReadDecimals(int count) { - ReadBytesIntoBuffer(count, 16); - decimal[] array = new decimal[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out decimal[] array)) { - array[i] = Utils.BytesToDecimal(_buffer, 16 * i); + ReadBytesIntoBuffer(count, 16); + array = new decimal[count]; + for (int i = 0; i < count; i++) + { + array[i] = Utils.BytesToDecimal(_buffer, 16 * i); + } } return array; } @@ -554,10 +661,13 @@ public decimal[] ReadDecimals(int count, long offset) } public TEnum[] ReadEnums(int count) where TEnum : struct, Enum { - var array = new TEnum[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out TEnum[] array)) { - array[i] = ReadEnum(); + array = new TEnum[count]; + for (int i = 0; i < count; i++) + { + array[i] = ReadEnum(); + } } return array; } @@ -578,10 +688,13 @@ public DateTime ReadDateTime(long offset) } public DateTime[] ReadDateTimes(int count) { - var array = new DateTime[count]; - for (int i = 0; i < count; i++) + if (!ValidateArraySize(count, out DateTime[] array)) { - array[i] = ReadDateTime(); + array = new DateTime[count]; + for (int i = 0; i < count; i++) + { + array[i] = ReadDateTime(); + } } return array; } @@ -656,79 +769,80 @@ public void ReadIntoObject(object obj) int arrayLength = Utils.GetArrayLength(obj, objType, propertyInfo); // Get array type Type elementType = propertyType.GetElementType(); - if (elementType.IsEnum) + if (arrayLength == 0) { - elementType = Enum.GetUnderlyingType(elementType); + value = Array.CreateInstance(elementType, 0); // Create 0 length array regardless of type } - switch (Type.GetTypeCode(elementType)) + else { - case TypeCode.Boolean: + if (elementType.IsEnum) { - BooleanSize booleanSize = Utils.AttributeValueOrDefault(propertyInfo, typeof(BinaryBooleanSizeAttribute), BooleanSize); - value = ReadBooleans(arrayLength, booleanSize); - break; + elementType = Enum.GetUnderlyingType(elementType); } - case TypeCode.Byte: value = ReadBytes(arrayLength); break; - case TypeCode.SByte: value = ReadSBytes(arrayLength); break; - case TypeCode.Char: + switch (Type.GetTypeCode(elementType)) { - EncodingType encodingType = Utils.AttributeValueOrDefault(propertyInfo, typeof(BinaryEncodingAttribute), Encoding); - value = ReadChars(arrayLength, encodingType); - break; - } - case TypeCode.Int16: value = ReadInt16s(arrayLength); break; - case TypeCode.UInt16: value = ReadUInt16s(arrayLength); break; - case TypeCode.Int32: value = ReadInt32s(arrayLength); break; - case TypeCode.UInt32: value = ReadUInt32s(arrayLength); break; - case TypeCode.Int64: value = ReadInt64s(arrayLength); break; - case TypeCode.UInt64: value = ReadUInt64s(arrayLength); break; - case TypeCode.Single: value = ReadSingles(arrayLength); break; - case TypeCode.Double: value = ReadDoubles(arrayLength); break; - case TypeCode.Decimal: value = ReadDecimals(arrayLength); break; - case TypeCode.DateTime: value = ReadDateTimes(arrayLength); break; - case TypeCode.String: - { - Utils.GetStringLength(obj, objType, propertyInfo, true, out bool? nullTerminated, out int stringLength); - EncodingType encodingType = Utils.AttributeValueOrDefault(propertyInfo, typeof(BinaryEncodingAttribute), Encoding); - value = Array.CreateInstance(elementType, arrayLength); - for (int i = 0; i < arrayLength; i++) + case TypeCode.Boolean: { - string str; + BooleanSize booleanSize = Utils.AttributeValueOrDefault(propertyInfo, typeof(BinaryBooleanSizeAttribute), BooleanSize); + value = ReadBooleans(arrayLength, booleanSize); + break; + } + case TypeCode.Byte: value = ReadBytes(arrayLength); break; + case TypeCode.SByte: value = ReadSBytes(arrayLength); break; + case TypeCode.Char: + { + EncodingType encodingType = Utils.AttributeValueOrDefault(propertyInfo, typeof(BinaryEncodingAttribute), Encoding); + value = ReadChars(arrayLength, encodingType); + break; + } + case TypeCode.Int16: value = ReadInt16s(arrayLength); break; + case TypeCode.UInt16: value = ReadUInt16s(arrayLength); break; + case TypeCode.Int32: value = ReadInt32s(arrayLength); break; + case TypeCode.UInt32: value = ReadUInt32s(arrayLength); break; + case TypeCode.Int64: value = ReadInt64s(arrayLength); break; + case TypeCode.UInt64: value = ReadUInt64s(arrayLength); break; + case TypeCode.Single: value = ReadSingles(arrayLength); break; + case TypeCode.Double: value = ReadDoubles(arrayLength); break; + case TypeCode.Decimal: value = ReadDecimals(arrayLength); break; + case TypeCode.DateTime: value = ReadDateTimes(arrayLength); break; + case TypeCode.String: + { + Utils.GetStringLength(obj, objType, propertyInfo, true, out bool? nullTerminated, out int stringLength); + EncodingType encodingType = Utils.AttributeValueOrDefault(propertyInfo, typeof(BinaryEncodingAttribute), Encoding); if (nullTerminated == true) { - str = ReadStringNullTerminated(encodingType); + value = ReadStringsNullTerminated(arrayLength, encodingType); } else { - str = ReadString(stringLength, encodingType); + value = ReadStrings(arrayLength, stringLength, encodingType); } - ((Array)value).SetValue(str, i); + break; } - break; - } - case TypeCode.Object: - { - value = Array.CreateInstance(elementType, arrayLength); - if (typeof(IBinarySerializable).IsAssignableFrom(elementType)) + case TypeCode.Object: { - for (int i = 0; i < arrayLength; i++) + value = Array.CreateInstance(elementType, arrayLength); + if (typeof(IBinarySerializable).IsAssignableFrom(elementType)) { - var serializable = (IBinarySerializable)Activator.CreateInstance(elementType); - serializable.Read(this); - ((Array)value).SetValue(serializable, i); + for (int i = 0; i < arrayLength; i++) + { + var serializable = (IBinarySerializable)Activator.CreateInstance(elementType); + serializable.Read(this); + ((Array)value).SetValue(serializable, i); + } } - } - else // Element's type is not supported so try to read the array's objects - { - for (int i = 0; i < arrayLength; i++) + else // Element's type is not supported so try to read the array's objects { - object elementObj = ReadObject(elementType); - ((Array)value).SetValue(elementObj, i); + for (int i = 0; i < arrayLength; i++) + { + object elementObj = ReadObject(elementType); + ((Array)value).SetValue(elementObj, i); + } } + break; } - break; + default: throw new ArgumentOutOfRangeException(nameof(elementType)); } - default: throw new ArgumentOutOfRangeException(nameof(elementType)); } } else diff --git a/Source/EndianBinaryWriter.cs b/Source/EndianBinaryWriter.cs index 023ad8f..6abf474 100644 --- a/Source/EndianBinaryWriter.cs +++ b/Source/EndianBinaryWriter.cs @@ -40,6 +40,15 @@ public void Dispose() } } + // Returns true if count is 0 + private static bool ValidateArraySize(int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException($"Invalid array length ({count})"); + } + return count == 0; + } private void SetBufferSize(int size) { if (_buffer is null || _buffer.Length < size) @@ -135,10 +144,7 @@ public void Write(bool[] value, BooleanSize booleanSize, long offset) } public void Write(bool[] value, int index, int count) { - for (int i = index; i < count; i++) - { - Write(value[i], BooleanSize); - } + Write(value, index, count, BooleanSize); } public void Write(bool[] value, int index, int count, long offset) { @@ -147,6 +153,10 @@ public void Write(bool[] value, int index, int count, long offset) } public void Write(bool[] value, int index, int count, BooleanSize booleanSize) { + if (ValidateArraySize(count)) + { + return; + } for (int i = index; i < count; i++) { Write(value[i], booleanSize); @@ -179,6 +189,10 @@ public void Write(byte[] value, long offset) } public void Write(byte[] value, int index, int count) { + if (ValidateArraySize(count)) + { + return; + } SetBufferSize(count); for (int i = 0; i < count; i++) { @@ -213,6 +227,10 @@ public void Write(sbyte[] value, long offset) } public void Write(sbyte[] value, int index, int count) { + if (ValidateArraySize(count)) + { + return; + } SetBufferSize(count); for (int i = 0; i < count; i++) { @@ -280,6 +298,10 @@ public void Write(char[] value, int index, int count, long offset) } public void Write(char[] value, int index, int count, EncodingType encodingType) { + if (ValidateArraySize(count)) + { + return; + } Encoding encoding = Utils.EncodingFromEnum(encodingType); int encodingSize = Utils.EncodingSize(encoding); SetBufferSize(encodingSize * count); @@ -336,6 +358,56 @@ public void Write(string value, int charCount, EncodingType encodingType, long o BaseStream.Position = offset; Write(value, charCount, encodingType); } + public void Write(string[] value, int index, int count, bool nullTerminated) + { + Write(value, index, count, nullTerminated, Encoding); + } + public void Write(string[] value, int index, int count, bool nullTerminated, long offset) + { + BaseStream.Position = offset; + Write(value, index, count, nullTerminated, Encoding); + } + public void Write(string[] value, int index, int count, bool nullTerminated, EncodingType encodingType) + { + if (ValidateArraySize(count)) + { + return; + } + for (int i = 0; i < count; i++) + { + Write(value[i + index], nullTerminated, encodingType); + } + } + public void Write(string[] value, int index, int count, bool nullTerminated, EncodingType encodingType, long offset) + { + BaseStream.Position = offset; + Write(value, index, count, nullTerminated, encodingType); + } + public void Write(string[] value, int index, int count, int charCount) + { + Write(value, index, count, charCount, Encoding); + } + public void Write(string[] value, int index, int count, int charCount, long offset) + { + BaseStream.Position = offset; + Write(value, index, count, charCount, Encoding); + } + public void Write(string[] value, int index, int count, int charCount, EncodingType encodingType) + { + if (ValidateArraySize(count)) + { + return; + } + for (int i = 0; i < count; i++) + { + Write(value[i + index], charCount, encodingType); + } + } + public void Write(string[] value, int index, int count, int charCount, EncodingType encodingType, long offset) + { + BaseStream.Position = offset; + Write(value, index, count, charCount, encodingType); + } public void Write(short value) { SetBufferSize(2); @@ -362,6 +434,10 @@ public void Write(short[] value, long offset) } public void Write(short[] value, int index, int count) { + if (ValidateArraySize(count)) + { + return; + } SetBufferSize(2 * count); for (int i = 0; i < count; i++) { @@ -404,6 +480,10 @@ public void Write(ushort[] value, long offset) } public void Write(ushort[] value, int index, int count) { + if (ValidateArraySize(count)) + { + return; + } SetBufferSize(2 * count); for (int i = 0; i < count; i++) { @@ -446,6 +526,10 @@ public void Write(int[] value, long offset) } public void Write(int[] value, int index, int count) { + if (ValidateArraySize(count)) + { + return; + } SetBufferSize(4 * count); for (int i = 0; i < count; i++) { @@ -488,6 +572,10 @@ public void Write(uint[] value, long offset) } public void Write(uint[] value, int index, int count) { + if (ValidateArraySize(count)) + { + return; + } SetBufferSize(4 * count); for (int i = 0; i < count; i++) { @@ -530,6 +618,10 @@ public void Write(long[] value, long offset) } public void Write(long[] value, int index, int count) { + if (ValidateArraySize(count)) + { + return; + } SetBufferSize(8 * count); for (int i = 0; i < count; i++) { @@ -572,6 +664,10 @@ public void Write(ulong[] value, long offset) } public void Write(ulong[] value, int index, int count) { + if (ValidateArraySize(count)) + { + return; + } SetBufferSize(8 * count); for (int i = 0; i < count; i++) { @@ -614,6 +710,10 @@ public void Write(float[] value, long offset) } public void Write(float[] value, int index, int count) { + if (ValidateArraySize(count)) + { + return; + } SetBufferSize(4 * count); for (int i = 0; i < count; i++) { @@ -656,6 +756,10 @@ public void Write(double[] value, long offset) } public void Write(double[] value, int index, int count) { + if (ValidateArraySize(count)) + { + return; + } SetBufferSize(8 * count); for (int i = 0; i < count; i++) { @@ -698,6 +802,10 @@ public void Write(decimal[] value, long offset) } public void Write(decimal[] value, int index, int count) { + if (ValidateArraySize(count)) + { + return; + } SetBufferSize(16 * count); for (int i = 0; i < count; i++) { @@ -750,6 +858,10 @@ public void Write(decimal[] value, int index, int count, long offset) } public void Write(TEnum[] value, int index, int count) where TEnum : Enum { + if (ValidateArraySize(count)) + { + return; + } for (int i = 0; i < count; i++) { Write(value[i + index]); @@ -781,6 +893,10 @@ public void Write(DateTime[] value, long offset) } public void Write(DateTime[] value, int index, int count) { + if (ValidateArraySize(count)) + { + return; + } for (int i = 0; i < count; i++) { Write(value[i + index]); @@ -834,76 +950,76 @@ public void Write(object obj) if (propertyType.IsArray) { int arrayLength = Utils.GetArrayLength(obj, objType, propertyInfo); - // Get array type - Type elementType = propertyType.GetElementType(); - if (elementType.IsEnum) + if (arrayLength != 0) // Do not need to do anything for length 0 { - elementType = Enum.GetUnderlyingType(elementType); - } - switch (Type.GetTypeCode(elementType)) - { - case TypeCode.Boolean: - { - BooleanSize booleanSize = Utils.AttributeValueOrDefault(propertyInfo, typeof(BinaryBooleanSizeAttribute), BooleanSize); - Write((bool[])value, 0, arrayLength, booleanSize); - break; - } - case TypeCode.Byte: Write((byte[])value, 0, arrayLength); break; - case TypeCode.SByte: Write((sbyte[])value, 0, arrayLength); break; - case TypeCode.Char: + // Get array type + Type elementType = propertyType.GetElementType(); + if (elementType.IsEnum) { - EncodingType encodingType = Utils.AttributeValueOrDefault(propertyInfo, typeof(BinaryEncodingAttribute), Encoding); - Write((char[])value, 0, arrayLength, encodingType); - break; + elementType = Enum.GetUnderlyingType(elementType); } - case TypeCode.Int16: Write((short[])value, 0, arrayLength); break; - case TypeCode.UInt16: Write((ushort[])value, 0, arrayLength); break; - case TypeCode.Int32: Write((int[])value, 0, arrayLength); break; - case TypeCode.UInt32: Write((uint[])value, 0, arrayLength); break; - case TypeCode.Int64: Write((long[])value, 0, arrayLength); break; - case TypeCode.UInt64: Write((ulong[])value, 0, arrayLength); break; - case TypeCode.Single: Write((float[])value, 0, arrayLength); break; - case TypeCode.Double: Write((double[])value, 0, arrayLength); break; - case TypeCode.Decimal: Write((decimal[])value, 0, arrayLength); break; - case TypeCode.DateTime: Write((DateTime[])value, 0, arrayLength); break; - case TypeCode.String: + switch (Type.GetTypeCode(elementType)) { - Utils.GetStringLength(obj, objType, propertyInfo, false, out bool? nullTerminated, out int stringLength); - EncodingType encodingType = Utils.AttributeValueOrDefault(propertyInfo, typeof(BinaryEncodingAttribute), Encoding); - for (int i = 0; i < arrayLength; i++) + case TypeCode.Boolean: + { + BooleanSize booleanSize = Utils.AttributeValueOrDefault(propertyInfo, typeof(BinaryBooleanSizeAttribute), BooleanSize); + Write((bool[])value, 0, arrayLength, booleanSize); + break; + } + case TypeCode.Byte: Write((byte[])value, 0, arrayLength); break; + case TypeCode.SByte: Write((sbyte[])value, 0, arrayLength); break; + case TypeCode.Char: { - string str = (string)((Array)value).GetValue(i); + EncodingType encodingType = Utils.AttributeValueOrDefault(propertyInfo, typeof(BinaryEncodingAttribute), Encoding); + Write((char[])value, 0, arrayLength, encodingType); + break; + } + case TypeCode.Int16: Write((short[])value, 0, arrayLength); break; + case TypeCode.UInt16: Write((ushort[])value, 0, arrayLength); break; + case TypeCode.Int32: Write((int[])value, 0, arrayLength); break; + case TypeCode.UInt32: Write((uint[])value, 0, arrayLength); break; + case TypeCode.Int64: Write((long[])value, 0, arrayLength); break; + case TypeCode.UInt64: Write((ulong[])value, 0, arrayLength); break; + case TypeCode.Single: Write((float[])value, 0, arrayLength); break; + case TypeCode.Double: Write((double[])value, 0, arrayLength); break; + case TypeCode.Decimal: Write((decimal[])value, 0, arrayLength); break; + case TypeCode.DateTime: Write((DateTime[])value, 0, arrayLength); break; + case TypeCode.String: + { + Utils.GetStringLength(obj, objType, propertyInfo, false, out bool? nullTerminated, out int stringLength); + EncodingType encodingType = Utils.AttributeValueOrDefault(propertyInfo, typeof(BinaryEncodingAttribute), Encoding); if (nullTerminated.HasValue) { - Write(str, nullTerminated.Value, encodingType); + Write((string[])value, 0, arrayLength, nullTerminated.Value, encodingType); } else { - Write(str, stringLength, encodingType); + Write((string[])value, 0, arrayLength, stringLength, encodingType); } + break; } - break; - } - case TypeCode.Object: - { - if (typeof(IBinarySerializable).IsAssignableFrom(elementType)) + case TypeCode.Object: { - for (int i = 0; i < arrayLength; i++) + if (typeof(IBinarySerializable).IsAssignableFrom(elementType)) { - var serializable = (IBinarySerializable)((Array)value).GetValue(i); - serializable.Write(this); + for (int i = 0; i < arrayLength; i++) + { + var serializable = (IBinarySerializable)((Array)value).GetValue(i); + serializable.Write(this); + } } - } - else // Element's type is not supported so try to write the array's objects - { - for (int i = 0; i < arrayLength; i++) + else // Element's type is not supported so try to write the array's objects { - Write(((Array)value).GetValue(i)); + for (int i = 0; i < arrayLength; i++) + { + object elementObj = ((Array)value).GetValue(i); + Write(elementObj); + } } + break; } - break; + default: throw new ArgumentOutOfRangeException(nameof(elementType)); } - default: throw new ArgumentOutOfRangeException(nameof(elementType)); } } else diff --git a/Source/Utils.cs b/Source/Utils.cs index e13bb82..b90a4d5 100644 --- a/Source/Utils.cs +++ b/Source/Utils.cs @@ -210,7 +210,7 @@ public static int GetArrayLength(object obj, Type objType, PropertyInfo property { int Validate(int value) { - if (value <= 0) + if (value < 0) { throw new ArgumentOutOfRangeException($"An array property in \"{objType.FullName}\" has an invalid length attribute ({value})"); } diff --git a/Testing/BasicTests.cs b/Testing/BasicTests.cs index f3e4a91..fc00c52 100644 --- a/Testing/BasicTests.cs +++ b/Testing/BasicTests.cs @@ -40,34 +40,34 @@ private sealed class MyBasicObj public string UTF16String { get; set; } } - private static readonly byte[] _bytes = new byte[115] + private static readonly byte[] _bytes = new byte[] { - 0x00, 0x08, - 0xFF, 0x01, - 0x00, 0x00, 0x4A, 0x7A, 0x9E, 0x01, 0xC0, 0x08, - - 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, - 0x0A, 0x00, 0x00, 0x00, - 0x0B, 0x00, 0x00, 0x00, - 0x0C, 0x00, 0x00, 0x00, - 0x0D, 0x00, 0x00, 0x00, - 0x0E, 0x00, 0x00, 0x00, - 0x0F, 0x00, 0x00, 0x00, - - 0x00, 0x00, 0x00, 0x00, - - 0x45, 0x6E, 0x64, 0x69, 0x61, 0x6E, 0x42, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x49, 0x4F, 0x00, - - 0x4B, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6D, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x00, 0x08, // ShortSizedEnum.Val2 + 0xFF, 0x01, // (short)0x1FF + 0x00, 0x00, 0x4A, 0x7A, 0x9E, 0x01, 0xC0, 0x08, // (DateTime)Dec. 30, 1998 + + 0x00, 0x00, 0x00, 0x00, // (uint)0 + 0x01, 0x00, 0x00, 0x00, // (uint)1 + 0x02, 0x00, 0x00, 0x00, // (uint)2 + 0x03, 0x00, 0x00, 0x00, // (uint)3 + 0x04, 0x00, 0x00, 0x00, // (uint)4 + 0x05, 0x00, 0x00, 0x00, // (uint)5 + 0x06, 0x00, 0x00, 0x00, // (uint)6 + 0x07, 0x00, 0x00, 0x00, // (uint)7 + 0x08, 0x00, 0x00, 0x00, // (uint)8 + 0x09, 0x00, 0x00, 0x00, // (uint)9 + 0x0A, 0x00, 0x00, 0x00, // (uint)10 + 0x0B, 0x00, 0x00, 0x00, // (uint)11 + 0x0C, 0x00, 0x00, 0x00, // (uint)12 + 0x0D, 0x00, 0x00, 0x00, // (uint)13 + 0x0E, 0x00, 0x00, 0x00, // (uint)14 + 0x0F, 0x00, 0x00, 0x00, // (uint)15 + + 0x00, 0x00, 0x00, 0x00, // (bool32)false + + 0x45, 0x6E, 0x64, 0x69, 0x61, 0x6E, 0x42, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x49, 0x4F, 0x00, // "EndianBinaryIO\0" + + 0x4B, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6D, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, // "Kermalis\0\0" }; [Fact] diff --git a/Testing/LengthsTests.cs b/Testing/LengthsTests.cs index 1710da6..3e38c46 100644 --- a/Testing/LengthsTests.cs +++ b/Testing/LengthsTests.cs @@ -1,4 +1,5 @@ using Kermalis.EndianBinaryIO; +using System; using System.IO; using System.Linq; using Xunit; @@ -23,27 +24,40 @@ private sealed class MyLengthyObj [BinaryArrayVariableLength(nameof(VariableLengthProperty))] public ShortSizedEnum[] VariableSizedArray { get; set; } } + private sealed class ZeroLenArrayObj + { + [BinaryArrayFixedLength(0)] + public byte[] SizedArray { get; set; } + + public byte VariableLength { get; set; } + [BinaryArrayVariableLength(nameof(VariableLength))] + public byte[] VariableArray { get; set; } + } - private static readonly byte[] _bytes = new byte[34] + private static readonly byte[] _lengthyObjBytes = new byte[] { - 0x48, 0x69, 0x00, - 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00, - 0x48, 0x6F, 0x6C, 0x61, 0x00, + 0x48, 0x69, 0x00, // "Hi\0" + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00, // "Hello\0" + 0x48, 0x6F, 0x6C, 0x61, 0x00, // "Hola\0" - 0x53, 0x65, 0x65, 0x79, 0x61, - 0x42, 0x79, 0x65, 0x00, 0x00, - 0x41, 0x64, 0x69, 0x6F, 0x73, + 0x53, 0x65, 0x65, 0x79, 0x61, // "Seeya" + 0x42, 0x79, 0x65, 0x00, 0x00, // "Bye\0\0" + 0x41, 0x64, 0x69, 0x6F, 0x73, // "Adios" - 0x02, - 0x40, 0x00, - 0x00, 0x08 + 0x02, // (byte)2 + 0x40, 0x00, // ShortSizedEnum.Val1 + 0x00, 0x08, // ShortSizedEnum.Val2 + }; + private static readonly byte[] _zeroLenArrayObjBytes = new byte[] + { + 0x00, // (byte)0 }; [Fact] - public void ReadObject() + public void ReadLengthyObject() { MyLengthyObj obj; - using (var stream = new MemoryStream(_bytes)) + using (var stream = new MemoryStream(_lengthyObjBytes)) using (var reader = new EndianBinaryReader(stream, Endianness.LittleEndian)) { obj = reader.ReadObject(); @@ -66,9 +80,9 @@ public void ReadObject() } [Fact] - public void WriteObject() + public void WriteLengthyObject() { - byte[] bytes = new byte[_bytes.Length]; + byte[] bytes = new byte[_lengthyObjBytes.Length]; using (var stream = new MemoryStream(bytes)) using (var writer = new EndianBinaryWriter(stream, Endianness.LittleEndian)) { @@ -91,7 +105,41 @@ public void WriteObject() } }); } - Assert.True(bytes.SequenceEqual(_bytes)); + Assert.True(bytes.SequenceEqual(_lengthyObjBytes)); + } + + [Fact] + public void ReadZeroLenArrayObject() + { + ZeroLenArrayObj obj; + using (var stream = new MemoryStream(_zeroLenArrayObjBytes)) + using (var reader = new EndianBinaryReader(stream, Endianness.LittleEndian)) + { + obj = reader.ReadObject(); + } + + Assert.True(obj.SizedArray.Length == 0); // Fixed size array works + + Assert.True(obj.VariableLength == 0); // This determines how long the following array is + Assert.True(obj.VariableArray.Length == 0); // Retrieves the proper size + } + + [Fact] + public void WriteZeroLenArrayObject() + { + byte[] bytes = new byte[_zeroLenArrayObjBytes.Length]; + using (var stream = new MemoryStream(bytes)) + using (var writer = new EndianBinaryWriter(stream, Endianness.LittleEndian)) + { + writer.Write(new ZeroLenArrayObj + { + SizedArray = Array.Empty(), + + VariableLength = 0, + VariableArray = Array.Empty() + }); + } + Assert.True(bytes.SequenceEqual(_zeroLenArrayObjBytes)); } } }