diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 21454d70ec..8e7db8df77 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -33,6 +33,9 @@ Microsoft\Data\Common\AdapterUtil.cs + + Microsoft\Data\Common\BitConverterCompatible.cs + Microsoft\Data\Common\DbConnectionOptions.Common.cs @@ -1008,4 +1011,4 @@ - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs index b93ba0749c..964d332ae4 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers.Binary; using System.Diagnostics; using System.Net; using System.Net.Security; @@ -59,10 +60,11 @@ public void Read(byte[] bytes) { SMID = bytes[0]; flags = bytes[1]; - sessionId = BitConverter.ToUInt16(bytes, 2); - length = BitConverter.ToUInt32(bytes, 4) - SNISMUXHeader.HEADER_LENGTH; - sequenceNumber = BitConverter.ToUInt32(bytes, 8); - highwater = BitConverter.ToUInt32(bytes, 12); + Span span = bytes.AsSpan(); + sessionId = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(2)); + length = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(4)) - SNISMUXHeader.HEADER_LENGTH; + sequenceNumber = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(8)); + highwater = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(12)); } public void Write(Span bytes) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.NetCoreApp.cs index d8026b36e3..95dd0d9731 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.NetCoreApp.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; +using System.Buffers.Binary; namespace Microsoft.Data.SqlClient { @@ -11,9 +12,9 @@ internal sealed partial class TdsParser { internal static void FillGuidBytes(Guid guid, Span buffer) => guid.TryWriteBytes(buffer); - internal static void FillDoubleBytes(double value, Span buffer) => BitConverter.TryWriteBytes(buffer, value); + internal static void FillDoubleBytes(double value, Span buffer) => BinaryPrimitives.TryWriteInt64LittleEndian(buffer, BitConverter.DoubleToInt64Bits(value)); - internal static void FillFloatBytes(float v, Span buffer) => BitConverter.TryWriteBytes(buffer, v); + internal static void FillFloatBytes(float value, Span buffer) => BinaryPrimitives.TryWriteInt32LittleEndian(buffer, BitConverterCompatible.SingleToInt32Bits(value)); internal static Guid ConstructGuid(ReadOnlySpan bytes) { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index d87c9c402f..034f891097 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; +using System.Buffers.Binary; using System.Collections.Generic; using System.Data; using System.Data.SqlTypes; @@ -1760,7 +1761,7 @@ internal void WriteInt(int v, TdsParserStateObject stateObj) internal static void WriteInt(Span buffer, int value) { #if NETCOREAPP - BitConverter.TryWriteBytes(buffer, value); + BinaryPrimitives.TryWriteInt32LittleEndian(buffer, value); #else buffer[0] = (byte)(value & 0xff); buffer[1] = (byte)((value >> 8) & 0xff); @@ -1779,7 +1780,9 @@ internal byte[] SerializeFloat(float v) throw ADP.ParameterValueOutOfRange(v.ToString()); } - return BitConverter.GetBytes(v); + var bytes = new byte[4]; + BinaryPrimitives.WriteInt32LittleEndian(bytes, BitConverterCompatible.SingleToInt32Bits(v)); + return bytes; } internal void WriteFloat(float v, TdsParserStateObject stateObj) @@ -1902,7 +1905,9 @@ internal byte[] SerializeDouble(double v) throw ADP.ParameterValueOutOfRange(v.ToString()); } - return BitConverter.GetBytes(v); + var bytes = new byte[8]; + BinaryPrimitives.WriteInt64LittleEndian(bytes, BitConverter.DoubleToInt64Bits(v)); + return bytes; } internal void WriteDouble(double v, TdsParserStateObject stateObj) @@ -3829,8 +3834,8 @@ private bool TryProcessFedAuthInfo(TdsParserStateObject stateObj, int tokenLen, uint currentOptionOffset = checked(i * optionSize); byte id = tokenData[currentOptionOffset]; - uint dataLen = BitConverter.ToUInt32(tokenData, checked((int)(currentOptionOffset + 1))); - uint dataOffset = BitConverter.ToUInt32(tokenData, checked((int)(currentOptionOffset + 5))); + uint dataLen = BinaryPrimitives.ReadUInt32LittleEndian(tokenData.AsSpan(checked((int)(currentOptionOffset + 1)))); + uint dataOffset = BinaryPrimitives.ReadUInt32LittleEndian(tokenData.AsSpan(checked((int)(currentOptionOffset + 5)))); if (SqlClientEventSource.Log.IsAdvancedTraceOn()) { SqlClientEventSource.Log.AdvancedTraceEvent(" FedAuthInfoOpt: ID={0}, DataLen={1}, Offset={2}", id, dataLen.ToString(CultureInfo.InvariantCulture), dataOffset.ToString(CultureInfo.InvariantCulture)); @@ -5799,7 +5804,7 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt return false; } - longValue = BitConverter.ToInt64(unencryptedBytes, 0); + longValue = BinaryPrimitives.ReadInt64LittleEndian(unencryptedBytes); if (tdsType == TdsEnums.SQLBIT || tdsType == TdsEnums.SQLBITN) @@ -5837,7 +5842,7 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt return false; } - singleValue = BitConverter.ToSingle(unencryptedBytes, 0); + singleValue = BitConverterCompatible.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(unencryptedBytes)); value.Single = singleValue; break; @@ -5848,7 +5853,7 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt return false; } - doubleValue = BitConverter.ToDouble(unencryptedBytes, 0); + doubleValue = BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64LittleEndian(unencryptedBytes)); value.Double = doubleValue; break; @@ -5865,8 +5870,8 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt return false; } - mid = BitConverter.ToInt32(unencryptedBytes, 0); - lo = BitConverter.ToUInt32(unencryptedBytes, 4); + mid = BinaryPrimitives.ReadInt32LittleEndian(unencryptedBytes); + lo = BinaryPrimitives.ReadUInt32LittleEndian(unencryptedBytes.AsSpan(4)); long l = (((long)mid) << 0x20) + ((long)lo); value.SetToMoney(l); @@ -5903,8 +5908,8 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt return false; } - daypart = BitConverter.ToInt32(unencryptedBytes, 0); - timepart = BitConverter.ToUInt32(unencryptedBytes, 4); + daypart = BinaryPrimitives.ReadInt32LittleEndian(unencryptedBytes); + timepart = BinaryPrimitives.ReadUInt32LittleEndian(unencryptedBytes.AsSpan(4)); value.SetToDateTime(daypart, (int)timepart); break; @@ -5947,10 +5952,11 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt length = checked((int)length - 1); int[] bits = new int[4]; int decLength = length >> 2; + var span = unencryptedBytes.AsSpan(); for (int i = 0; i < decLength; i++) { // up to 16 bytes of data following the sign byte - bits[i] = BitConverter.ToInt32(unencryptedBytes, index); + bits[i] = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(index)); index += 4; } value.SetToDecimal(md.baseTI.precision, md.baseTI.scale, fPositive, bits); @@ -7518,7 +7524,20 @@ internal Task WriteString(string s, int length, int offset, TdsParserStateObject private static void CopyCharsToBytes(char[] source, int sourceOffset, byte[] dest, int destOffset, int charLength) { - Buffer.BlockCopy(source, sourceOffset, dest, destOffset, charLength * ADP.CharSize); + if (!BitConverter.IsLittleEndian) + { + int desti = 0; + Span span = dest.AsSpan(); + for (int srci = 0; srci < charLength; srci++) + { + BinaryPrimitives.WriteUInt16LittleEndian(span.Slice(desti + destOffset), (ushort)source[srci + sourceOffset]); + desti += 2; + } + } + else + { + Buffer.BlockCopy(source, sourceOffset, dest, destOffset, charLength * ADP.CharSize); + } } private static void CopyStringToBytes(string source, int sourceOffset, byte[] dest, int destOffset, int charLength) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 7e35e5a122..6c32e9594d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; +using System.Buffers.Binary; using System.Runtime.InteropServices; using System.Security; using System.Threading; @@ -1055,6 +1056,15 @@ private void SetBufferSecureStrings() str = Marshal.SecureStringToBSTR(_securePasswords[i]); byte[] data = new byte[_securePasswords[i].Length * 2]; Marshal.Copy(str, data, 0, _securePasswords[i].Length * 2); + if (!BitConverter.IsLittleEndian) + { + Span span = data.AsSpan(); + for (int ii = 0; ii < _securePasswords[i].Length * 2; ii += 2) + { + short value = BinaryPrimitives.ReadInt16LittleEndian(span.Slice(ii)); + BinaryPrimitives.WriteInt16BigEndian(span.Slice(ii), value); + } + } TdsParserStaticMethods.ObfuscatePassword(data); data.CopyTo(_outBuff, _securePasswordOffsetsInBuffer[i]); } @@ -1556,7 +1566,8 @@ internal Task WritePacket(byte flushMode, bool canAccumulate = false) // So we need to avoid this check prior to login completing state == TdsParserState.OpenLoggedIn && !_bulkCopyOpperationInProgress // ignore the condition checking for bulk copy - && _outBytesUsed == (_outputHeaderLen + BitConverter.ToInt32(_outBuff, _outputHeaderLen)) + && _outBytesUsed == (_outputHeaderLen + + BinaryPrimitives.ReadInt32LittleEndian(_outBuff.AsSpan(_outputHeaderLen))) && _outputPacketCount == 0 || _outBytesUsed == _outputHeaderLen && _outputPacketCount == 0) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index c81604f3c3..f17622f303 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -95,6 +95,9 @@ Microsoft\Data\Common\AdapterUtil.Windows.cs + + Microsoft\Data\Common\BitConverterCompatible.cs + Microsoft\Data\Common\DbConnectionStringCommon.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/BitConverterCompatible.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/BitConverterCompatible.cs new file mode 100644 index 0000000000..5e93a2f336 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/BitConverterCompatible.cs @@ -0,0 +1,18 @@ +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.Data.SqlClient +{ + internal static class BitConverterCompatible + { + public static unsafe int SingleToInt32Bits(float value) + { + return *(int*)(&value); + } + public static unsafe float Int32BitsToSingle(int value) + { + return *(float*)(&value); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs index 436ad67c8e..9ea820000e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers.Binary; using System.Diagnostics; using System.Text; using System.Threading; @@ -514,6 +515,15 @@ public override void Convert(byte[] bytes, int byteIndex, int byteCount, char[] completed = (bytesUsed == byteCount); // BlockCopy uses offsets\length measured in bytes, not the actual array index + if (!BitConverter.IsLittleEndian) + { + Span span = bytes.AsSpan(); + for (int ii = 0; ii < byteCount; ii += 2) + { + short value = BinaryPrimitives.ReadInt16LittleEndian(span.Slice(ii)); + BinaryPrimitives.WriteInt16BigEndian(span.Slice(ii), value); + } + } Buffer.BlockCopy(bytes, byteIndex, chars, charIndex * 2, bytesUsed); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index 0e6655a8b2..8f405cc85f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Diagnostics; using System.Security; @@ -527,6 +528,13 @@ internal bool TryReadChars(char[] chars, int charsOffset, int charsCount, out in } } } + if (!BitConverter.IsLittleEndian) + { + for (int ii = charsOffset; ii < charsCopied + charsOffset; ii++) + { + chars[ii] = (char)BinaryPrimitives.ReverseEndianness((ushort)chars[ii]); + } + } return true; } @@ -1365,7 +1373,7 @@ internal bool TryReadInt64(out long value) Debug.Assert(_bTmpRead + bytesRead == 8, "TryReadByteArray returned true without reading all data required"); _bTmpRead = 0; AssertValidState(); - value = BitConverter.ToInt64(_bTmp, 0); + value = BinaryPrimitives.ReadInt64LittleEndian(_bTmp); return true; } } @@ -1374,7 +1382,7 @@ internal bool TryReadInt64(out long value) // The entire long is in the packet and in the buffer, so just return it // and take care of the counters. - value = BitConverter.ToInt64(_inBuff, _inBytesUsed); + value = BinaryPrimitives.ReadInt64LittleEndian(_inBuff.AsSpan(_inBytesUsed)); _inBytesUsed += 8; _inBytesPacket -= 8; @@ -1444,7 +1452,7 @@ internal bool TryReadUInt32(out uint value) Debug.Assert(_bTmpRead + bytesRead == 4, "TryReadByteArray returned true without reading all data required"); _bTmpRead = 0; AssertValidState(); - value = BitConverter.ToUInt32(_bTmp, 0); + value = BinaryPrimitives.ReadUInt32LittleEndian(_bTmp); return true; } } @@ -1453,7 +1461,7 @@ internal bool TryReadUInt32(out uint value) // The entire int is in the packet and in the buffer, so just return it // and take care of the counters. - value = BitConverter.ToUInt32(_inBuff, _inBytesUsed); + value = BinaryPrimitives.ReadUInt32LittleEndian(_inBuff.AsSpan(_inBytesUsed)); _inBytesUsed += 4; _inBytesPacket -= 4; @@ -1479,7 +1487,7 @@ internal bool TryReadSingle(out float value) } AssertValidState(); - value = BitConverter.ToSingle(_bTmp, 0); + value = BitConverterCompatible.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(_bTmp)); return true; } else @@ -1487,7 +1495,7 @@ internal bool TryReadSingle(out float value) // The entire float is in the packet and in the buffer, so just return it // and take care of the counters. - value = BitConverter.ToSingle(_inBuff, _inBytesUsed); + value = BitConverterCompatible.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(_inBuff.AsSpan(_inBytesUsed))); _inBytesUsed += 4; _inBytesPacket -= 4; @@ -1513,7 +1521,7 @@ internal bool TryReadDouble(out double value) } AssertValidState(); - value = BitConverter.ToDouble(_bTmp, 0); + value = BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64LittleEndian(_bTmp)); return true; } else @@ -1521,7 +1529,7 @@ internal bool TryReadDouble(out double value) // The entire double is in the packet and in the buffer, so just return it // and take care of the counters. - value = BitConverter.ToDouble(_inBuff, _inBytesUsed); + value = BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64LittleEndian(_inBuff.AsSpan(_inBytesUsed))); _inBytesUsed += 8; _inBytesPacket -= 8; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs index fd50f68a0b..f2c33824a2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers.Binary; using System.Data; using System.Data.SqlTypes; using System.Diagnostics; @@ -586,9 +587,21 @@ internal void SetDateTime(DateTime value) if (SqlDbType.DateTime2 == _metaData.SqlDbType) { long time = value.TimeOfDay.Ticks / TdsEnums.TICKS_FROM_SCALE[_metaData.Scale]; +#if NETCOREAPP + Span result_time = stackalloc byte[8]; + BinaryPrimitives.WriteInt64LittleEndian(result_time, time); + _stateObj.WriteByteSpan(result_time.Slice(0, (int)_metaData.MaxLength - 3)); +#else _stateObj.WriteByteArray(BitConverter.GetBytes(time), (int)_metaData.MaxLength - 3, 0); +#endif } +#if NETCOREAPP + Span result_date = stackalloc byte[4]; + BinaryPrimitives.WriteInt32LittleEndian(result_date, days); + _stateObj.WriteByteSpan(result_date.Slice(0, 3)); +#else _stateObj.WriteByteArray(BitConverter.GetBytes(days), 3, 0); +#endif } } } @@ -646,7 +659,13 @@ internal void SetTimeSpan(TimeSpan value) _stateObj.WriteByte(length); } long time = value.Ticks / TdsEnums.TICKS_FROM_SCALE[scale]; +#if NETCOREAPP + Span result_time = stackalloc byte[8]; + BinaryPrimitives.WriteInt64LittleEndian(result_time, time); + _stateObj.WriteByteSpan(result_time.Slice(0, length)); +#else _stateObj.WriteByteArray(BitConverter.GetBytes(time), length, 0); +#endif } // valid for DateTimeOffset @@ -677,8 +696,15 @@ internal void SetDateTimeOffset(DateTimeOffset value) int days = utcDateTime.Subtract(DateTime.MinValue).Days; short offset = (short)value.Offset.TotalMinutes; +#if NETCOREAPP + Span result = stackalloc byte[9]; + BinaryPrimitives.WriteInt64LittleEndian(result, time); + BinaryPrimitives.WriteInt32LittleEndian(result.Slice(5), days); + _stateObj.WriteByteSpan(result.Slice(0, 8)); +#else _stateObj.WriteByteArray(BitConverter.GetBytes(time), length - 5, 0); // time _stateObj.WriteByteArray(BitConverter.GetBytes(days), 3, 0); // date +#endif _stateObj.WriteByte((byte)(offset & 0xff)); // offset byte 1 _stateObj.WriteByte((byte)((offset >> 8) & 0xff)); // offset byte 2 }