From 060ab393935b614970898350c01fdf58b509716e Mon Sep 17 00:00:00 2001 From: Sidraya Date: Thu, 24 Aug 2023 04:59:46 +0000 Subject: [PATCH] Add support for BigEndian clients in MySQL.Data BitConverter methods used in classes defined under the MySqlClient namespace inherently check for the endianness of the client system which leads to incorrect reads of incoming MySql packets sent by the server for BigEndian client machines. Implemented a PacketBitConverter class to read incoming packets in LittleEndian order always. BitConverter.ToString is unchanged since string values are read byte by byte. BitConverter used in packages like NetworkStream and CompressedStream should not be replaced as it interfaces with network libraries. Signed-off-by: Sidraya --- .../src/Authentication/MySqlPemReader.cs | 2 +- MySQL.Data/src/MySqlPacket.cs | 12 +-- MySQL.Data/src/PacketBitConverter.cs | 88 +++++++++++++++++++ MySQL.Data/src/Types/MySqlDouble.cs | 6 +- MySQL.Data/src/Types/MySqlGeometry.cs | 8 +- MySQL.Data/src/Types/MySqlSingle.cs | 6 +- 6 files changed, 105 insertions(+), 17 deletions(-) create mode 100644 MySQL.Data/src/PacketBitConverter.cs diff --git a/MySQL.Data/src/Authentication/MySqlPemReader.cs b/MySQL.Data/src/Authentication/MySqlPemReader.cs index 9de5044ac..6784819ff 100644 --- a/MySQL.Data/src/Authentication/MySqlPemReader.cs +++ b/MySQL.Data/src/Authentication/MySqlPemReader.cs @@ -113,7 +113,7 @@ static RSACryptoServiceProvider DecodeX509Key(byte[] key) } else return null; - int modulusSize = BitConverter.ToInt32(new byte[] { lowByte, highByte, 0x00, 0x00 }, 0); + int modulusSize = highByte << 8 | lowByte; byte firstByte = reader.ReadByte(); reader.BaseStream.Seek(-1, SeekOrigin.Current); diff --git a/MySQL.Data/src/MySqlPacket.cs b/MySQL.Data/src/MySqlPacket.cs index acc4887c4..0945576ac 100644 --- a/MySQL.Data/src/MySqlPacket.cs +++ b/MySQL.Data/src/MySqlPacket.cs @@ -212,9 +212,9 @@ public long ReadLong(int numbytes) switch (numbytes) { - case 2: return BitConverter.ToUInt16(bits, pos); - case 4: return BitConverter.ToUInt32(bits, pos); - case 8: return BitConverter.ToInt64(bits, pos); + case 2: return PacketBitConverter.ToUInt16(bits, pos); + case 4: return PacketBitConverter.ToUInt32(bits, pos); + case 8: return PacketBitConverter.ToInt64(bits, pos); } throw new NotSupportedException("Only byte lengths of 2, 4, or 8 are supported"); } @@ -230,9 +230,9 @@ public ulong ReadULong(int numbytes) switch (numbytes) { - case 2: return BitConverter.ToUInt16(bits, pos); - case 4: return BitConverter.ToUInt32(bits, pos); - case 8: return BitConverter.ToUInt64(bits, pos); + case 2: return PacketBitConverter.ToUInt16(bits, pos); + case 4: return PacketBitConverter.ToUInt32(bits, pos); + case 8: return PacketBitConverter.ToUInt64(bits, pos); } throw new NotSupportedException("Only byte lengths of 2, 4, or 8 are supported"); } diff --git a/MySQL.Data/src/PacketBitConverter.cs b/MySQL.Data/src/PacketBitConverter.cs new file mode 100644 index 000000000..a960a060e --- /dev/null +++ b/MySQL.Data/src/PacketBitConverter.cs @@ -0,0 +1,88 @@ +using System; +namespace MySql.Data.MySqlClient +{ + public static class PacketBitConverter + { + // Due to the lack of support of the C# BinaryPrimitives class + // on net452 and net48 this class is necessary for back compatibility. + // All instances of PacketBitConverter can be replaced with corresponding + // BinaryPrimitives methods for future versions. + + // The server sends MySql Packets in LittleEndian encoding + // The methods provided by the BitConverter class check for the + // endian-ness of the client system and do conversions accordingly + // which lead to incorrect conversions on BigEndian systems + + // Following function are analogues with BinaryPrimitives.Write*LittleEndian + public static byte[] GetBytes(int value) + { + return new byte[] { + (byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24) + }; + } + + public static byte[] GetBytes(long value) + { + return new byte[] { + (byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24), + (byte)(value >> 32), (byte)(value >> 40), (byte)(value >> 48), (byte)(value >> 56) + }; + } + + unsafe public static byte[] GetBytes(float value) + { + int val = *(int*)&value; + return GetBytes(val); + } + + unsafe public static byte[] GetBytes(double value) + { + long val = *(long*)&value; + return GetBytes(val); + } + + // Following functions are analogous to BinaryPrimitives.Read*LittleEndian + unsafe public static float ToSingle(byte[] byteArray, int startIndex) + { + int val = ToInt32(byteArray, startIndex); + return *(float*)&val; + } + + unsafe public static double ToDouble(byte[] byteArray, int startIndex) + { + long val = ToInt64(byteArray, startIndex); + return *(double*)&val; + } + + public static ushort ToUInt16(byte[] byteArray, int startIndex) + { + return (ushort)(byteArray[startIndex++] | byteArray[startIndex] << 8); + } + + public static uint ToUInt32(byte[] byteArray, int startIndex) + { + return (uint)(byteArray[startIndex++] | byteArray[startIndex++] << 8 + | byteArray[startIndex++] << 16 | byteArray[startIndex] << 24); + } + + public static ulong ToUInt64(byte[] byteArray, int startIndex) + { + return (ulong)ToUInt32(byteArray, startIndex) + ((ulong)ToUInt32(byteArray, startIndex+4) << 32); + } + + public static short ToInt16(byte[] byteArray, int startIndex) + { + return (short)ToUInt16(byteArray, startIndex); + } + + public static int ToInt32(byte[] byteArray, int startIndex) + { + return (int)ToUInt32(byteArray, startIndex); + } + + public static long ToInt64(byte[] byteArray, int startIndex) + { + return (long)ToUInt64(byteArray, startIndex); + } + } +} diff --git a/MySQL.Data/src/Types/MySqlDouble.cs b/MySQL.Data/src/Types/MySqlDouble.cs index 4e8989569..39bc786ec 100644 --- a/MySQL.Data/src/Types/MySqlDouble.cs +++ b/MySQL.Data/src/Types/MySqlDouble.cs @@ -66,7 +66,7 @@ async Task IMySqlValue.WriteValueAsync(MySqlPacket packet, bool binary, object v { double v = val as double? ?? Convert.ToDouble(val); if (binary) - await packet.WriteAsync(BitConverter.GetBytes(v), execAsync).ConfigureAwait(false); + await packet.WriteAsync(PacketBitConverter.GetBytes(v), execAsync).ConfigureAwait(false); else await packet.WriteStringNoNullAsync(v.ToString("R", CultureInfo.InvariantCulture), execAsync).ConfigureAwait(false); } @@ -80,7 +80,7 @@ async Task IMySqlValue.ReadValueAsync(MySqlPacket packet, long leng { byte[] b = new byte[8]; await packet.ReadAsync(b, 0, 8, execAsync).ConfigureAwait(false); - return new MySqlDouble(BitConverter.ToDouble(b, 0)); + return new MySqlDouble(PacketBitConverter.ToDouble(b, 0)); } string s = await packet.ReadStringAsync(length, execAsync).ConfigureAwait(false); @@ -142,4 +142,4 @@ internal static void SetDSInfo(MySqlSchemaCollection sc) row["NativeDataType"] = null; } } -} \ No newline at end of file +} diff --git a/MySQL.Data/src/Types/MySqlGeometry.cs b/MySQL.Data/src/Types/MySqlGeometry.cs index cb56723fc..a80fbaac5 100644 --- a/MySQL.Data/src/Types/MySqlGeometry.cs +++ b/MySQL.Data/src/Types/MySqlGeometry.cs @@ -109,7 +109,7 @@ internal MySqlGeometry(MySqlDbType type, Double xValue, Double yValue, int srid) this._srid = srid; this.Value = new byte[GEOMETRY_LENGTH]; - byte[] sridBinary = BitConverter.GetBytes(srid); + byte[] sridBinary = PacketBitConverter.GetBytes(srid); for (int i = 0; i < sridBinary.Length; i++) Value[i] = sridBinary[i]; @@ -148,9 +148,9 @@ public MySqlGeometry(MySqlDbType type, byte[] val) var yIndex = val.Length == GEOMETRY_LENGTH ? 17 : 13; Value = buffValue; - _xValue = val.Length >= xIndex + 8 ? BitConverter.ToDouble(val, xIndex) : 0; - _yValue = val.Length >= yIndex + 8 ? BitConverter.ToDouble(val, yIndex) : 0; - this._srid = val.Length == GEOMETRY_LENGTH ? BitConverter.ToInt32(val, 0) : 0; + _xValue = val.Length >= xIndex + 8 ? PacketBitConverter.ToDouble(val, xIndex) : 0; + _yValue = val.Length >= yIndex + 8 ? PacketBitConverter.ToDouble(val, yIndex) : 0; + this._srid = val.Length == GEOMETRY_LENGTH ? PacketBitConverter.ToInt32(val, 0) : 0; this.IsNull = false; this._type = type; } diff --git a/MySQL.Data/src/Types/MySqlSingle.cs b/MySQL.Data/src/Types/MySqlSingle.cs index 1cb2d544c..580b22f67 100644 --- a/MySQL.Data/src/Types/MySqlSingle.cs +++ b/MySQL.Data/src/Types/MySqlSingle.cs @@ -68,7 +68,7 @@ async Task IMySqlValue.WriteValueAsync(MySqlPacket packet, bool binary, object v { Single v = val as Single? ?? Convert.ToSingle(val); if (binary) - await packet.WriteAsync(BitConverter.GetBytes(v), execAsync).ConfigureAwait(false); + await packet.WriteAsync(PacketBitConverter.GetBytes(v), execAsync).ConfigureAwait(false); else await packet.WriteStringNoNullAsync(v.ToString("R", CultureInfo.InvariantCulture), execAsync).ConfigureAwait(false); } @@ -82,7 +82,7 @@ async Task IMySqlValue.ReadValueAsync(MySqlPacket packet, long leng { byte[] b = new byte[4]; await packet.ReadAsync(b, 0, 4, execAsync).ConfigureAwait(false); - return new MySqlSingle(BitConverter.ToSingle(b, 0)); + return new MySqlSingle(PacketBitConverter.ToSingle(b, 0)); } return new MySqlSingle(Single.Parse(await packet.ReadStringAsync(length, execAsync).ConfigureAwait(false), @@ -127,4 +127,4 @@ internal static void SetDSInfo(MySqlSchemaCollection sc) row["NativeDataType"] = null; } } -} \ No newline at end of file +}