1 // created on 6/14/2002 at 7:56 PM
3 // Npgsql.NpgsqlState.cs
6 // Dave Joyner <d4ljoyn@yahoo.com>
8 // Copyright (C) 2002 The Npgsql Development Team
9 // npgsql-general@gborg.postgresql.org
10 // http://gborg.postgresql.org/project/npgsql/projdisplay.php
12 // This library is free software; you can redistribute it and/or
13 // modify it under the terms of the GNU Lesser General Public
14 // License as published by the Free Software Foundation; either
15 // version 2.1 of the License, or (at your option) any later version.
17 // This library is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 // Lesser General Public License for more details.
22 // You should have received a copy of the GNU Lesser General Public
23 // License along with this library; if not, write to the Free Software
24 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31 using System.Net.Sockets;
32 using System.Collections;
34 using System.Resources;
38 ///<summary> This class represents the base class for the state pattern design pattern
43 internal abstract class NpgsqlState
45 private readonly String CLASSNAME = "NpgsqlState";
46 protected ResourceManager resman = null;
48 public virtual void Open(NpgsqlConnection context)
50 throw new InvalidOperationException("Internal Error! " + this);
52 public virtual void Startup(NpgsqlConnection context)
54 throw new InvalidOperationException("Internal Error! " + this);
56 public virtual void Authenticate(NpgsqlConnection context, string password)
58 throw new InvalidOperationException("Internal Error! " + this);
60 public virtual void Query(NpgsqlConnection context, NpgsqlCommand command)
62 throw new InvalidOperationException("Internal Error! " + this);
64 public virtual void Ready( NpgsqlConnection context )
66 throw new InvalidOperationException("Internal Error! " + this);
68 public virtual void FunctionCall(NpgsqlConnection context, NpgsqlCommand command)
70 throw new InvalidOperationException("Internal Error! " + this);
72 public virtual void Parse(NpgsqlConnection context, NpgsqlParse parse)
74 throw new InvalidOperationException("Internal Error! " + this);
76 public virtual void Flush(NpgsqlConnection context)
78 throw new InvalidOperationException("Internal Error! " + this);
80 public virtual void Sync(NpgsqlConnection context)
82 throw new InvalidOperationException("Internal Error! " + this);
84 public virtual void Bind(NpgsqlConnection context, NpgsqlBind bind)
86 throw new InvalidOperationException("Internal Error! " + this);
88 public virtual void Execute(NpgsqlConnection context, NpgsqlExecute execute)
90 throw new InvalidOperationException("Internal Error! " + this);
95 resman = new ResourceManager(this.GetType());
98 public virtual void Close( NpgsqlConnection context )
100 /*NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Close");
101 if ( context.State == ConnectionState.Open )
103 Stream stream = context.Stream;
104 if ( stream.CanWrite )
106 stream.WriteByte((Byte)'X');
107 if (context.BackendProtocolVersion >= ProtocolVersion.Version3)
108 PGUtil.WriteInt32(stream, 4);
114 // The close logic is pretty messed up I think. Needs lots of work.
116 if (! context.Connector.Shared) {
117 if (context.Connector.Stream != null) {
119 context.Connector.Stream.Close();
124 //ChangeState( context, NpgsqlClosedState.Instance );
128 ///This method is used by the states to change the state of the context.
130 protected virtual void ChangeState(NpgsqlConnection context, NpgsqlState newState)
132 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ChangeState");
133 context.CurrentState = newState;
137 /// This method is responsible to handle all protocol messages sent from the backend.
138 /// It holds all the logic to do it.
139 /// To exchange data, it uses a Mediator object from which it reads/writes information
140 /// to handle backend requests.
143 protected virtual void ProcessBackendResponses( NpgsqlConnection context )
145 // reset any responses just before getting new ones
146 context.Mediator.ResetResponses();
149 switch (context.BackendProtocolVersion) {
150 case ProtocolVersion.Version2 :
151 ProcessBackendResponses_Ver_2(context);
154 case ProtocolVersion.Version3 :
155 ProcessBackendResponses_Ver_3(context);
160 // reset expectations right after getting new responses
161 context.Mediator.ResetExpectations();
165 protected virtual void ProcessBackendResponses_Ver_2( NpgsqlConnection context )
167 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ProcessBackendResponses");
169 BufferedStream stream = new BufferedStream(context.Stream);
170 NpgsqlMediator mediator = context.Mediator;
173 Byte[] inputBuffer = new Byte[ 4 ];
175 Boolean readyForQuery = false;
177 while (!readyForQuery)
179 // Check the first Byte of response.
180 switch ( stream.ReadByte() )
182 case NpgsqlMessageTypes_Ver_2.ErrorResponse :
185 NpgsqlError error = new NpgsqlError(context.BackendProtocolVersion);
186 error.ReadFromStream(stream, context.Encoding);
188 mediator.Errors.Add(error);
190 NpgsqlEventLog.LogMsg(resman, "Log_ErrorResponse", LogLevel.Debug, error.Message);
193 // Return imediately if it is in the startup state or connected state as
194 // there is no more messages to consume.
195 // Possible error in the NpgsqlStartupState:
197 // Possible error in the NpgsqlConnectedState:
198 // No pg_hba.conf configured.
200 if (! mediator.RequireReadyForQuery) {
207 case NpgsqlMessageTypes_Ver_2.AuthenticationRequest :
209 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "AuthenticationRequest");
212 Int32 authType = PGUtil.ReadInt32(stream, inputBuffer);
214 if ( authType == NpgsqlMessageTypes_Ver_2.AuthenticationOk )
216 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationOK", LogLevel.Debug);
221 if ( authType == NpgsqlMessageTypes_Ver_2.AuthenticationClearTextPassword )
223 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationClearTextRequest", LogLevel.Debug);
225 // Send the PasswordPacket.
227 ChangeState( context, NpgsqlStartupState.Instance );
228 context.Authenticate(context.Password);
234 if ( authType == NpgsqlMessageTypes_Ver_2.AuthenticationMD5Password )
236 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationMD5Request", LogLevel.Debug);
237 // Now do the "MD5-Thing"
238 // for this the Password has to be:
239 // 1. md5-hashed with the username as salt
240 // 2. md5-hashed again with the salt we get from the backend
243 MD5 md5 = MD5.Create();
247 byte[] passwd = context.Encoding.GetBytes(context.Password);
248 byte[] saltUserName = context.Encoding.GetBytes(context.UserName);
250 byte[] crypt_buf = new byte[passwd.Length + saltUserName.Length];
252 passwd.CopyTo(crypt_buf, 0);
253 saltUserName.CopyTo(crypt_buf, passwd.Length);
257 StringBuilder sb = new StringBuilder ();
258 byte[] hashResult = md5.ComputeHash(crypt_buf);
259 foreach (byte b in hashResult)
260 sb.Append (b.ToString ("x2"));
263 String prehash = sb.ToString();
265 byte[] prehashbytes = context.Encoding.GetBytes(prehash);
269 byte[] saltServer = new byte[4];
270 stream.Read(saltServer, 0, 4);
271 // Send the PasswordPacket.
272 ChangeState( context, NpgsqlStartupState.Instance );
277 crypt_buf = new byte[prehashbytes.Length + saltServer.Length];
278 prehashbytes.CopyTo(crypt_buf, 0);
279 saltServer.CopyTo(crypt_buf, prehashbytes.Length);
281 sb = new StringBuilder ("md5"); // This is needed as the backend expects md5 result starts with "md5"
282 hashResult = md5.ComputeHash(crypt_buf);
283 foreach (byte b in hashResult)
284 sb.Append (b.ToString ("x2"));
286 context.Authenticate(sb.ToString ());
291 // Only AuthenticationClearTextPassword and AuthenticationMD5Password supported for now.
292 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationOK", LogLevel.Debug);
293 mediator.Errors.Add(String.Format(resman.GetString("Exception_AuthenticationMethodNotSupported"), authType));
298 case NpgsqlMessageTypes_Ver_2.RowDescription:
299 // This is the RowDescription message.
300 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "RowDescription");
303 NpgsqlRowDescription rd = new NpgsqlRowDescription(context.BackendProtocolVersion);
304 rd.ReadFromStream(stream, context.Encoding);
306 // Initialize the array list which will contain the data from this rowdescription.
307 mediator.AddRowDescription(rd);
310 // Now wait for the AsciiRow messages.
313 case NpgsqlMessageTypes_Ver_2.AsciiRow:
314 // This is the AsciiRow message.
315 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "AsciiRow");
318 NpgsqlAsciiRow asciiRow = new NpgsqlAsciiRow(context.Mediator.LastRowDescription, context.OidToNameMapping, context.BackendProtocolVersion);
319 asciiRow.ReadFromStream(stream, context.Encoding);
321 // Add this row to the rows array.
322 mediator.AddAsciiRow(asciiRow);
325 // Now wait for CompletedResponse message.
328 case NpgsqlMessageTypes_Ver_2.BinaryRow:
329 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BinaryRow");
332 NpgsqlBinaryRow binaryRow = new NpgsqlBinaryRow(context.Mediator.LastRowDescription);
333 binaryRow.ReadFromStream(stream, context.Encoding);
335 mediator.AddBinaryRow(binaryRow);
340 case NpgsqlMessageTypes_Ver_2.ReadyForQuery :
342 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ReadyForQuery");
343 readyForQuery = true;
344 ChangeState( context, NpgsqlReadyState.Instance );
347 case NpgsqlMessageTypes_Ver_2.BackendKeyData :
349 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BackendKeyData");
350 // BackendKeyData message.
351 NpgsqlBackEndKeyData backend_keydata = new NpgsqlBackEndKeyData(context.BackendProtocolVersion);
352 backend_keydata.ReadFromStream(stream);
353 mediator.SetBackendKeydata(backend_keydata);
356 // Wait for ReadForQuery message
360 case NpgsqlMessageTypes_Ver_2.NoticeResponse :
363 NpgsqlError notice = new NpgsqlError(context.BackendProtocolVersion);
364 notice.ReadFromStream(stream, context.Encoding);
366 mediator.Notices.Add(notice);
368 NpgsqlEventLog.LogMsg(resman, "Log_NoticeResponse", LogLevel.Debug, notice.Message);
371 // Wait for ReadForQuery message
374 case NpgsqlMessageTypes_Ver_2.CompletedResponse :
375 // This is the CompletedResponse message.
376 // Get the string returned.
379 String result = PGUtil.ReadString(stream, context.Encoding);
381 NpgsqlEventLog.LogMsg(resman, "Log_CompletedResponse", LogLevel.Debug, result);
382 // Add result from the processing.
384 mediator.AddCompletedResponse(result);
386 // Now wait for ReadyForQuery message.
389 case NpgsqlMessageTypes_Ver_2.CursorResponse :
390 // This is the cursor response message.
391 // It is followed by a C NULL terminated string with the name of
392 // the cursor in a FETCH case or 'blank' otherwise.
393 // In this case it should be always 'blank'.
394 // [FIXME] Get another name for this function.
395 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "CursorResponse");
397 String cursorName = PGUtil.ReadString(stream, context.Encoding);
398 // Continue waiting for ReadyForQuery message.
401 case NpgsqlMessageTypes_Ver_2.EmptyQueryResponse :
402 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "EmptyQueryResponse");
403 PGUtil.ReadString(stream, context.Encoding);
406 case NpgsqlMessageTypes_Ver_2.NotificationResponse :
408 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "NotificationResponse");
410 Int32 PID = PGUtil.ReadInt32(stream, inputBuffer);
411 String notificationResponse = PGUtil.ReadString( stream, context.Encoding );
412 mediator.AddNotification(new NpgsqlNotificationEventArgs(PID, notificationResponse));
414 // Wait for ReadForQuery message
418 // This could mean a number of things
419 // We've gotten out of sync with the backend?
420 // We need to implement this type?
421 // Backend has gone insane?
423 // what exception should we really throw here?
424 throw new NotSupportedException("Backend sent unrecognized response type");
430 protected virtual void ProcessBackendResponses_Ver_3( NpgsqlConnection context )
432 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ProcessBackendResponses");
434 BufferedStream stream = new BufferedStream(context.Stream);
435 NpgsqlMediator mediator = context.Mediator;
437 // Often used buffers
438 Byte[] inputBuffer = new Byte[ 4 ];
441 Boolean readyForQuery = false;
443 while (!readyForQuery)
445 // Check the first Byte of response.
446 switch ( stream.ReadByte() )
448 case NpgsqlMessageTypes_Ver_3.ErrorResponse :
451 NpgsqlError error = new NpgsqlError(context.BackendProtocolVersion);
452 error.ReadFromStream(stream, context.Encoding);
454 mediator.Errors.Add(error);
456 NpgsqlEventLog.LogMsg(resman, "Log_ErrorResponse", LogLevel.Debug, error.Message);
459 // Return imediately if it is in the startup state or connected state as
460 // there is no more messages to consume.
461 // Possible error in the NpgsqlStartupState:
463 // Possible error in the NpgsqlConnectedState:
464 // No pg_hba.conf configured.
466 if (! mediator.RequireReadyForQuery) {
473 case NpgsqlMessageTypes_Ver_3.AuthenticationRequest :
475 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "AuthenticationRequest");
478 PGUtil.ReadInt32(stream, inputBuffer);
481 Int32 authType = PGUtil.ReadInt32(stream, inputBuffer);
483 if ( authType == NpgsqlMessageTypes_Ver_3.AuthenticationOk )
485 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationOK", LogLevel.Debug);
490 if ( authType == NpgsqlMessageTypes_Ver_3.AuthenticationClearTextPassword )
492 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationClearTextRequest", LogLevel.Debug);
494 // Send the PasswordPacket.
496 ChangeState( context, NpgsqlStartupState.Instance );
497 context.Authenticate(context.Password);
503 if ( authType == NpgsqlMessageTypes_Ver_3.AuthenticationMD5Password )
505 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationMD5Request", LogLevel.Debug);
506 // Now do the "MD5-Thing"
507 // for this the Password has to be:
508 // 1. md5-hashed with the username as salt
509 // 2. md5-hashed again with the salt we get from the backend
512 MD5 md5 = MD5.Create();
516 byte[] passwd = context.Encoding.GetBytes(context.Password);
517 byte[] saltUserName = context.Encoding.GetBytes(context.UserName);
519 byte[] crypt_buf = new byte[passwd.Length + saltUserName.Length];
521 passwd.CopyTo(crypt_buf, 0);
522 saltUserName.CopyTo(crypt_buf, passwd.Length);
526 StringBuilder sb = new StringBuilder ();
527 byte[] hashResult = md5.ComputeHash(crypt_buf);
528 foreach (byte b in hashResult)
529 sb.Append (b.ToString ("x2"));
532 String prehash = sb.ToString();
534 byte[] prehashbytes = context.Encoding.GetBytes(prehash);
538 stream.Read(inputBuffer, 0, 4);
539 // Send the PasswordPacket.
540 ChangeState( context, NpgsqlStartupState.Instance );
545 crypt_buf = new byte[prehashbytes.Length + 4];
546 prehashbytes.CopyTo(crypt_buf, 0);
547 inputBuffer.CopyTo(crypt_buf, prehashbytes.Length);
549 sb = new StringBuilder ("md5"); // This is needed as the backend expects md5 result starts with "md5"
550 hashResult = md5.ComputeHash(crypt_buf);
551 foreach (byte b in hashResult)
552 sb.Append (b.ToString ("x2"));
554 context.Authenticate(sb.ToString ());
559 // Only AuthenticationClearTextPassword and AuthenticationMD5Password supported for now.
560 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationOK", LogLevel.Debug);
561 mediator.Errors.Add(String.Format(resman.GetString("Exception_AuthenticationMethodNotSupported"), authType));
566 case NpgsqlMessageTypes_Ver_3.RowDescription:
567 // This is the RowDescription message.
568 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "RowDescription");
570 NpgsqlRowDescription rd = new NpgsqlRowDescription(context.BackendProtocolVersion);
571 rd.ReadFromStream(stream, context.Encoding);
573 mediator.AddRowDescription(rd);
576 // Now wait for the AsciiRow messages.
579 case NpgsqlMessageTypes_Ver_3.DataRow:
580 // This is the AsciiRow message.
581 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "DataRow");
583 NpgsqlAsciiRow asciiRow = new NpgsqlAsciiRow(context.Mediator.LastRowDescription, context.OidToNameMapping, context.BackendProtocolVersion);
584 asciiRow.ReadFromStream(stream, context.Encoding);
586 // Add this row to the rows array.
587 mediator.AddAsciiRow(asciiRow);
590 // Now wait for CompletedResponse message.
593 case NpgsqlMessageTypes_Ver_3.ReadyForQuery :
595 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ReadyForQuery");
597 // Possible status bytes returned:
598 // I = Idle (no transaction active).
599 // T = In transaction, ready for more.
600 // E = Error in transaction, queries will fail until transaction aborted.
601 // Just eat the status byte, we have no use for it at this time.
602 PGUtil.ReadInt32(stream, inputBuffer);
603 PGUtil.ReadString(stream, context.Encoding, 1);
605 readyForQuery = true;
606 ChangeState( context, NpgsqlReadyState.Instance );
610 case NpgsqlMessageTypes_Ver_3.BackendKeyData :
612 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BackendKeyData");
613 // BackendKeyData message.
614 NpgsqlBackEndKeyData backend_keydata = new NpgsqlBackEndKeyData(context.BackendProtocolVersion);
615 backend_keydata.ReadFromStream(stream);
616 mediator.SetBackendKeydata(backend_keydata);
619 // Wait for ReadForQuery message
622 case NpgsqlMessageTypes_Ver_3.NoticeResponse :
624 // Notices and errors are identical except that we
625 // just throw notices away completely ignored.
627 NpgsqlError notice = new NpgsqlError(context.BackendProtocolVersion);
628 notice.ReadFromStream(stream, context.Encoding);
630 mediator.Notices.Add(notice);
632 NpgsqlEventLog.LogMsg(resman, "Log_NoticeResponse", LogLevel.Debug, notice.Message);
635 // Wait for ReadForQuery message
638 case NpgsqlMessageTypes_Ver_3.CompletedResponse :
639 // This is the CompletedResponse message.
640 // Get the string returned.
642 PGUtil.ReadInt32(stream, inputBuffer);
643 Str = PGUtil.ReadString(stream, context.Encoding);
645 NpgsqlEventLog.LogMsg(resman, "Log_CompletedResponse", LogLevel.Debug, Str);
647 // Add result from the processing.
648 mediator.AddCompletedResponse(Str);
652 case NpgsqlMessageTypes_Ver_3.ParseComplete :
653 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ParseComplete");
654 // Just read up the message length.
655 PGUtil.ReadInt32(stream, inputBuffer);
656 readyForQuery = true;
659 case NpgsqlMessageTypes_Ver_3.BindComplete :
660 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BindComplete");
661 // Just read up the message length.
662 PGUtil.ReadInt32(stream, inputBuffer);
663 readyForQuery = true;
666 case NpgsqlMessageTypes_Ver_3.EmptyQueryResponse :
667 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "EmptyQueryResponse");
668 PGUtil.ReadInt32(stream, inputBuffer);
671 case NpgsqlMessageTypes_Ver_3.NotificationResponse :
672 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "NotificationResponse");
675 PGUtil.ReadInt32(stream, inputBuffer);
677 // Process ID sending notification
678 Int32 PID = PGUtil.ReadInt32(stream, inputBuffer);
679 // Notification string
680 String notificationResponse = PGUtil.ReadString( stream, context.Encoding );
681 // Additional info, currently not implemented by PG (empty string always), eat it
682 PGUtil.ReadString( stream, context.Encoding );
683 mediator.AddNotification(new NpgsqlNotificationEventArgs(PID, notificationResponse));
686 // Wait for ReadForQuery message
689 case NpgsqlMessageTypes_Ver_3.ParameterStatus :
690 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ParameterStatus");
691 NpgsqlParameterStatus parameterStatus = new NpgsqlParameterStatus();
692 parameterStatus.ReadFromStream(stream, context.Encoding);
694 NpgsqlEventLog.LogMsg(resman, "Log_ParameterStatus", LogLevel.Debug, parameterStatus.Parameter, parameterStatus.ParameterValue);
696 mediator.AddParameterStatus(parameterStatus.Parameter, parameterStatus);
698 if (parameterStatus.Parameter == "server_version") {
699 // Add this one under our own name so that if the parameter name
700 // changes in a future backend version, we can handle it here in the
701 // protocol handler and leave everybody else put of it.
702 mediator.AddParameterStatus("__npgsql_server_version", parameterStatus);
703 // context.ServerVersionString = parameterStatus.ParameterValue;
709 // This could mean a number of things
710 // We've gotten out of sync with the backend?
711 // We need to implement this type?
712 // Backend has gone insane?
714 // what exception should we really throw here?
715 throw new NotSupportedException("Backend sent unrecognized response type");