* FileSystemInfo.cs: corrected COM visibility of UTC properties
[mono.git] / mcs / class / Npgsql / Npgsql / NpgsqlState.cs
1 // created on 6/14/2002 at 7:56 PM
2
3 // Npgsql.NpgsqlState.cs
4 //
5 // Author:
6 //      Dave Joyner <d4ljoyn@yahoo.com>
7 //
8 //      Copyright (C) 2002 The Npgsql Development Team
9 //      npgsql-general@gborg.postgresql.org
10 //      http://gborg.postgresql.org/project/npgsql/projdisplay.php
11 //
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.
16 //
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.
21 //
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
25
26
27 using System;
28 using System.Data;
29 using System.IO;
30 using System.Net;
31 using System.Net.Sockets;
32 using System.Collections;
33 using System.Text;
34 using System.Resources;
35
36 namespace Npgsql
37 {
38     ///<summary> This class represents the base class for the state pattern design pattern
39     /// implementation.
40     /// </summary>
41     ///
42
43     internal abstract class NpgsqlState
44     {
45         private readonly String CLASSNAME = "NpgsqlState";
46         protected ResourceManager resman = null;
47
48         public virtual void Open(NpgsqlConnection context)
49         {
50             throw new InvalidOperationException("Internal Error! " + this);
51         }
52         public virtual void Startup(NpgsqlConnection context)
53         {
54             throw new InvalidOperationException("Internal Error! " + this);
55         }
56         public virtual void Authenticate(NpgsqlConnection context, string password)
57         {
58             throw new InvalidOperationException("Internal Error! " + this);
59         }
60         public virtual void Query(NpgsqlConnection context, NpgsqlCommand command)
61         {
62             throw new InvalidOperationException("Internal Error! " + this);
63         }
64         public virtual void Ready( NpgsqlConnection context )
65         {
66             throw new InvalidOperationException("Internal Error! " + this);
67         }
68         public virtual void FunctionCall(NpgsqlConnection context, NpgsqlCommand command)
69         {
70             throw new InvalidOperationException("Internal Error! " + this);
71         }
72         public virtual void Parse(NpgsqlConnection context, NpgsqlParse parse)
73         {
74             throw new InvalidOperationException("Internal Error! " + this);
75         }
76         public virtual void Flush(NpgsqlConnection context)
77         {
78             throw new InvalidOperationException("Internal Error! " + this);
79         }
80         public virtual void Sync(NpgsqlConnection context)
81         {
82             throw new InvalidOperationException("Internal Error! " + this);
83         }
84         public virtual void Bind(NpgsqlConnection context, NpgsqlBind bind)
85         {
86             throw new InvalidOperationException("Internal Error! " + this);
87         }
88         public virtual void Execute(NpgsqlConnection context, NpgsqlExecute execute)
89         {
90             throw new InvalidOperationException("Internal Error! " + this);
91         }
92
93         public NpgsqlState()
94         {
95             resman = new ResourceManager(this.GetType());
96         }
97
98         public virtual void Close( NpgsqlConnection context )
99         {
100             /*NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Close");
101             if ( context.State == ConnectionState.Open )
102             {
103                 Stream stream = context.Stream;
104                 if ( stream.CanWrite )
105                 {
106                     stream.WriteByte((Byte)'X');
107                     if (context.BackendProtocolVersion >= ProtocolVersion.Version3)
108                         PGUtil.WriteInt32(stream, 4);
109                     stream.Flush();
110                 }
111             }*/
112             
113             context.Connector.InUse = false;
114             context.Connector = null;
115             //ChangeState( context, NpgsqlClosedState.Instance );
116         }
117
118         ///<summary> This method is used by the states to change the state of the context.
119         /// </summary>
120         ///
121
122         protected virtual void ChangeState(NpgsqlConnection context, NpgsqlState newState)
123         {
124             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ChangeState");
125             context.CurrentState = newState;
126         }
127
128         ///<summary>
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.
133         /// </summary>
134         ///
135
136         protected virtual void ProcessBackendResponses( NpgsqlConnection context )
137         {
138             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ProcessBackendResponses");
139
140             BufferedStream      stream = new BufferedStream(context.Stream);
141             Int32       authType;
142             Boolean readyForQuery = false;
143
144             NpgsqlMediator mediator = context.Mediator;
145
146             // Reset the mediator.
147             mediator.Reset();
148
149             Int16 rowDescNumFields = 0;
150             NpgsqlRowDescription rd = null;
151
152             Byte[] inputBuffer = new Byte[ 500 ];
153
154
155             while (!readyForQuery)
156             {
157                 // Check the first Byte of response.
158                 switch ( stream.ReadByte() )
159                 {
160                 case NpgsqlMessageTypes.ErrorResponse :
161
162                     NpgsqlError error = new NpgsqlError(context.BackendProtocolVersion);
163                     error.ReadFromStream(stream, context.Encoding);
164
165                     //mediator.Errors.Add(errorMessage);
166                     mediator.Errors.Add(error.Message);
167
168                     NpgsqlEventLog.LogMsg(resman, "Log_ErrorResponse", LogLevel.Debug, error.Message);
169
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:
173                     //          Invalid password.
174                     // Possible error in the NpgsqlConnectedState:
175                     //          No pg_hba.conf configured.
176
177                     if ((context.CurrentState == NpgsqlStartupState.Instance) ||
178                             (context.CurrentState == NpgsqlConnectedState.Instance))
179                         return;
180
181                     break;
182
183
184                 case NpgsqlMessageTypes.AuthenticationRequest :
185
186                     NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "AuthenticationRequest");
187
188                     stream.Read(inputBuffer, 0, 4);
189
190                     if (context.BackendProtocolVersion >= ProtocolVersion.Version3)
191                         stream.Read(inputBuffer, 0, 4);
192
193                     authType = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(inputBuffer, 0));
194
195                     if ( authType == NpgsqlMessageTypes.AuthenticationOk )
196                     {
197                         NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationOK", LogLevel.Debug);
198
199                         break;
200                     }
201
202                     if ( authType == NpgsqlMessageTypes.AuthenticationClearTextPassword )
203                     {
204                         NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationClearTextRequest", LogLevel.Debug);
205
206                         // Send the PasswordPacket.
207
208                         ChangeState( context, NpgsqlStartupState.Instance );
209                         context.Authenticate(context.ServerPassword);
210
211                         break;
212                     }
213
214
215                     if ( authType == NpgsqlMessageTypes.AuthenticationMD5Password )
216                     {
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
222
223
224                         MD5 md5 = MD5.Create();
225
226
227                         // 1.
228                         byte[] passwd = context.Encoding.GetBytes(context.ServerPassword);
229                         byte[] saltUserName = context.Encoding.GetBytes(context.UserName);
230
231                         byte[] crypt_buf = new byte[passwd.Length + saltUserName.Length];
232
233                         passwd.CopyTo(crypt_buf, 0);
234                         saltUserName.CopyTo(crypt_buf, passwd.Length);
235
236
237
238                         StringBuilder sb = new StringBuilder ();
239                         byte[] hashResult = md5.ComputeHash(crypt_buf);
240                         foreach (byte b in hashResult)
241                         sb.Append (b.ToString ("x2"));
242
243
244                         String prehash = sb.ToString();
245
246                         byte[] prehashbytes = context.Encoding.GetBytes(prehash);
247
248
249
250                         byte[] saltServer = new byte[4];
251                         stream.Read(saltServer, 0, 4);
252                         // Send the PasswordPacket.
253                         ChangeState( context, NpgsqlStartupState.Instance );
254
255
256                         // 2.
257
258                         crypt_buf = new byte[prehashbytes.Length + saltServer.Length];
259                         prehashbytes.CopyTo(crypt_buf, 0);
260                         saltServer.CopyTo(crypt_buf, prehashbytes.Length);
261
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"));
266
267                         context.Authenticate(sb.ToString ());
268
269                         break;
270                     }
271
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));
275                     return;
276
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);
282
283                     // Initialize the array list which will contain the data from this rowdescription.
284                     //rows = new ArrayList();
285
286                     rowDescNumFields = rd.NumFields;
287                     mediator.AddRowDescription(rd);
288
289
290                     // Now wait for the AsciiRow messages.
291                     break;
292
293                 case NpgsqlMessageTypes.AsciiRow:
294
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);
299
300
301                     // Add this row to the rows array.
302                     //rows.Add(ascii_row);
303                     mediator.AddAsciiRow(asciiRow);
304
305                     // Now wait for CompletedResponse message.
306                     break;
307
308                 case NpgsqlMessageTypes.BinaryRow:
309
310                     NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BinaryRow");
311                     NpgsqlBinaryRow binaryRow = new NpgsqlBinaryRow(rd);
312                     binaryRow.ReadFromStream(stream, context.Encoding);
313
314                     mediator.AddBinaryRow(binaryRow);
315
316                     break;
317
318                 case NpgsqlMessageTypes.ReadyForQuery :
319
320                     NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ReadyForQuery");
321                     readyForQuery = true;
322                     ChangeState( context, NpgsqlReadyState.Instance );
323                     break;
324
325                 case NpgsqlMessageTypes.BackendKeyData :
326
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);
332
333
334                     // Wait for ReadForQuery message
335                     break;
336                     ;
337
338                 case NpgsqlMessageTypes.NoticeResponse :
339
340                     NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "NoticeResponse");
341                     String noticeResponse = PGUtil.ReadString( stream, context.Encoding );
342
343                     // Wait for ReadForQuery message
344                     break;
345
346                 case NpgsqlMessageTypes.CompletedResponse :
347                     // This is the CompletedResponse message.
348                     // Get the string returned.
349
350
351                     if (context.BackendProtocolVersion >= ProtocolVersion.Version3)
352                         PGUtil.ReadInt32(stream, new Byte[4]);
353
354                     String result = PGUtil.ReadString(stream, context.Encoding);
355
356                     NpgsqlEventLog.LogMsg(resman, "Log_CompletedResponse", LogLevel.Debug, result);
357                     // Add result from the processing.
358
359                     mediator.AddCompletedResponse(result);
360
361                     // Now wait for ReadyForQuery message.
362                     break;
363
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");
371
372                     String cursorName = PGUtil.ReadString(stream, context.Encoding);
373                     // Continue waiting for ReadyForQuery message.
374                     break;
375
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;
381                     break;
382
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;
388                     break;
389
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);
397                     break;
398
399                 case NpgsqlMessageTypes.NotificationResponse  :
400
401                     NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "NotificationResponse");
402
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));
408
409                     // Wait for ReadForQuery message
410                     break;
411
412                 case NpgsqlMessageTypes.ParameterStatus :
413                     NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ParameterStatus");
414                     NpgsqlParameterStatus parameterStatus = new NpgsqlParameterStatus();
415                     parameterStatus.ReadFromStream(stream, context.Encoding);
416
417                     NpgsqlEventLog.LogMsg(resman, "Log_ParameterStatus", LogLevel.Debug, parameterStatus.Parameter, parameterStatus.ParameterValue);
418                     if (parameterStatus.Parameter == "server_version")
419                         context.ServerVersion = parameterStatus.ParameterValue;
420                     break;
421                 }
422             }
423
424         }
425     }
426
427
428
429 }