From 291c47082c95ec61ef8987cb0c1ade91c1e7b836 Mon Sep 17 00:00:00 2001 From: Steve Molloy Date: Fri, 9 Sep 2022 12:13:03 -0700 Subject: [PATCH] Manually backporting #74599 to 7.0 for RC2. --- .../src/System/Xml/XmlBinaryReader.cs | 12 +- .../src/System/Xml/XmlBinaryWriter.cs | 274 ++++++++++++------ .../src/System/Xml/XmlBufferReader.cs | 8 +- .../tests/XmlDictionaryReaderTests.cs | 29 +- .../tests/XmlDictionaryWriterTest.cs | 174 +++++++++++ 5 files changed, 394 insertions(+), 103 deletions(-) diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryReader.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryReader.cs index aa3046d42f8c7..3d909709a8fa5 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryReader.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryReader.cs @@ -1350,14 +1350,14 @@ private unsafe int ReadArray(float[] array, int offset, int count) public override int ReadArray(string localName, string namespaceUri, float[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } public override int ReadArray(XmlDictionaryString localName, XmlDictionaryString namespaceUri, float[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } @@ -1373,14 +1373,14 @@ private unsafe int ReadArray(double[] array, int offset, int count) public override int ReadArray(string localName, string namespaceUri, double[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } public override int ReadArray(XmlDictionaryString localName, XmlDictionaryString namespaceUri, double[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } @@ -1396,14 +1396,14 @@ private unsafe int ReadArray(decimal[] array, int offset, int count) public override int ReadArray(string localName, string namespaceUri, decimal[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } public override int ReadArray(XmlDictionaryString localName, XmlDictionaryString namespaceUri, decimal[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs index be47e110a151b..0277530923ef2 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs @@ -12,6 +12,7 @@ using System.Runtime.Serialization; using System.Globalization; using System.Collections.Generic; +using System.Buffers.Binary; namespace System.Xml { @@ -757,12 +758,8 @@ public override unsafe void WriteFloatText(float f) { int offset; byte[] buffer = GetTextNodeBuffer(1 + sizeof(float), out offset); - byte* bytes = (byte*)&f; buffer[offset + 0] = (byte)XmlBinaryNodeType.FloatText; - buffer[offset + 1] = bytes[0]; - buffer[offset + 2] = bytes[1]; - buffer[offset + 3] = bytes[2]; - buffer[offset + 4] = bytes[3]; + BinaryPrimitives.WriteSingleLittleEndian(buffer.AsSpan(offset + 1, sizeof(float)), f); Advance(1 + sizeof(float)); } } @@ -778,16 +775,8 @@ public override unsafe void WriteDoubleText(double d) { int offset; byte[] buffer = GetTextNodeBuffer(1 + sizeof(double), out offset); - byte* bytes = (byte*)&d; buffer[offset + 0] = (byte)XmlBinaryNodeType.DoubleText; - buffer[offset + 1] = bytes[0]; - buffer[offset + 2] = bytes[1]; - buffer[offset + 3] = bytes[2]; - buffer[offset + 4] = bytes[3]; - buffer[offset + 5] = bytes[4]; - buffer[offset + 6] = bytes[5]; - buffer[offset + 7] = bytes[6]; - buffer[offset + 8] = bytes[7]; + BinaryPrimitives.WriteDoubleLittleEndian(buffer.AsSpan(offset + 1, sizeof(double)), d); Advance(1 + sizeof(double)); } } @@ -798,9 +787,24 @@ public override unsafe void WriteDecimalText(decimal d) byte[] buffer = GetTextNodeBuffer(1 + sizeof(decimal), out offset); byte* bytes = (byte*)&d; buffer[offset++] = (byte)XmlBinaryNodeType.DecimalText; - for (int i = 0; i < sizeof(decimal); i++) + if (BitConverter.IsLittleEndian) { - buffer[offset++] = bytes[i]; + for (int i = 0; i < sizeof(decimal); i++) + { + buffer[offset++] = bytes[i]; + } + } + else + { + Span bits = stackalloc int[4]; + decimal.TryGetBits(d, bits, out int intsWritten); + Debug.Assert(intsWritten == 4); + + Span span = buffer.AsSpan(offset, sizeof(decimal)); + BinaryPrimitives.WriteInt32LittleEndian(span, bits[3]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(4), bits[2]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(8), bits[0]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(12), bits[1]); } Advance(1 + sizeof(decimal)); } @@ -870,17 +874,147 @@ private void WriteArrayInfo(XmlBinaryNodeType nodeType, int count) WriteMultiByteInt32(count); } - public unsafe void UnsafeWriteArray(XmlBinaryNodeType nodeType, int count, byte* array, byte* arrayMax) + public unsafe void UnsafeWriteBoolArray(bool[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.BoolTextWithEndElement, count); + fixed (bool* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, count); + } + } + + public unsafe void UnsafeWriteInt16Array(short[] array, int offset, int count) { - WriteArrayInfo(nodeType, count); - UnsafeWriteArray(array, (int)(arrayMax - array)); + WriteArrayInfo(XmlBinaryNodeType.Int16TextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (short* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(short) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(short), out int bufferOffset).AsSpan(bufferOffset, sizeof(short)); + BinaryPrimitives.WriteInt16LittleEndian(span, array[offset + i]); + Advance(sizeof(short)); + } + } } - private unsafe void UnsafeWriteArray(byte* array, int byteCount) + public unsafe void UnsafeWriteInt32Array(int[] array, int offset, int count) { - base.UnsafeWriteBytes(array, byteCount); + WriteArrayInfo(XmlBinaryNodeType.Int32TextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (int* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(int) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(int), out int bufferOffset).AsSpan(bufferOffset, sizeof(int)); + BinaryPrimitives.WriteInt32LittleEndian(span, array[offset + i]); + Advance(sizeof(int)); + } + } } + public unsafe void UnsafeWriteInt64Array(long[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.Int64TextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (long* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(long) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(long), out int bufferOffset).AsSpan(bufferOffset, sizeof(long)); + BinaryPrimitives.WriteInt64LittleEndian(span, array[offset + i]); + Advance(sizeof(long)); + } + } + } + + public unsafe void UnsafeWriteFloatArray(float[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.FloatTextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (float* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(float) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(float), out int bufferOffset).AsSpan(bufferOffset, sizeof(float)); + BinaryPrimitives.WriteSingleLittleEndian(span, array[offset + i]); + Advance(sizeof(float)); + } + } + } + + public unsafe void UnsafeWriteDoubleArray(double[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.DoubleTextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (double* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(double) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(double), out int bufferOffset).AsSpan(bufferOffset, sizeof(double)); + BinaryPrimitives.WriteDoubleLittleEndian(span, array[offset + i]); + Advance(sizeof(double)); + } + } + } + + public unsafe void UnsafeWriteDecimalArray(decimal[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.DecimalTextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (decimal* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(decimal) * count); + } + } + else + { + Span bits = stackalloc int[4]; + for (int i = 0; i < count; i++) + { + decimal.TryGetBits(array[offset + i], bits, out int intsWritten); + Debug.Assert(intsWritten == 4); + + Span span = GetBuffer(16, out int bufferOffset).AsSpan(bufferOffset, 16); + BinaryPrimitives.WriteInt32LittleEndian(span, bits[3]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(4), bits[2]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(8), bits[0]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(12), bits[1]); + Advance(16); + } + } + } public void WriteDateTimeArray(DateTime[] array, int offset, int count) { WriteArrayInfo(XmlBinaryNodeType.DateTimeTextWithEndElement, count); @@ -1160,20 +1294,6 @@ private void WriteStartArray(string? prefix, XmlDictionaryString localName, XmlD WriteEndElement(); } - private unsafe void UnsafeWriteArray(string? prefix, string localName, string? namespaceUri, - XmlBinaryNodeType nodeType, int count, byte* array, byte* arrayMax) - { - WriteStartArray(prefix, localName, namespaceUri, count); - _writer.UnsafeWriteArray(nodeType, count, array, arrayMax); - } - - private unsafe void UnsafeWriteArray(string? prefix, XmlDictionaryString localName, XmlDictionaryString? namespaceUri, - XmlBinaryNodeType nodeType, int count, byte* array, byte* arrayMax) - { - WriteStartArray(prefix, localName, namespaceUri, count); - _writer.UnsafeWriteArray(nodeType, count, array, arrayMax); - } - private static void CheckArray(Array array, int offset, int count) { ArgumentNullException.ThrowIfNull(array); @@ -1199,10 +1319,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (bool* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.BoolTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteBoolArray(array, offset, count); } } } @@ -1218,10 +1336,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (bool* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.BoolTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteBoolArray(array, offset, count); } } } @@ -1237,10 +1353,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (short* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int16TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt16Array(array, offset, count); } } } @@ -1256,10 +1370,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (short* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int16TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt16Array(array, offset, count); } } } @@ -1275,10 +1387,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (int* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int32TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt32Array(array, offset, count); } } } @@ -1294,10 +1404,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (int* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int32TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt32Array(array, offset, count); } } } @@ -1313,10 +1421,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (long* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int64TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt64Array(array, offset, count); } } } @@ -1332,10 +1438,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (long* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int64TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt64Array(array, offset, count); } } } @@ -1351,10 +1455,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (float* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteFloatArray(array, offset, count); } } } @@ -1370,10 +1472,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (float* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteFloatArray(array, offset, count); } } } @@ -1389,10 +1489,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (double* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteDoubleArray(array, offset, count); } } } @@ -1408,10 +1506,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (double* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteDoubleArray(array, offset, count); } } } @@ -1427,10 +1523,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (decimal* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteDecimalArray(array, offset, count); } } } @@ -1446,10 +1540,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (decimal* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteDecimalArray(array, offset, count); } } } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs index f193d1815cd59..15b3f23b57708 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs @@ -373,10 +373,10 @@ public long ReadInt64() => BitConverter.IsLittleEndian ? ReadRawBytes() : BinaryPrimitives.ReverseEndianness(ReadRawBytes()); public float ReadSingle() - => ReadRawBytes(); + => BinaryPrimitives.ReadSingleLittleEndian(GetBuffer(sizeof(float), out int offset).AsSpan(offset, sizeof(float))); public double ReadDouble() - => ReadRawBytes(); + => BinaryPrimitives.ReadSingleLittleEndian(GetBuffer(sizeof(double), out int offset).AsSpan(offset, sizeof(double))); public decimal ReadDecimal() { @@ -964,10 +964,10 @@ public ulong GetUInt64(int offset) => (ulong)GetInt64(offset); public float GetSingle(int offset) - => ReadRawBytes(offset); + => BinaryPrimitives.ReadSingleLittleEndian(_buffer.AsSpan(offset, sizeof(float))); public double GetDouble(int offset) - => ReadRawBytes(offset); + => BinaryPrimitives.ReadSingleLittleEndian(_buffer.AsSpan(offset, sizeof(double))); public decimal GetDecimal(int offset) { diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryReaderTests.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryReaderTests.cs index e15f9fc2df544..6880fc48f0227 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryReaderTests.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryReaderTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Linq; @@ -193,8 +194,10 @@ public static void BinaryXml_ReadPrimitiveTypes() AssertReadContentFromBinary(8.20788039913184E-304, XmlBinaryNodeType.DoubleText, new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 }); AssertReadContentFromBinary(guid, XmlBinaryNodeType.GuidText, guid.ToByteArray()); AssertReadContentFromBinary(new TimeSpan(0x0807060504030201), XmlBinaryNodeType.TimeSpanText, new byte[] { 01, 02, 03, 04, 05, 06, 07, 08 }); - AssertReadContentFromBinary(new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b), XmlBinaryNodeType.DecimalText - , new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10 }); + AssertReadContentFromBinary(new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b), XmlBinaryNodeType.DecimalText, + new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10 }); + AssertReadContentFromBinary(new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc), XmlBinaryNodeType.DateTimeText, + new byte[] { 0x00, 0x18, 0xdf, 0x61, 0x5f, 0x87, 0xda, 0x48 }); // Double can be represented as float or inte as long as no detail is lost AssertReadContentFromBinary((double)0x0100, XmlBinaryNodeType.Int16Text, new byte[] { 0x00, 0x01 }); @@ -205,6 +208,16 @@ public static void BinaryXml_ReadPrimitiveTypes() public static void BinaryXml_Array_RoundTrip() { int[] ints = new int[] { -1, 0x01020304, 0x11223344, -1 }; + float[] floats = new float[] { 1.2345f, 2.3456f }; + double[] doubles = new double[] { 1.2345678901, 2.3456789012 }; + decimal[] decimals = new[] { + new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b), + new decimal(0x50515253, 0x40414243, 0x31323334, false, scale: 0x1c) + }; + DateTime[] datetimes = new[] { + new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc), + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local) + }; TimeSpan[] timespans = new[] { TimeSpan.FromTicks(0x0102030405060708), TimeSpan.FromTicks(0x1011121314151617) }; // Write more than 4 kb in a single call to ensure we hit path for reading (and writing happens on 512b) large arrays long[] longs = Enumerable.Range(0x01020304, 513).Select(i => (long)i | (long)(~i << 32)).ToArray(); @@ -217,6 +230,10 @@ public static void BinaryXml_Array_RoundTrip() using var writer = XmlDictionaryWriter.CreateBinaryWriter(ms); writer.WriteStartElement("root"); writer.WriteArray(null, "ints", null, ints, 1, 2); + writer.WriteArray(null, "floats", null, floats, 0, floats.Length); + writer.WriteArray(null, "doubles", null, doubles, 0, doubles.Length); + writer.WriteArray(null, "decimals", null, decimals, 0, decimals.Length); + writer.WriteArray(null, "datetimes", null, datetimes, 0, datetimes.Length); writer.WriteArray(null, "timespans", null, timespans, 0, timespans.Length); writer.WriteArray(null, "longs", null, longs, 0, longs.Length); writer.WriteArray(null, "guids", null, guids, 0, guids.Length); @@ -230,6 +247,10 @@ public static void BinaryXml_Array_RoundTrip() using var reader = XmlDictionaryReader.CreateBinaryReader(ms, XmlDictionaryReaderQuotas.Max); reader.ReadStartElement("root"); int intsRead = reader.ReadArray("ints", string.Empty, actualInts, 1, 3); + float[] actualFloats = reader.ReadSingleArray("floats", string.Empty); + double[] actualDoubles = reader.ReadDoubleArray("doubles", string.Empty); + decimal[] actualDecimals = reader.ReadDecimalArray("decimals", string.Empty); + DateTime[] actualDateTimes = reader.ReadDateTimeArray("datetimes", string.Empty); TimeSpan[] actualTimeSpans = reader.ReadTimeSpanArray("timespans", string.Empty); long[] actualLongs = reader.ReadInt64Array("longs", string.Empty); Guid[] actualGuids = reader.ReadGuidArray("guids", string.Empty); @@ -240,6 +261,10 @@ public static void BinaryXml_Array_RoundTrip() Assert.Equal(2, intsRead); AssertExtensions.SequenceEqual(ints, actualInts); AssertExtensions.SequenceEqual(actualLongs, longs); + AssertExtensions.SequenceEqual(actualFloats, floats); + AssertExtensions.SequenceEqual(actualDoubles, doubles); + AssertExtensions.SequenceEqual(actualDecimals, decimals); + AssertExtensions.SequenceEqual(actualDateTimes, datetimes); AssertExtensions.SequenceEqual(actualTimeSpans, timespans); AssertExtensions.SequenceEqual(actualGuids, guids); } diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs index 719a06d6e48cf..3ad4d32400e37 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Linq; @@ -320,6 +321,179 @@ public static void FragmentTest() Assert.False(FragmentHelper.CanFragment(writer)); } + [Fact] + public static void BinaryWriter_PrimitiveTypes() + { + using MemoryStream ms = new(); + using XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(ms); + writer.WriteStartElement("root"); + + AssertBytesWritten(x => x.WriteValue((byte)0x78), XmlBinaryNodeType.Int8Text, new byte[] { 0x78 }); + AssertBytesWritten(x => x.WriteValue((short)0x1234), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0x12 }); + AssertBytesWritten(x => x.WriteValue(unchecked((short)0xf234)), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0xf2 }); + AssertBytesWritten(x => x.WriteValue((int)0x12345678), XmlBinaryNodeType.Int32Text, new byte[] { 0x78, 0x56, 0x34, 0x12 }); + AssertBytesWritten(x => x.WriteValue((long)0x0102030412345678), XmlBinaryNodeType.Int64Text, new byte[] { 0x78, 0x56, 0x34, 0x12, 04, 03, 02, 01 }); + + // Integer values should be represented using smalles possible type + AssertBytesWritten(x => x.WriteValue((long)0), XmlBinaryNodeType.ZeroText, Span.Empty); + AssertBytesWritten(x => x.WriteValue((long)1), XmlBinaryNodeType.OneText, Span.Empty); + AssertBytesWritten(x => x.WriteValue((int)0x00000078), XmlBinaryNodeType.Int8Text, new byte[] { 0x78 }); + AssertBytesWritten(x => x.WriteValue(unchecked((int)0xfffffff0)), XmlBinaryNodeType.Int8Text, new byte[] { 0xf0 }); + AssertBytesWritten(x => x.WriteValue((int)0x00001234), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0x12 }); + AssertBytesWritten(x => x.WriteValue(unchecked((int)0xfffff234)), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0xf2 }); + AssertBytesWritten(x => x.WriteValue((long)0x12345678), XmlBinaryNodeType.Int32Text, new byte[] { 0x78, 0x56, 0x34, 0x12 }); + AssertBytesWritten(x => x.WriteValue(unchecked((long)0xfffffffff2345678)), XmlBinaryNodeType.Int32Text, new byte[] { 0x78, 0x56, 0x34, 0xf2 }); + + float f = 1.23456788f; + ReadOnlySpan floatBytes = new byte[] { 0x52, 0x06, 0x9e, 0x3f }; + double d = 1.0 / 3.0; + ReadOnlySpan doubleBytes = new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f }; + Guid guid = new Guid(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }); + DateTime datetime = new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc); + Span datetimeBytes = stackalloc byte[8]; + BinaryPrimitives.WriteInt64LittleEndian(datetimeBytes, datetime.ToBinary()); + + AssertBytesWritten(x => x.WriteValue(f), XmlBinaryNodeType.FloatText, floatBytes); + AssertBytesWritten(x => x.WriteValue(new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b)), XmlBinaryNodeType.DecimalText, + new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10 }); + AssertBytesWritten(x => x.WriteValue(guid), XmlBinaryNodeType.GuidText, guid.ToByteArray()); + AssertBytesWritten(x => x.WriteValue(new TimeSpan(0x0807060504030201)), XmlBinaryNodeType.TimeSpanText, new byte[] { 01, 02, 03, 04, 05, 06, 07, 08 }); + AssertBytesWritten(x => x.WriteValue(datetime), XmlBinaryNodeType.DateTimeText, datetimeBytes); + + // Double can be represented as float or int as long as no detail is lost + AssertBytesWritten(x => x.WriteValue((double)f), XmlBinaryNodeType.FloatText, floatBytes); + AssertBytesWritten(x => x.WriteValue((double)0x0100), XmlBinaryNodeType.Int16Text, new byte[] { 0x00, 0x01 }); + AssertBytesWritten(x => x.WriteValue(d), XmlBinaryNodeType.DoubleText, doubleBytes); + + + void AssertBytesWritten(Action action, XmlBinaryNodeType nodeType, ReadOnlySpan expected) + { + writer.WriteStartElement("a"); + + // Reset stream so we only compare the actual value written (including end element) + writer.Flush(); + ms.Position = 0; + ms.SetLength(0); + + action(writer); + + writer.Flush(); + ms.TryGetBuffer(out ArraySegment segement); + Assert.Equal(nodeType, (XmlBinaryNodeType)segement[0]); + AssertExtensions.SequenceEqual(expected, segement.AsSpan(1)); + writer.WriteEndElement(); + } + } + + + [Fact] + public static void BinaryWriter_Arrays() + { + using var ms = new MemoryStream(); + using var writer = XmlDictionaryWriter.CreateBinaryWriter(ms); + writer.WriteStartElement("root"); + int offset = 1; + int count = 2; + + bool[] bools = new bool[] { false, true, false, true }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, bools, offset, count), XmlBinaryNodeType.BoolTextWithEndElement, + count, new byte[] { 1, 0 }); + + short[] shorts = new short[] { -1, 0x0102, 0x1122, -1 }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, shorts, offset, count), XmlBinaryNodeType.Int16TextWithEndElement, + count, new byte[] { 2, 1, 0x22, 0x11 }); + + int[] ints = new int[] { -1, 0x01020304, 0x11223344, -1 }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, ints, offset, count), XmlBinaryNodeType.Int32TextWithEndElement, + count, new byte[] { 4, 3, 2, 1, 0x44, 0x33, 0x22, 0x11 }); + + long[] longs = new long[] { -1, 0x0102030405060708, 0x1122334455667788, -1 }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, longs, offset, count), XmlBinaryNodeType.Int64TextWithEndElement, + count, new byte[] { 8, 7, 6, 5, 4, 3, 2, 1, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 }); + + float[] floats = new float[] { -1.0f, 1.23456788f, 1.23456788f, -1.0f }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, floats, offset, count), XmlBinaryNodeType.FloatTextWithEndElement, + count, new byte[] { 0x52, 0x06, 0x9e, 0x3f, 0x52, 0x06, 0x9e, 0x3f }); + + double[] doubles = new double[] { -1.0, 1.0 / 3.0, 1.0 / 3.0, -1.0 }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, doubles, offset, count), XmlBinaryNodeType.DoubleTextWithEndElement, + count, new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f }); + + decimal[] decimals = new[] { + new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b), + new decimal(0x50515253, 0x40414243, 0x31323334, false, scale: 0x1c) + }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, decimals, 0, decimals.Length), XmlBinaryNodeType.DecimalTextWithEndElement, + decimals.Length, new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, + 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10, + 0x0, 0x0, 0x1c, 0x00, 0x34, 0x33, 0x32, 0x31, + 0x53, 0x52, 0x51, 0x50, 0x43, 0x42, 0x41, 0x40 }); + + DateTime[] datetimes = new[] { + new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc), + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local) + }; + Span datetimeBytes = stackalloc byte[8 * datetimes.Length]; + for (int i = 0; i < datetimes.Length; i++) + { + BinaryPrimitives.WriteInt64LittleEndian(datetimeBytes.Slice(8 * i), datetimes[i].ToBinary()); + } + AssertBytesWritten(x => x.WriteArray(null, "a", null, datetimes, 0, datetimes.Length), XmlBinaryNodeType.DateTimeTextWithEndElement, + datetimes.Length, datetimeBytes); + + TimeSpan[] timespans = new[] { new TimeSpan(0x0807060504030201), new TimeSpan(0x1817161514131211) }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, timespans, 0, timespans.Length), XmlBinaryNodeType.TimeSpanTextWithEndElement, + timespans.Length, new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 }); + + Guid[] guids = new Guid[] + { + new Guid(new ReadOnlySpan(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 })), + new Guid(new ReadOnlySpan(new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160 })) + }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, guids, 0, guids.Length), XmlBinaryNodeType.GuidTextWithEndElement, + guids.Length, new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160 }); + + // Write more than 512 bytes in a single call to trigger different writing logic in XmlStreamNodeWriter.WriteBytes + long[] many_longs = Enumerable.Range(0x01020304, 127).Select(i => (long)i | (long)(~i << 32)).ToArray(); + Span many_longBytes = stackalloc byte[8 * many_longs.Length]; + for (int i = 0; i < many_longs.Length; i++) + { + BinaryPrimitives.WriteInt64LittleEndian(many_longBytes.Slice(8 * i), many_longs[i]); + } + AssertBytesWritten(x => x.WriteArray(null, "a", null, many_longs, 0, many_longs.Length), XmlBinaryNodeType.Int64TextWithEndElement, + many_longs.Length, many_longBytes); + + void AssertBytesWritten(Action action, XmlBinaryNodeType nodeType, int count, ReadOnlySpan expected) + { + // Reset stream so we only compare the actual value written (including end element) + writer.Flush(); + ms.Position = 0; + ms.SetLength(0); + + action(writer); + + writer.Flush(); + ms.TryGetBuffer(out ArraySegment segement); + + var actual = segement.AsSpan(); + Assert.Equal(XmlBinaryNodeType.Array, (XmlBinaryNodeType)actual[0]); + Assert.Equal(XmlBinaryNodeType.ShortElement, (XmlBinaryNodeType)actual[1]); + int elementLength = actual[2]; + Assert.InRange(elementLength, 0, 0x8f); // verify count is single byte + Assert.Equal(XmlBinaryNodeType.EndElement, (XmlBinaryNodeType)actual[3 + elementLength]); + + actual = actual.Slice(4 + elementLength); + // nodetype and count + Assert.Equal(nodeType, (XmlBinaryNodeType)actual[0]); + Assert.Equal(checked((sbyte)count), (sbyte)actual[1]); + + AssertExtensions.SequenceEqual(expected, actual.Slice(2)); + } + } + private static bool ReadTest(MemoryStream ms, Encoding encoding, ReaderWriterFactory.ReaderWriterType rwType, byte[] byteArray) { ms.Position = 0;