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);
113 context.Connector.InUse = false;
114 context.Connector = null;
115 //ChangeState( context, NpgsqlClosedState.Instance );
118 ///<summary> This method is used by the states to change the state of the context.
122 protected virtual void ChangeState(NpgsqlConnection context, NpgsqlState newState)
124 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ChangeState");
125 context.CurrentState = newState;
129 /// This method is responsible to handle all protocol messages sent from the backend.
130 /// It holds all the logic to do it.
131 /// To exchange data, it uses a Mediator object from which it reads/writes information
132 /// to handle backend requests.
136 protected virtual void ProcessBackendResponses( NpgsqlConnection context )
138 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ProcessBackendResponses");
140 BufferedStream stream = new BufferedStream(context.Stream);
142 Boolean readyForQuery = false;
144 NpgsqlMediator mediator = context.Mediator;
146 // Reset the mediator.
149 Int16 rowDescNumFields = 0;
150 NpgsqlRowDescription rd = null;
152 Byte[] inputBuffer = new Byte[ 500 ];
155 while (!readyForQuery)
157 // Check the first Byte of response.
158 switch ( stream.ReadByte() )
160 case NpgsqlMessageTypes.ErrorResponse :
162 NpgsqlError error = new NpgsqlError(context.BackendProtocolVersion);
163 error.ReadFromStream(stream, context.Encoding);
165 //mediator.Errors.Add(errorMessage);
166 mediator.Errors.Add(error.Message);
168 NpgsqlEventLog.LogMsg(resman, "Log_ErrorResponse", LogLevel.Debug, error.Message);
170 // Return imediately if it is in the startup state or connected state as
171 // there is no more messages to consume.
172 // Possible error in the NpgsqlStartupState:
174 // Possible error in the NpgsqlConnectedState:
175 // No pg_hba.conf configured.
177 if ((context.CurrentState == NpgsqlStartupState.Instance) ||
178 (context.CurrentState == NpgsqlConnectedState.Instance))
184 case NpgsqlMessageTypes.AuthenticationRequest :
186 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "AuthenticationRequest");
188 stream.Read(inputBuffer, 0, 4);
190 if (context.BackendProtocolVersion >= ProtocolVersion.Version3)
191 stream.Read(inputBuffer, 0, 4);
193 authType = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(inputBuffer, 0));
195 if ( authType == NpgsqlMessageTypes.AuthenticationOk )
197 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationOK", LogLevel.Debug);
202 if ( authType == NpgsqlMessageTypes.AuthenticationClearTextPassword )
204 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationClearTextRequest", LogLevel.Debug);
206 // Send the PasswordPacket.
208 ChangeState( context, NpgsqlStartupState.Instance );
209 context.Authenticate(context.ServerPassword);
215 if ( authType == NpgsqlMessageTypes.AuthenticationMD5Password )
217 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationMD5Request", LogLevel.Debug);
218 // Now do the "MD5-Thing"
219 // for this the Password has to be:
220 // 1. md5-hashed with the username as salt
221 // 2. md5-hashed again with the salt we get from the backend
224 MD5 md5 = MD5.Create();
228 byte[] passwd = context.Encoding.GetBytes(context.ServerPassword);
229 byte[] saltUserName = context.Encoding.GetBytes(context.UserName);
231 byte[] crypt_buf = new byte[passwd.Length + saltUserName.Length];
233 passwd.CopyTo(crypt_buf, 0);
234 saltUserName.CopyTo(crypt_buf, passwd.Length);
238 StringBuilder sb = new StringBuilder ();
239 byte[] hashResult = md5.ComputeHash(crypt_buf);
240 foreach (byte b in hashResult)
241 sb.Append (b.ToString ("x2"));
244 String prehash = sb.ToString();
246 byte[] prehashbytes = context.Encoding.GetBytes(prehash);
250 byte[] saltServer = new byte[4];
251 stream.Read(saltServer, 0, 4);
252 // Send the PasswordPacket.
253 ChangeState( context, NpgsqlStartupState.Instance );
258 crypt_buf = new byte[prehashbytes.Length + saltServer.Length];
259 prehashbytes.CopyTo(crypt_buf, 0);
260 saltServer.CopyTo(crypt_buf, prehashbytes.Length);
262 sb = new StringBuilder ("md5"); // This is needed as the backend expects md5 result starts with "md5"
263 hashResult = md5.ComputeHash(crypt_buf);
264 foreach (byte b in hashResult)
265 sb.Append (b.ToString ("x2"));
267 context.Authenticate(sb.ToString ());
272 // Only AuthenticationClearTextPassword and AuthenticationMD5Password supported for now.
273 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationOK", LogLevel.Debug);
274 mediator.Errors.Add(String.Format(resman.GetString("Exception_AuthenticationMethodNotSupported"), authType));
277 case NpgsqlMessageTypes.RowDescription:
278 // This is the RowDescription message.
279 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "RowDescription");
280 rd = new NpgsqlRowDescription(context.BackendProtocolVersion);
281 rd.ReadFromStream(stream, context.Encoding);
283 // Initialize the array list which will contain the data from this rowdescription.
284 //rows = new ArrayList();
286 rowDescNumFields = rd.NumFields;
287 mediator.AddRowDescription(rd);
290 // Now wait for the AsciiRow messages.
293 case NpgsqlMessageTypes.AsciiRow:
295 // This is the AsciiRow message.
296 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "AsciiRow");
297 NpgsqlAsciiRow asciiRow = new NpgsqlAsciiRow(rd, context.OidToNameMapping, context.BackendProtocolVersion);
298 asciiRow.ReadFromStream(stream, context.Encoding);
301 // Add this row to the rows array.
302 //rows.Add(ascii_row);
303 mediator.AddAsciiRow(asciiRow);
305 // Now wait for CompletedResponse message.
308 case NpgsqlMessageTypes.BinaryRow:
310 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BinaryRow");
311 NpgsqlBinaryRow binaryRow = new NpgsqlBinaryRow(rd);
312 binaryRow.ReadFromStream(stream, context.Encoding);
314 mediator.AddBinaryRow(binaryRow);
318 case NpgsqlMessageTypes.ReadyForQuery :
320 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ReadyForQuery");
321 readyForQuery = true;
322 ChangeState( context, NpgsqlReadyState.Instance );
325 case NpgsqlMessageTypes.BackendKeyData :
327 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BackendKeyData");
328 // BackendKeyData message.
329 NpgsqlBackEndKeyData backend_keydata = new NpgsqlBackEndKeyData(context.BackendProtocolVersion);
330 backend_keydata.ReadFromStream(stream);
331 mediator.AddBackendKeydata(backend_keydata);
334 // Wait for ReadForQuery message
338 case NpgsqlMessageTypes.NoticeResponse :
340 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "NoticeResponse");
341 String noticeResponse = PGUtil.ReadString( stream, context.Encoding );
343 // Wait for ReadForQuery message
346 case NpgsqlMessageTypes.CompletedResponse :
347 // This is the CompletedResponse message.
348 // Get the string returned.
351 if (context.BackendProtocolVersion >= ProtocolVersion.Version3)
352 PGUtil.ReadInt32(stream, new Byte[4]);
354 String result = PGUtil.ReadString(stream, context.Encoding);
356 NpgsqlEventLog.LogMsg(resman, "Log_CompletedResponse", LogLevel.Debug, result);
357 // Add result from the processing.
359 mediator.AddCompletedResponse(result);
361 // Now wait for ReadyForQuery message.
364 case NpgsqlMessageTypes.CursorResponse :
365 // This is the cursor response message.
366 // It is followed by a C NULL terminated string with the name of
367 // the cursor in a FETCH case or 'blank' otherwise.
368 // In this case it should be always 'blank'.
369 // [FIXME] Get another name for this function.
370 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "CursorResponse");
372 String cursorName = PGUtil.ReadString(stream, context.Encoding);
373 // Continue waiting for ReadyForQuery message.
376 case NpgsqlMessageTypes.ParseComplete :
377 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ParseComplete");
378 // Just read up the message length.
379 PGUtil.ReadInt32(stream, new Byte[4]);
380 readyForQuery = true;
383 case NpgsqlMessageTypes.BindComplete :
384 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BindComplete");
385 // Just read up the message length.
386 PGUtil.ReadInt32(stream, new Byte[4]);
387 readyForQuery = true;
390 case NpgsqlMessageTypes.EmptyQueryResponse :
391 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "EmptyQueryResponse");
392 // This is the EmptyQueryResponse.
393 // [FIXME] Just ignore it this way?
394 // networkStream.Read(inputBuffer, 0, 1);
395 //GetStringFromNetStream(networkStream);
396 PGUtil.ReadString(stream, context.Encoding);
399 case NpgsqlMessageTypes.NotificationResponse :
401 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "NotificationResponse");
403 Byte[] input_buffer = new Byte[4];
404 stream.Read(input_buffer, 0, 4);
405 Int32 PID = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(input_buffer, 0));
406 String notificationResponse = PGUtil.ReadString( stream, context.Encoding );
407 mediator.AddNotification(new NpgsqlNotificationEventArgs(PID, notificationResponse));
409 // Wait for ReadForQuery message
412 case NpgsqlMessageTypes.ParameterStatus :
413 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ParameterStatus");
414 NpgsqlParameterStatus parameterStatus = new NpgsqlParameterStatus();
415 parameterStatus.ReadFromStream(stream, context.Encoding);
417 NpgsqlEventLog.LogMsg(resman, "Log_ParameterStatus", LogLevel.Debug, parameterStatus.Parameter, parameterStatus.ParameterValue);
418 if (parameterStatus.Parameter == "server_version")
419 context.ServerVersion = parameterStatus.ParameterValue;