* roottypes.cs: Rename from tree.cs.
[mono.git] / mcs / class / ByteFX.Data / mysqlclient / Driver.cs
old mode 100755 (executable)
new mode 100644 (file)
index eab99e2..7e51bd0
@@ -20,559 +20,410 @@ using System.Net;
 using System.Net.Sockets;\r
 using System.IO;\r
 using ICSharpCode.SharpZipLib.Zip.Compression;\r
+using ICSharpCode.SharpZipLib.Zip.Compression.Streams;\r
+using System.Security.Cryptography;\r
+using ByteFX.Data.Common;\r
+using System.Collections;\r
+using System.Text;\r
 \r
-namespace ByteFX.Data.MySQLClient\r
+namespace ByteFX.Data.MySqlClient\r
 {\r
        /// <summary>\r
        /// Summary description for Driver.\r
        /// </summary>\r
-       internal class Driver : IDisposable\r
+       internal class Driver\r
        {\r
-               protected const int COMPRESS_HEADER_LEN = 3;\r
                protected const int HEADER_LEN = 4;\r
-               protected const int MIN_COMPRESS_LEN = 50;\r
-\r
-               public MemoryStream             _packet;\r
-               protected Stream                _stream;\r
-               protected Socket                _socket;\r
-               protected int                   m_Seq;\r
-               protected int                   m_BufIndex;\r
-               protected byte                  m_LastResult;\r
-               protected byte[]                m_Buffer;\r
-               protected int                   m_Timeout;\r
-               protected int                   _port;\r
-\r
-               int             m_Protocol;\r
-               String  m_ServerVersion;\r
-               int             m_ThreadID;\r
-               String  m_EncryptionSeed;\r
-               int             m_ServerCaps;\r
-               bool    m_UseCompression = false;\r
-\r
-\r
-               public Driver(int ConnectionTimeout)\r
-               {\r
-                       m_Seq = -1;\r
-                       m_LastResult = 0xff;\r
-                       m_Timeout = ConnectionTimeout;\r
-                       m_BufIndex = 0;\r
+               protected const int MIN_COMPRESS_LENGTH = 50;\r
+               protected const int MAX_PACKET_SIZE = 256*256*256-1;\r
 \r
-                       ResetPacket();\r
-               }\r
+               protected Stream                        stream;\r
+               protected BufferedStream        writer;\r
+               protected Encoding                      encoding;\r
+               protected byte                          packetSeq;\r
+               protected long                          maxPacketSize;\r
+               protected DBVersion                     serverVersion;\r
+               protected bool                          isOpen;\r
+               protected string                        versionString;\r
+               protected Packet                        peekedPacket;\r
+\r
+               protected int                           protocol;\r
+               protected uint                          threadID;\r
+               protected String                        encryptionSeed;\r
+               protected int                           serverCaps;\r
+               protected bool                          useCompression = false;\r
 \r
-               ~Driver() \r
-               {\r
-               }\r
 \r
-               public byte LastResult \r
+               public Driver()\r
                {\r
-                       get { return m_LastResult; }\r
+                       packetSeq = 0;\r
+                       encoding = System.Text.Encoding.Default;\r
+                       isOpen = false;\r
                }\r
 \r
-               public string ServerVersion \r
+               public Encoding Encoding \r
                {\r
-                       get { return m_ServerVersion; }\r
+                       get { return encoding; }\r
+                       set { encoding = value; }\r
                }\r
 \r
-               public void Dispose() \r
+               public long MaxPacketSize \r
                {\r
+                       get { return maxPacketSize; }\r
+                       set { maxPacketSize = value; }\r
                }\r
 \r
-#if WINDOWS\r
-               /// <summary>\r
-               /// \r
-               /// </summary>\r
-               /// <param name="host"></param>\r
-               void CreatePipeStream( string host ) \r
+               public string VersionString \r
                {\r
-                       string _pipename;\r
-                       if (host.ToLower().Equals("localhost"))\r
-                               _pipename = @"\\.\pipe\MySQL";\r
-                       else\r
-                               _pipename = String.Format(@"\\{0}\pipe\MySQL", host);\r
-\r
-                       _stream = new ByteFX.Data.Common.NamedPipeStream(_pipename, FileAccess.ReadWrite);\r
+                       get { return versionString; }\r
                }\r
-#endif\r
 \r
-               /// <summary>\r
-               /// \r
-               /// </summary>\r
-               /// <param name="host"></param>\r
-               /// <param name="port"></param>\r
-               void CreateSocketStream( string host, int port ) \r
+               public DBVersion Version \r
                {\r
-                       _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);\r
-                       IPHostEntry he = Dns.GetHostByName(host);\r
-                       IPEndPoint _serverAddr = new IPEndPoint(he.AddressList[0], port);\r
-\r
-                       _socket.Connect(_serverAddr);\r
-                       _stream = new NetworkStream(_socket);\r
+                       get { return serverVersion; }\r
                }\r
 \r
-               /// <summary>\r
-               /// \r
-               /// </summary>\r
-               /// <param name="host"></param>\r
-               /// <param name="port"></param>\r
-               /// <param name="userid"></param>\r
-               /// <param name="password"></param>\r
-               public void Open( String host, int port, String userid, String password, bool UseCompression ) \r
+               public void Open( MySqlConnectionString settings )\r
                {\r
-                       _port = port;\r
-#if WINDOWS\r
-                       if (-1 == port) \r
+                       // connect to one of our specified hosts\r
+                       try \r
                        {\r
-                               CreatePipeStream(host);\r
+                               StreamCreator sc = new StreamCreator( settings.Server, settings.Port, settings.PipeName );\r
+                               stream = sc.GetStream( settings.ConnectionTimeout );\r
                        }\r
-#endif\r
-                       \r
-                       if (-1 != port)\r
+                       catch (Exception ex)\r
                        {\r
-                               CreateSocketStream(host, port);\r
+                               throw new MySqlException("Unable to connect to any of the specified MySQL hosts", ex);\r
                        }\r
 \r
-                       ReadPacket();\r
+                       if (stream == null) \r
+                               throw new MySqlException("Unable to connect to any of the specified MySQL hosts");\r
 \r
-                       // read off the protocol version\r
-                       m_Protocol = _packet.ReadByte();\r
-                       m_ServerVersion = ReadString();\r
-                       m_ThreadID = ReadInteger(4);\r
-                       m_EncryptionSeed = ReadString();\r
+                       writer = new BufferedStream( stream );\r
+                       // read off the welcome packet and parse out it's values\r
+                       Packet packet = ReadPacket();\r
+                       protocol = packet.ReadByte();\r
+                       versionString = packet.ReadString();\r
+                       serverVersion = DBVersion.Parse( versionString );\r
+                       threadID = (uint)packet.ReadInteger(4);\r
+                       encryptionSeed = packet.ReadString();\r
 \r
                        // read in Server capabilities if they are provided\r
-                       m_ServerCaps = 0;\r
-                       if (_packet.CanRead)\r
-                               m_ServerCaps = ReadInteger(2);\r
+                       serverCaps = 0;\r
+                       if (packet.HasMoreData)\r
+                               serverCaps = (int)packet.ReadInteger(2);\r
+\r
+                       Authenticate( settings.UserId, settings.Password, settings.UseCompression );\r
+\r
+                       // if we are using compression, then we use our CompressedStream class\r
+                       // to hide the ugliness of managing the compression\r
+                       if (settings.UseCompression)\r
+                       {\r
+                               stream = new CompressedStream( stream );\r
+                               writer = new BufferedStream( stream );\r
+                       }\r
 \r
-                       Authenticate( userid, password, UseCompression );\r
+                       isOpen = true;\r
+               }\r
+\r
+               private Packet CreatePacket( byte[] buf )\r
+               {\r
+                       if (buf == null)\r
+                               return new Packet( serverVersion.isAtLeast(3, 22, 5) );\r
+                       return new Packet( buf, serverVersion.isAtLeast(3, 22, 5 ));\r
                }\r
 \r
-               /// <summary>\r
-               /// \r
-               /// </summary>\r
-               /// <param name="userid"></param>\r
-               /// <param name="password"></param>\r
                private void Authenticate( String userid, String password, bool UseCompression )\r
                {\r
                        ClientParam clientParam = ClientParam.CLIENT_FOUND_ROWS | ClientParam.CLIENT_LONG_FLAG;\r
 \r
-                       if ((m_ServerCaps & (int)ClientParam.CLIENT_COMPRESS) != 0 && UseCompression)\r
+                       if ((serverCaps & (int)ClientParam.CLIENT_COMPRESS) != 0 && UseCompression)\r
                        {\r
                                clientParam |= ClientParam.CLIENT_COMPRESS;\r
                        }\r
 \r
                        clientParam |= ClientParam.CLIENT_LONG_PASSWORD;\r
+                       clientParam |= ClientParam.CLIENT_LOCAL_FILES;\r
+//                     if (serverVersion.isAtLeast(4,1,0))\r
+//                             clientParam |= ClientParam.CLIENT_PROTOCOL_41;\r
+//                     if ( (serverCaps & (int)ClientParam.CLIENT_SECURE_CONNECTION ) != 0 && password.Length > 0 )\r
+//                             clientParam |= ClientParam.CLIENT_SECURE_CONNECTION;\r
 \r
-                       password = EncryptPassword(password, m_EncryptionSeed, m_Protocol > 9);\r
-                       // header_length = 4\r
-                       //int headerLength = (userid.Length + 16) + 6 + 4; // Passwords can be 16 chars long\r
-\r
-                       ResetPacket();\r
-                       WriteInteger( (int)clientParam, 2 );\r
-                       WriteInteger( 0, 3 ); \r
-                       WriteString( userid );\r
-                       WriteString( password );\r
-                       WritePacket();\r
-\r
-                       CheckResult();\r
-\r
-                       if ((clientParam & ClientParam.CLIENT_COMPRESS) != 0)\r
-                               m_UseCompression = true;\r
-               }\r
-\r
-               public void ResetPacket()\r
-               {\r
-                       _packet = new MemoryStream();\r
-                       _packet.SetLength(0);\r
-\r
-                       // hack for Mono 0.17 not handling length < position on MemoryStream\r
-                       _packet.Position = 0;\r
-                       WriteInteger(0, HEADER_LEN);\r
+                       int packetLength = userid.Length + 16 + 6 + 4;  // Passwords can be 16 chars long\r
 \r
-                       if (m_UseCompression)\r
-                               _packet.Position += (COMPRESS_HEADER_LEN+HEADER_LEN);\r
-               }\r
+                       Packet packet = CreatePacket(null);\r
 \r
-               protected bool CanReadStream()\r
-               {\r
-#if WINDOWS\r
-                       if (_port == -1)\r
+                       if ((clientParam & ClientParam.CLIENT_PROTOCOL_41) != 0)\r
                        {\r
-                               return (_stream as ByteFX.Data.Common.NamedPipeStream).DataAvailable;\r
+                               packet.WriteInteger( (int)clientParam, 4 );\r
+                               packet.WriteInteger( (256*256*256)-1, 4 );\r
                        }\r
-#endif\r
-                       return (_stream as NetworkStream).DataAvailable;\r
-               }\r
-\r
-               /// <summary>\r
-               /// \r
-               /// </summary>\r
-               /// <returns></returns>\r
-               public byte ReadStreamByte()\r
-               {\r
-                       long start = DateTime.Now.Ticks;\r
-                       long timeout = m_Timeout * TimeSpan.TicksPerSecond;\r
-\r
-                       while ((DateTime.Now.Ticks - start) < timeout)\r
+                       else\r
                        {\r
-                               if (CanReadStream()) return (byte)_stream.ReadByte();\r
+                               packet.WriteInteger( (int)clientParam, 2 );\r
+                               packet.WriteInteger( 255*255*255, 3 );\r
                        }\r
-                       throw new MySQLException("Timeout waiting for response from server");\r
-               }\r
 \r
-               /// <summary>\r
-               /// \r
-               /// </summary>\r
-               /// <param name="buf"></param>\r
-               /// <param name="offset"></param>\r
-               /// <param name="count"></param>\r
-               protected void ReadStreamBytes(byte[] buf, int offset, int count)\r
-               {\r
-                       long start = DateTime.Now.Ticks;\r
-                       long timeout = m_Timeout * TimeSpan.TicksPerSecond;\r
-                       long curoffset = offset;\r
-\r
-                       while (count > 0 && ((DateTime.Now.Ticks - start) < timeout))\r
+                       packet.WriteString( userid, encoding  );\r
+                       if ( (clientParam & ClientParam.CLIENT_SECURE_CONNECTION ) != 0 )\r
                        {\r
-                               if (CanReadStream()) \r
-                               {\r
-                                       int cnt = _stream.Read(buf, (int)curoffset, count);\r
-                                       count -= cnt;\r
-                                       curoffset += cnt;\r
-                               }\r
+                               // use the new authentication system\r
+                               AuthenticateSecurely( packet, password );\r
                        }\r
-                       if (count > 0)\r
-                               throw new MySQLException("Timeout waiting for response from server");\r
+                       else\r
+                       {\r
+                               // use old authentication system\r
+                               packet.WriteString( EncryptPassword(password, encryptionSeed, protocol > 9), encoding );\r
+                               // pad zeros out to packetLength for auth\r
+                               for (int i=0; i < (packetLength-packet.Length); i++)\r
+                                       packet.WriteByte(0);\r
+                               SendPacket(packet);\r
+                       }\r
+\r
+                       packet = ReadPacket();\r
+                       if ((clientParam & ClientParam.CLIENT_COMPRESS) != 0)\r
+                               useCompression = true;\r
                }\r
 \r
                /// <summary>\r
-               /// \r
+               /// AuthenticateSecurity implements the new 4.1 authentication scheme\r
                /// </summary>\r
-               private void ReadServerDataBlock()\r
+               /// <param name="packet">The in-progress packet we use to complete the authentication</param>\r
+               /// <param name="password">The password of the user to use</param>\r
+               private void AuthenticateSecurely( Packet packet, string password )\r
                {\r
-                       int b0 = (int)ReadStreamByte();\r
-                       int b1 = (int)ReadStreamByte();\r
-                       int b2 = (int)ReadStreamByte();\r
+                       packet.WriteString("xxxxxxxx", encoding );\r
+                       SendPacket(packet);\r
 \r
-                       if (b0 == -1 && b1 == -1 && b2 == -1) \r
-                       {\r
-                               //TODO: close?\r
-                               throw new IOException("Unexpected end of input stream");\r
-                       }\r
+                       packet = ReadPacket();\r
 \r
-                       int packetLength = (int)(b0+ (256*b1) + (256*256*b2));\r
-                       int comp_len = 0;\r
-                       byte Seq = (byte)ReadStreamByte();\r
-       \r
-                       // handle the stupid field swapping does if compression is used\r
-                       // If the block is compressed, then the first length field is the compressed\r
-                       // length and the second is the uncompressed.\r
-                       // If the block is uncompressed, even if compression is selected, the first\r
-                       // length field is the uncompressed size and the second field is zero\r
-                       if (m_UseCompression) \r
-                       {\r
-                               int c0 = (int)ReadStreamByte();\r
-                               int c1 = (int)ReadStreamByte();\r
-                               int c2 = (int)ReadStreamByte();\r
-                               comp_len = (int)(c0 + (256*c1) + (256*256*c2));\r
-                               if (comp_len > 0) \r
-                               {\r
-                                       int temp = packetLength;\r
-                                       packetLength = comp_len;\r
-                                       comp_len = temp;\r
-                               }\r
-                       }\r
+                       // compute pass1 hash\r
+                       string newPass = password.Replace(" ","").Replace("\t","");\r
+                       SHA1 sha = new SHA1CryptoServiceProvider(); \r
+                       byte[] firstPassBytes = sha.ComputeHash( System.Text.Encoding.Default.GetBytes(newPass));\r
 \r
-                       if (m_UseCompression && comp_len > 0) \r
-                       {\r
-                               m_Buffer = new Byte[packetLength];\r
-                               byte[] comp = new Byte[comp_len];\r
-                               // read in the compressed data\r
-                               ReadStreamBytes(comp, 0, comp_len);\r
+                       byte[] salt = packet.GetBuffer();\r
+                       byte[] input = new byte[ firstPassBytes.Length + 4 ];\r
+                       salt.CopyTo( input, 0 );\r
+                       firstPassBytes.CopyTo( input, 4 );\r
+                       byte[] outPass = new byte[100];\r
+                       byte[] secondPassBytes = sha.ComputeHash( input );\r
 \r
-                               Inflater i = new Inflater();\r
-                               i.SetInput( comp );\r
+                       byte[] cryptSalt = new byte[20];\r
+                       Security.ArrayCrypt( salt, 4, cryptSalt, 0, secondPassBytes, 20 );\r
 \r
-                               i.Inflate(m_Buffer);\r
-                               return;\r
-                       }\r
+                       Security.ArrayCrypt( cryptSalt, 0, firstPassBytes, 0, firstPassBytes, 20 );\r
 \r
-                       if (!m_UseCompression) \r
-                       {\r
-                               m_Buffer = new Byte[packetLength+4];\r
-                               ReadStreamBytes(m_Buffer, 4, packetLength);\r
-                               m_Buffer[0] = (byte)b0; m_Buffer[1] = (byte)b1; m_Buffer[2] = (byte)b2;\r
-                               m_Buffer[3] = (byte)Seq;\r
-                       }\r
-                       else \r
-                       {\r
-                               m_Buffer = new Byte[packetLength];\r
-                               ReadStreamBytes(m_Buffer, 0, packetLength);\r
-                       }\r
+                       // send the packet\r
+                       packet = CreatePacket(null);\r
+                       packet.Write( firstPassBytes, 0, 20 );\r
+                       SendPacket(packet);\r
                }\r
 \r
+\r
                /// <summary>\r
                /// \r
                /// </summary>\r
                /// <returns></returns>\r
-               public int ReadPacket()\r
+               public Packet PeekPacket()\r
                {\r
-                       if (_packet == null || m_Buffer == null || m_BufIndex == m_Buffer.Length)\r
-                       {\r
-                               ReadServerDataBlock();\r
-                               m_BufIndex = 0;\r
-                       }\r
+                       if (peekedPacket != null)\r
+                               return peekedPacket;\r
 \r
-                       _packet = new MemoryStream(m_Buffer, m_BufIndex, m_Buffer.Length - m_BufIndex);\r
-                       int len = ReadInteger(3);\r
-                       int seq = (int)ReadByte();\r
-                       _packet.SetLength(len+HEADER_LEN);\r
-                       m_BufIndex += (int)_packet.Length;\r
-\r
-                       // if the sequence doesn't match up, then there must be some orphaned\r
-                       // packets so we just read them off\r
-                       if (seq != (m_Seq+1)) return ReadPacket();\r
-                       \r
-                       m_Seq = seq;\r
-                       return len;\r
-}\r
+                       peekedPacket = ReadPacket();\r
+                       return peekedPacket;\r
+               }\r
 \r
                /// <summary>\r
-               /// \r
+               /// ReadBuffer continuously loops until it has read the entire\r
+               /// requested data\r
                /// </summary>\r
-               /// <returns></returns>\r
-               protected int CompressPacket()\r
+               /// <param name="buf">Buffer to read data into</param>\r
+               /// <param name="offset">Offset to place the data</param>\r
+               /// <param name="length">Number of bytes to read</param>\r
+               private void ReadBuffer( byte[] buf, int offset, int length )\r
                {\r
-                       // compress the entire packet except the length\r
-\r
-                       // make sure we are using a packet prep'ed for compression\r
-                       // and that our packet is large enough to warrant compression\r
-                       // re: my_compress.c from mysql src\r
-                       int offset = HEADER_LEN + COMPRESS_HEADER_LEN;\r
-                       int original_len = (int)(_packet.Length - offset);\r
-                       if (original_len < MIN_COMPRESS_LEN) return 0;\r
-\r
-                       byte[] packetData = _packet.ToArray();\r
-\r
-                       byte[] output = new Byte[ original_len * 2 ];\r
-                       Deflater d = new Deflater();\r
-                       d.SetInput( packetData, offset, original_len );\r
-                       d.Finish();\r
-                       int comp_len = d.Deflate( output, offset, output.Length - offset  );\r
-\r
-                       if (comp_len > original_len) return 0;\r
-                       _packet = new MemoryStream( output, 0, comp_len + offset );\r
-                       return (int)comp_len;\r
+                       while (length > 0)\r
+                       {\r
+                               int amountRead = stream.Read( buf, offset, length );\r
+                               if (amountRead == 0)\r
+                                       throw new MySqlException("Unexpected end of data encountered");\r
+                               length -= amountRead;\r
+                               offset += amountRead;\r
+                       }\r
+               }\r
+\r
+               private Packet ReadPacketFromServer()\r
+               {\r
+                       int len = stream.ReadByte() + (stream.ReadByte() << 8) +\r
+                               (stream.ReadByte() << 16);\r
+                       byte seq = (byte)stream.ReadByte();\r
+                       byte[] buf = new byte[ len ];\r
+                       ReadBuffer( buf, 0, len );\r
+\r
+                       if (seq != packetSeq) \r
+                               throw new MySqlException("Unknown transmission status: sequence out of order");\r
+                       packetSeq++;\r
+\r
+                       Packet p = CreatePacket(buf);\r
+                       p.Encoding = this.Encoding;\r
+                       if (p.Length == MAX_PACKET_SIZE && serverVersion.isAtLeast(4,0,0)) \r
+                               p.Append( ReadPacketFromServer() );\r
+                       return p;\r
                }\r
 \r
                /// <summary>\r
-               /// \r
+               /// Reads a single packet off the stream\r
                /// </summary>\r
-               /// <param name="useCompressionIfAvail"></param>\r
-               protected void WritePacket()\r
+               /// <returns></returns>\r
+               public Packet ReadPacket()\r
                {\r
-                       if (m_UseCompression)\r
+                       // if we have peeked at a packet, then return it\r
+                       if (peekedPacket != null)\r
                        {\r
-                               // store the length of the buffer we are going to compress\r
-                               long num_bytes = _packet.Length - (HEADER_LEN*2) - COMPRESS_HEADER_LEN;\r
-                               _packet.Position = HEADER_LEN + COMPRESS_HEADER_LEN;\r
-                               WriteInteger( (int) num_bytes, 3 );\r
-                               _packet.WriteByte(0);                           // internal packet has 0 as seq if compressing\r
+                               Packet packet = peekedPacket;\r
+                               peekedPacket = null;\r
+                               return packet;\r
+                       }\r
 \r
-                               // now compress it\r
-                               int compressed_size = CompressPacket();\r
+                       Packet p = ReadPacketFromServer();\r
 \r
-                               _packet.Position = 0;\r
-                               if (compressed_size == 0) \r
-                               {\r
-                                       WriteInteger( (int)num_bytes + HEADER_LEN, 3);\r
-                                       _packet.WriteByte((byte)++m_Seq);\r
-                                       WriteInteger( compressed_size, 3 );\r
-                               }\r
-                               else \r
-                               {\r
-                                       WriteInteger( compressed_size, 3 );\r
-                                       _packet.WriteByte((byte)++m_Seq);\r
-                                       WriteInteger( (int)num_bytes + HEADER_LEN, 3);\r
-                               }\r
-                       }\r
-                       else \r
+                       // if this is an error packet, then throw the exception\r
+                       if (p[0] == 0xff)\r
                        {\r
-                               _packet.Position = 0;\r
-                               WriteInteger( (int)(_packet.Length - HEADER_LEN), 3 );\r
-                               _packet.WriteByte((byte)++m_Seq);\r
+                               p.ReadByte();\r
+                               int errorCode = (int)p.ReadInteger(2);\r
+                               string msg = p.ReadString();\r
+                               throw new MySqlException( msg, errorCode );\r
                        }\r
-\r
-                       _stream.Write( _packet.ToArray(), 0, (int)_packet.Length );\r
-                       _stream.Flush();\r
-\r
-                       // reset the writeStream to empty\r
-                       ResetPacket();\r
+                       \r
+                       return p;\r
                }\r
 \r
-\r
-               protected void WriteString(string v)\r
+               protected MemoryStream CompressBuffer(byte[] buf, int index, int length)\r
                {\r
-                       WriteStringNoNull(v);\r
-                       _packet.WriteByte(0);\r
-               }\r
 \r
-               protected void WriteStringNoNull(string v)\r
-               {\r
-                       byte[] bytes = System.Text.Encoding.ASCII.GetBytes(v);\r
-                       _packet.Write(bytes, 0, bytes.Length);\r
-               }\r
+                       if (length < MIN_COMPRESS_LENGTH) return null;\r
 \r
-               public void Close() \r
-               {\r
-                       m_Seq = -1;\r
-                       _stream.Close();\r
-                       if (_socket != null)\r
-                               _socket.Close();\r
-               }\r
+                       MemoryStream ms = new MemoryStream(buf.Length);\r
+                       DeflaterOutputStream dos = new DeflaterOutputStream(ms);\r
 \r
-               public string ReadString()\r
-               {\r
-                       String str = new String('c',0);\r
+                       dos.WriteByte( (byte)(length & 0xff ));\r
+                       dos.WriteByte( (byte)((length >> 8) & 0xff ));\r
+                       dos.WriteByte( (byte)((length >> 16) & 0xff ));\r
+                       dos.WriteByte( 0 );\r
 \r
-                       while (_packet.Position < _packet.Length) \r
-                       {\r
-                               byte b = (byte)_packet.ReadByte();\r
-                               if (b == 0) break;\r
-                               str += Convert.ToChar(b);\r
-                       }\r
-                       return str;\r
+                       dos.Write( buf, index, length );\r
+                       dos.Finish();\r
+                       if (ms.Length > length+4) return null;\r
+                       return ms;\r
                }\r
 \r
-               protected void WriteInteger( int v, int numbytes )\r
+               private void WriteInteger( int v, int numbytes )\r
                {\r
                        int val = v;\r
 \r
                        if (numbytes < 1 || numbytes > 4) \r
-                               throw new Exception("Wrong byte count for WriteInteger");\r
+                               throw new ArgumentOutOfRangeException("Wrong byte count for WriteInteger");\r
 \r
                        for (int x=0; x < numbytes; x++)\r
                        {\r
-                               _packet.WriteByte( (byte)(val&0xff) );\r
+                               writer.WriteByte( (byte)(val&0xff) );\r
                                val >>= 8;\r
                        }\r
                }\r
 \r
                /// <summary>\r
-               /// \r
-               /// </summary>\r
-               /// <param name="numbytes"></param>\r
-               /// <returns></returns>\r
-               public int ReadInteger(int numbytes)\r
-               {\r
-                       int val = 0;\r
-                       int raise = 1;\r
-                       for (int x=0; x < numbytes; x++)\r
-                       {\r
-                               int b = (int)_packet.ReadByte();\r
-                               val += (b*raise);\r
-                               raise *= 256;\r
-                       }\r
-                       return val;\r
-               }\r
-\r
-               /// <summary>\r
-               /// \r
+               /// Send a buffer to the server in a compressed form\r
                /// </summary>\r
-               /// <returns></returns>\r
-               public int ReadLength()\r
-               {\r
-                       byte c  = (byte)_packet.ReadByte();\r
-                       switch(c) \r
+               /// <param name="buf">Byte buffer to send</param>\r
+               /// <param name="index">Location in buffer to start sending</param>\r
+               /// <param name="length">Amount of data to send</param>\r
+               protected void SendCompressedBuffer(byte[] buf, int index, int length)\r
+               {\r
+                       MemoryStream compressed_bytes = CompressBuffer(buf, index, length);\r
+                       int comp_len = compressed_bytes == null ? length+HEADER_LEN : (int)compressed_bytes.Length;\r
+                       int ucomp_len = compressed_bytes == null ? 0 : length+HEADER_LEN;\r
+\r
+                       WriteInteger( comp_len, 3 );\r
+                       writer.WriteByte( packetSeq++ );\r
+                       WriteInteger( ucomp_len, 3 );\r
+                       if (compressed_bytes != null)\r
+                               writer.Write( compressed_bytes.GetBuffer(), 0, (int)compressed_bytes.Length );\r
+                       else \r
                        {\r
-                               case 251 : return (int) 0; \r
-                               case 252 : return ReadInteger(2);\r
-                               case 253 : return ReadInteger(3);\r
-                               case 254 : return ReadInteger(4);\r
-                               default  : return (int) c;\r
+                               WriteInteger( length, 3 );      \r
+                               writer.WriteByte( 0 );\r
+                               writer.Write( buf, index, length );\r
                        }\r
+                       stream.Flush();\r
                }\r
 \r
-               public byte ReadByte()\r
-               {\r
-                       return (byte)_packet.ReadByte();\r
-               }\r
-\r
-               public int ReadNBytes()\r
-               {\r
-                       byte c = (byte)_packet.ReadByte();\r
-                       if (c < 1 || c > 4) throw new MySQLException("Unexpected byte count received");\r
-                       return ReadInteger((int)c);\r
-               }\r
-\r
-               public string ReadLenString()\r
+               protected void SendBuffer( byte[] buf, int offset, int length )\r
                {\r
-                       int len = ReadLength();\r
-\r
-                       byte[] buf = new Byte[len];\r
-                       _packet.Read(buf, 0, len);\r
-\r
-                       String s = new String('c', 0);\r
-                       for (int x=0; x < buf.Length; x++)\r
-                               s += Convert.ToChar(buf[x]);\r
-                       return s;\r
-               }\r
-\r
-\r
-               void CheckResult()\r
-               {\r
-                       ReadPacket();\r
-\r
-                       m_LastResult = (byte)_packet.ReadByte();\r
-\r
-                       if (0xff == m_LastResult) \r
+                       while (length > 0)\r
                        {\r
-                               int errno = ReadInteger(2);\r
-                               string msg = ReadString();\r
-                               throw new MySQLException(msg, errno);\r
+                               int amount = Math.Min( 1024, length );\r
+                               writer.Write( buf, offset, amount );\r
+                               writer.Flush();\r
+                               offset += amount;\r
+                               length -= amount;\r
                        }\r
                }\r
 \r
                /// <summary>\r
-               /// \r
+               /// Send a single packet to the server.\r
                /// </summary>\r
-               /// <returns></returns>\r
-               public bool IsLastPacketSignal() \r
-               {\r
-                       byte b = (byte)_packet.ReadByte();\r
-                       _packet.Position--;\r
+               /// <param name="packet">Packet to send to the server</param>\r
+               /// <remarks>This method will send a single packet to the server\r
+               /// possibly breaking the packet up into smaller packets that are\r
+               /// smaller than max_allowed_packet.  This method will always send at\r
+               /// least one packet to the server</remarks>\r
+        protected void SendPacket(Packet packet)\r
+               {\r
+                       byte[]  buf = packet.GetBuffer();\r
+                       int             len = packet.Length;\r
+                       int             index = 0;\r
+                       bool    oneSent = false;\r
+\r
+                       // make sure we are not trying to send too much\r
+                       if (packet.Length > maxPacketSize && maxPacketSize > 0)\r
+                               throw new MySqlException("Packet size too large.  This MySQL server cannot accept rows larger than " + maxPacketSize + " bytes.");\r
 \r
-                       if ((_packet.Length - HEADER_LEN) == 1 && b == 0xfe)\r
+                       try \r
                        {\r
-                               return true;\r
+                               while (len > 0 || ! oneSent) \r
+                               {\r
+                                       int lenToSend = Math.Min( len, MAX_PACKET_SIZE );\r
+\r
+                                       // send the data\r
+                                       if (useCompression)\r
+                                               SendCompressedBuffer( buf, index, lenToSend );\r
+                                       else \r
+                                       {\r
+                                               WriteInteger( lenToSend, 3 );\r
+                                               writer.WriteByte( packetSeq++ );\r
+                                               writer.Write( buf, index, lenToSend );\r
+                                               writer.Flush();\r
+                                       }\r
+\r
+                                       len -= lenToSend;\r
+                                       index += lenToSend;\r
+                                       oneSent = true;\r
+                               }\r
+                               writer.Flush();\r
                        }\r
-\r
-                       return false;\r
-               }\r
-\r
-               /// <summary>\r
-               /// Read the byte data from the server for the next column\r
-               /// </summary>\r
-               /// <returns></returns>\r
-               public byte[] ReadColumnData()\r
-               {\r
-                       int             len;\r
-\r
-                       byte c = (byte)_packet.ReadByte(); \r
-\r
-                       switch (c)\r
+                       catch (Exception ex)\r
                        {\r
-                               case 251:  return null; //new byte[1] { c }; \r
-                               case 252:  len = ReadInteger(2); break;\r
-                               case 253:  len = ReadInteger(3); break;\r
-                               case 254:  len = ReadInteger(4); break;\r
-                               default:   len = c; break;\r
+                               Console.WriteLine( ex.Message );\r
                        }\r
+               }\r
+\r
 \r
-                       byte[] buf = new Byte[len];\r
-                       _packet.Read(buf, 0, len);\r
-                       return buf;\r
+               public void Close() \r
+               {\r
+                       if (stream != null)\r
+                               stream.Close();\r
                }\r
 \r
 \r
@@ -582,45 +433,85 @@ namespace ByteFX.Data.MySQLClient
                /// <param name="command">Command to execute</param>\r
                /// <param name="text">Text attribute of command</param>\r
                /// <returns>Result packet returned from database server</returns>\r
-               public void SendCommand( DBCmd command, String text ) \r
+               public void Send( DBCmd command, String text ) \r
                {\r
-                       m_Seq = -1;\r
-                       ResetPacket();\r
+                       CommandResult result = Send( command, this.Encoding.GetBytes( text ) );\r
+                       if (result.IsResultSet)\r
+                               throw new MySqlException("SendCommand failed for command " + text );\r
+               }\r
 \r
-                       _packet.WriteByte( (byte)command );\r
+               public CommandResult Send( DBCmd cmd, byte[] bytes )\r
+               {\r
+//                     string s = Encoding.GetString( bytes );\r
 \r
-                       if (text != null && text.Length > 0)\r
-                               WriteStringNoNull(text);\r
+                       Packet packet = CreatePacket(null);\r
+                       packetSeq = 0;\r
+                       packet.WriteByte( (byte)cmd );\r
+                       if (bytes != null)\r
+                               packet.Write( bytes, 0, bytes.Length );\r
 \r
-                       try \r
-                       {\r
-                               WritePacket();\r
+                       SendPacket( packet );\r
+                       packet = ReadPacket();\r
 \r
-                               if (command != DBCmd.QUIT)\r
-                                       CheckResult();\r
-                       }\r
-                       catch (Exception e) \r
+                       // first check to see if this is a LOAD DATA LOCAL callback\r
+                       // if so, send the file and then read the results\r
+                       long fieldcount = packet.ReadLenInteger();\r
+                       if (fieldcount == Packet.NULL_LEN)\r
                        {\r
-                               throw e;\r
+                               string filename = packet.ReadString();\r
+                               SendFileToServer( filename );\r
+                               packet = ReadPacket();\r
                        }\r
+                       else\r
+                               packet.Position = 0;\r
+\r
+                       return new CommandResult(packet, this);\r
                }\r
 \r
-               public void SendQuery( byte[] sql )\r
+               /// <summary>\r
+               /// Sends the specified file to the server. \r
+               /// This supports the LOAD DATA LOCAL INFILE\r
+               /// </summary>\r
+               /// <param name="filename"></param>\r
+               private void SendFileToServer( string filename )\r
                {\r
+                       Packet          p = CreatePacket(null);\r
+                       byte[]          buffer = new byte[4092];\r
+                       FileStream      fs = null;\r
                        try \r
                        {\r
-                               m_Seq = -1;\r
-                               ResetPacket();\r
-\r
-                               _packet.WriteByte( (byte)DBCmd.QUERY );\r
-                               _packet.Write( sql, 0, sql.Length );\r
+                               fs = new FileStream( filename, FileMode.Open );\r
+                               int count = fs.Read( buffer, 0, buffer.Length );\r
+                               while (count != 0) \r
+                               {\r
+                                       if ((p.Length + count) > MAX_PACKET_SIZE)\r
+                                       {\r
+                                               SendPacket( p );\r
+                                               p.Clear();\r
+                                       }\r
+                                       p.Write( buffer, 0, count );\r
+                                       count = fs.Read( buffer, 0, buffer.Length );\r
+                               }\r
+                               fs.Close();\r
 \r
-                               WritePacket();\r
-                               CheckResult();\r
+                               // send any remaining data\r
+                               if (p.Length > 0) \r
+                               {\r
+                                       SendPacket(p);\r
+                                       p.Clear();\r
+                               }\r
                        }\r
-                       catch (Exception e\r
+                       catch (Exception ex)\r
                        {\r
-                               throw e;\r
+                               throw new MySqlException("Error during LOAD DATA LOCAL INFILE", ex);\r
+                       }\r
+                       finally \r
+                       {\r
+                               if (fs != null)\r
+                                       fs.Close();\r
+                               // empty packet signals end of file\r
+                               p.Clear();\r
+                               SendPacket(p);\r
                        }\r
                }\r
 \r
@@ -634,10 +525,11 @@ namespace ByteFX.Data.MySQLClient
                }\r
 \r
                /// <summary>\r
-               /// \r
+               /// Encrypts a password using the MySql encryption scheme\r
                /// </summary>\r
-               /// <param name="password"></param>\r
-               /// <param name="seed"></param>\r
+               /// <param name="password">The password to encrypt</param>\r
+               /// <param name="message">The encryption seed the server gave us</param>\r
+               /// <param name="new_ver">Indicates if we should use the old or new encryption scheme</param>\r
                /// <returns></returns>\r
                public static String EncryptPassword(String password, String message, bool new_ver)\r
                {\r