(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / Npgsql / Npgsql / NpgsqlState.cs
blob3a31cc469ac5fe0d86855fe0478113bb5cbee998
1 // created on 6/14/2002 at 7:56 PM
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
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
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;
36 namespace Npgsql
38 ///<summary> This class represents the base class for the state pattern design pattern
39 /// implementation.
40 /// </summary>
41 ///
43 internal abstract class NpgsqlState
45 private readonly String CLASSNAME = "NpgsqlState";
46 protected static ResourceManager resman = new ResourceManager(typeof(NpgsqlState));
48 public virtual void Open(NpgsqlConnector context)
50 throw new InvalidOperationException("Internal Error! " + this);
52 public virtual void Startup(NpgsqlConnector context)
54 throw new InvalidOperationException("Internal Error! " + this);
56 public virtual void Authenticate(NpgsqlConnector context, string password)
58 throw new InvalidOperationException("Internal Error! " + this);
60 public virtual void Query(NpgsqlConnector context, NpgsqlCommand command)
62 throw new InvalidOperationException("Internal Error! " + this);
64 public virtual void Ready( NpgsqlConnector context )
66 throw new InvalidOperationException("Internal Error! " + this);
68 public virtual void FunctionCall(NpgsqlConnector context, NpgsqlCommand command)
70 throw new InvalidOperationException("Internal Error! " + this);
72 public virtual void Parse(NpgsqlConnector context, NpgsqlParse parse)
74 throw new InvalidOperationException("Internal Error! " + this);
76 public virtual void Flush(NpgsqlConnector context)
78 throw new InvalidOperationException("Internal Error! " + this);
80 public virtual void Sync(NpgsqlConnector context)
82 throw new InvalidOperationException("Internal Error! " + this);
84 public virtual void Bind(NpgsqlConnector context, NpgsqlBind bind)
86 throw new InvalidOperationException("Internal Error! " + this);
88 public virtual void Execute(NpgsqlConnector context, NpgsqlExecute execute)
90 throw new InvalidOperationException("Internal Error! " + this);
93 public virtual void Close( NpgsqlConnector context )
95 if (this != NpgsqlClosedState.Instance)
97 try
99 context.Stream.Close();
101 catch {}
103 context.Stream = null;
104 ChangeState( context, NpgsqlClosedState.Instance )
109 ///<summary>
110 ///This method is used by the states to change the state of the context.
111 /// </summary>
112 protected virtual void ChangeState(NpgsqlConnector context, NpgsqlState newState)
114 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ChangeState");
115 context.CurrentState = newState;
118 ///<summary>
119 /// This method is responsible to handle all protocol messages sent from the backend.
120 /// It holds all the logic to do it.
121 /// To exchange data, it uses a Mediator object from which it reads/writes information
122 /// to handle backend requests.
123 /// </summary>
125 protected virtual void ProcessBackendResponses( NpgsqlConnector context )
130 switch (context.BackendProtocolVersion)
132 case ProtocolVersion.Version2 :
133 ProcessBackendResponses_Ver_2(context);
134 break;
136 case ProtocolVersion.Version3 :
137 ProcessBackendResponses_Ver_3(context);
138 break;
142 finally
144 // reset expectations right after getting new responses
145 context.Mediator.ResetExpectations();
149 protected virtual void ProcessBackendResponses_Ver_2( NpgsqlConnector context )
151 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ProcessBackendResponses");
153 BufferedStream stream = new BufferedStream(context.Stream);
154 NpgsqlMediator mediator = context.Mediator;
156 // Often used buffer
157 Byte[] inputBuffer = new Byte[ 4 ];
159 Boolean readyForQuery = false;
161 while (!readyForQuery)
163 // Check the first Byte of response.
164 switch ( stream.ReadByte() )
166 case NpgsqlMessageTypes_Ver_2.ErrorResponse :
169 NpgsqlError error = new NpgsqlError(context.BackendProtocolVersion);
170 error.ReadFromStream(stream, context.Encoding);
172 mediator.Errors.Add(error);
174 NpgsqlEventLog.LogMsg(resman, "Log_ErrorResponse", LogLevel.Debug, error.Message);
177 // Return imediately if it is in the startup state or connected state as
178 // there is no more messages to consume.
179 // Possible error in the NpgsqlStartupState:
180 // Invalid password.
181 // Possible error in the NpgsqlConnectedState:
182 // No pg_hba.conf configured.
184 if (! mediator.RequireReadyForQuery)
186 return;
189 break;
192 case NpgsqlMessageTypes_Ver_2.AuthenticationRequest :
194 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "AuthenticationRequest");
197 Int32 authType = PGUtil.ReadInt32(stream, inputBuffer);
199 if ( authType == NpgsqlMessageTypes_Ver_2.AuthenticationOk )
201 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationOK", LogLevel.Debug);
203 break;
206 if ( authType == NpgsqlMessageTypes_Ver_2.AuthenticationClearTextPassword )
208 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationClearTextRequest", LogLevel.Debug);
210 // Send the PasswordPacket.
212 ChangeState( context, NpgsqlStartupState.Instance );
213 context.Authenticate(context.Password);
215 break;
219 if ( authType == NpgsqlMessageTypes_Ver_2.AuthenticationMD5Password )
221 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationMD5Request", LogLevel.Debug);
222 // Now do the "MD5-Thing"
223 // for this the Password has to be:
224 // 1. md5-hashed with the username as salt
225 // 2. md5-hashed again with the salt we get from the backend
228 MD5 md5 = MD5.Create();
231 // 1.
232 byte[] passwd = context.Encoding.GetBytes(context.Password);
233 byte[] saltUserName = context.Encoding.GetBytes(context.UserName);
235 byte[] crypt_buf = new byte[passwd.Length + saltUserName.Length];
237 passwd.CopyTo(crypt_buf, 0);
238 saltUserName.CopyTo(crypt_buf, passwd.Length);
242 StringBuilder sb = new StringBuilder ();
243 byte[] hashResult = md5.ComputeHash(crypt_buf);
244 foreach (byte b in hashResult)
245 sb.Append (b.ToString ("x2"));
248 String prehash = sb.ToString();
250 byte[] prehashbytes = context.Encoding.GetBytes(prehash);
254 byte[] saltServer = new byte[4];
255 stream.Read(saltServer, 0, 4);
256 // Send the PasswordPacket.
257 ChangeState( context, NpgsqlStartupState.Instance );
260 // 2.
262 crypt_buf = new byte[prehashbytes.Length + saltServer.Length];
263 prehashbytes.CopyTo(crypt_buf, 0);
264 saltServer.CopyTo(crypt_buf, prehashbytes.Length);
266 sb = new StringBuilder ("md5"); // This is needed as the backend expects md5 result starts with "md5"
267 hashResult = md5.ComputeHash(crypt_buf);
268 foreach (byte b in hashResult)
269 sb.Append (b.ToString ("x2"));
271 context.Authenticate(sb.ToString ());
273 break;
276 // Only AuthenticationClearTextPassword and AuthenticationMD5Password supported for now.
277 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationOK", LogLevel.Debug);
278 mediator.Errors.Add(String.Format(resman.GetString("Exception_AuthenticationMethodNotSupported"), authType));
281 return;
283 case NpgsqlMessageTypes_Ver_2.RowDescription:
284 // This is the RowDescription message.
285 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "RowDescription");
288 NpgsqlRowDescription rd = new NpgsqlRowDescription(context.BackendProtocolVersion);
289 rd.ReadFromStream(stream, context.Encoding, context.OidToNameMapping);
291 // Initialize the array list which will contain the data from this rowdescription.
292 mediator.AddRowDescription(rd);
295 // Now wait for the AsciiRow messages.
296 break;
298 case NpgsqlMessageTypes_Ver_2.AsciiRow:
299 // This is the AsciiRow message.
300 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "AsciiRow");
303 NpgsqlAsciiRow asciiRow = new NpgsqlAsciiRow(context.Mediator.LastRowDescription, context.BackendProtocolVersion);
304 asciiRow.ReadFromStream(stream, context.Encoding);
306 // Add this row to the rows array.
307 mediator.AddAsciiRow(asciiRow);
310 // Now wait for CompletedResponse message.
311 break;
313 case NpgsqlMessageTypes_Ver_2.BinaryRow:
314 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BinaryRow");
317 NpgsqlBinaryRow binaryRow = new NpgsqlBinaryRow(context.Mediator.LastRowDescription);
318 binaryRow.ReadFromStream(stream, context.Encoding);
320 mediator.AddBinaryRow(binaryRow);
323 break;
325 case NpgsqlMessageTypes_Ver_2.ReadyForQuery :
327 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ReadyForQuery");
328 readyForQuery = true;
329 ChangeState( context, NpgsqlReadyState.Instance );
330 break;
332 case NpgsqlMessageTypes_Ver_2.BackendKeyData :
334 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BackendKeyData");
335 // BackendKeyData message.
336 NpgsqlBackEndKeyData backend_keydata = new NpgsqlBackEndKeyData(context.BackendProtocolVersion);
337 backend_keydata.ReadFromStream(stream);
338 mediator.SetBackendKeydata(backend_keydata);
341 // Wait for ReadForQuery message
342 break;
345 case NpgsqlMessageTypes_Ver_2.NoticeResponse :
348 NpgsqlError notice = new NpgsqlError(context.BackendProtocolVersion);
349 notice.ReadFromStream(stream, context.Encoding);
351 mediator.Notices.Add(notice);
353 NpgsqlEventLog.LogMsg(resman, "Log_NoticeResponse", LogLevel.Debug, notice.Message);
356 // Wait for ReadForQuery message
357 break;
359 case NpgsqlMessageTypes_Ver_2.CompletedResponse :
360 // This is the CompletedResponse message.
361 // Get the string returned.
364 String result = PGUtil.ReadString(stream, context.Encoding);
366 NpgsqlEventLog.LogMsg(resman, "Log_CompletedResponse", LogLevel.Debug, result);
367 // Add result from the processing.
369 mediator.AddCompletedResponse(result);
371 // Now wait for ReadyForQuery message.
372 break;
374 case NpgsqlMessageTypes_Ver_2.CursorResponse :
375 // This is the cursor response message.
376 // It is followed by a C NULL terminated string with the name of
377 // the cursor in a FETCH case or 'blank' otherwise.
378 // In this case it should be always 'blank'.
379 // [FIXME] Get another name for this function.
380 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "CursorResponse");
382 String cursorName = PGUtil.ReadString(stream, context.Encoding);
383 // Continue waiting for ReadyForQuery message.
384 break;
386 case NpgsqlMessageTypes_Ver_2.EmptyQueryResponse :
387 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "EmptyQueryResponse");
388 PGUtil.ReadString(stream, context.Encoding);
389 break;
391 case NpgsqlMessageTypes_Ver_2.NotificationResponse :
393 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "NotificationResponse");
395 Int32 PID = PGUtil.ReadInt32(stream, inputBuffer);
396 String notificationResponse = PGUtil.ReadString( stream, context.Encoding );
397 mediator.AddNotification(new NpgsqlNotificationEventArgs(PID, notificationResponse));
399 // Wait for ReadForQuery message
400 break;
402 default :
403 // This could mean a number of things
404 // We've gotten out of sync with the backend?
405 // We need to implement this type?
406 // Backend has gone insane?
407 // FIXME
408 // what exception should we really throw here?
409 throw new NotSupportedException("Backend sent unrecognized response type");
415 protected virtual void ProcessBackendResponses_Ver_3( NpgsqlConnector context )
417 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ProcessBackendResponses");
419 BufferedStream stream = new BufferedStream(context.Stream);
420 NpgsqlMediator mediator = context.Mediator;
422 // Often used buffers
423 Byte[] inputBuffer = new Byte[ 4 ];
424 String Str;
426 Boolean readyForQuery = false;
428 while (!readyForQuery)
430 // Check the first Byte of response.
431 Int32 message = stream.ReadByte();
432 switch ( message )
434 case NpgsqlMessageTypes_Ver_3.ErrorResponse :
437 NpgsqlError error = new NpgsqlError(context.BackendProtocolVersion);
438 error.ReadFromStream(stream, context.Encoding);
440 mediator.Errors.Add(error);
442 NpgsqlEventLog.LogMsg(resman, "Log_ErrorResponse", LogLevel.Debug, error.Message);
445 // Return imediately if it is in the startup state or connected state as
446 // there is no more messages to consume.
447 // Possible error in the NpgsqlStartupState:
448 // Invalid password.
449 // Possible error in the NpgsqlConnectedState:
450 // No pg_hba.conf configured.
452 if (! mediator.RequireReadyForQuery)
454 return;
457 break;
460 case NpgsqlMessageTypes_Ver_3.AuthenticationRequest :
462 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "AuthenticationRequest");
464 // Eat length
465 PGUtil.ReadInt32(stream, inputBuffer);
468 Int32 authType = PGUtil.ReadInt32(stream, inputBuffer);
470 if ( authType == NpgsqlMessageTypes_Ver_3.AuthenticationOk )
472 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationOK", LogLevel.Debug);
474 break;
477 if ( authType == NpgsqlMessageTypes_Ver_3.AuthenticationClearTextPassword )
479 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationClearTextRequest", LogLevel.Debug);
481 // Send the PasswordPacket.
483 ChangeState( context, NpgsqlStartupState.Instance );
484 context.Authenticate(context.Password);
486 break;
490 if ( authType == NpgsqlMessageTypes_Ver_3.AuthenticationMD5Password )
492 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationMD5Request", LogLevel.Debug);
493 // Now do the "MD5-Thing"
494 // for this the Password has to be:
495 // 1. md5-hashed with the username as salt
496 // 2. md5-hashed again with the salt we get from the backend
499 MD5 md5 = MD5.Create();
502 // 1.
503 byte[] passwd = context.Encoding.GetBytes(context.Password);
504 byte[] saltUserName = context.Encoding.GetBytes(context.UserName);
506 byte[] crypt_buf = new byte[passwd.Length + saltUserName.Length];
508 passwd.CopyTo(crypt_buf, 0);
509 saltUserName.CopyTo(crypt_buf, passwd.Length);
513 StringBuilder sb = new StringBuilder ();
514 byte[] hashResult = md5.ComputeHash(crypt_buf);
515 foreach (byte b in hashResult)
516 sb.Append (b.ToString ("x2"));
519 String prehash = sb.ToString();
521 byte[] prehashbytes = context.Encoding.GetBytes(prehash);
525 stream.Read(inputBuffer, 0, 4);
526 // Send the PasswordPacket.
527 ChangeState( context, NpgsqlStartupState.Instance );
530 // 2.
532 crypt_buf = new byte[prehashbytes.Length + 4];
533 prehashbytes.CopyTo(crypt_buf, 0);
534 inputBuffer.CopyTo(crypt_buf, prehashbytes.Length);
536 sb = new StringBuilder ("md5"); // This is needed as the backend expects md5 result starts with "md5"
537 hashResult = md5.ComputeHash(crypt_buf);
538 foreach (byte b in hashResult)
539 sb.Append (b.ToString ("x2"));
541 context.Authenticate(sb.ToString ());
543 break;
546 // Only AuthenticationClearTextPassword and AuthenticationMD5Password supported for now.
547 NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationOK", LogLevel.Debug);
548 mediator.Errors.Add(String.Format(resman.GetString("Exception_AuthenticationMethodNotSupported"), authType));
551 return;
553 case NpgsqlMessageTypes_Ver_3.RowDescription:
554 // This is the RowDescription message.
555 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "RowDescription");
557 NpgsqlRowDescription rd = new NpgsqlRowDescription(context.BackendProtocolVersion);
558 rd.ReadFromStream(stream, context.Encoding, context.OidToNameMapping);
560 mediator.AddRowDescription(rd);
563 // Now wait for the AsciiRow messages.
564 break;
566 case NpgsqlMessageTypes_Ver_3.DataRow:
567 // This is the AsciiRow message.
568 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "DataRow");
570 NpgsqlAsciiRow asciiRow = new NpgsqlAsciiRow(context.Mediator.LastRowDescription, context.BackendProtocolVersion);
571 asciiRow.ReadFromStream(stream, context.Encoding);
573 // Add this row to the rows array.
574 mediator.AddAsciiRow(asciiRow);
577 // Now wait for CompletedResponse message.
578 break;
580 case NpgsqlMessageTypes_Ver_3.ReadyForQuery :
582 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ReadyForQuery");
584 // Possible status bytes returned:
585 // I = Idle (no transaction active).
586 // T = In transaction, ready for more.
587 // E = Error in transaction, queries will fail until transaction aborted.
588 // Just eat the status byte, we have no use for it at this time.
589 PGUtil.ReadInt32(stream, inputBuffer);
590 PGUtil.ReadString(stream, context.Encoding, 1);
592 readyForQuery = true;
593 ChangeState( context, NpgsqlReadyState.Instance );
595 break;
597 case NpgsqlMessageTypes_Ver_3.BackendKeyData :
599 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BackendKeyData");
600 // BackendKeyData message.
601 NpgsqlBackEndKeyData backend_keydata = new NpgsqlBackEndKeyData(context.BackendProtocolVersion);
602 backend_keydata.ReadFromStream(stream);
603 mediator.SetBackendKeydata(backend_keydata);
606 // Wait for ReadForQuery message
607 break;
609 case NpgsqlMessageTypes_Ver_3.NoticeResponse :
611 // Notices and errors are identical except that we
612 // just throw notices away completely ignored.
614 NpgsqlError notice = new NpgsqlError(context.BackendProtocolVersion);
615 notice.ReadFromStream(stream, context.Encoding);
617 mediator.Notices.Add(notice);
619 NpgsqlEventLog.LogMsg(resman, "Log_NoticeResponse", LogLevel.Debug, notice.Message);
622 // Wait for ReadForQuery message
623 break;
625 case NpgsqlMessageTypes_Ver_3.CompletedResponse :
626 // This is the CompletedResponse message.
627 // Get the string returned.
629 PGUtil.ReadInt32(stream, inputBuffer);
630 Str = PGUtil.ReadString(stream, context.Encoding);
632 NpgsqlEventLog.LogMsg(resman, "Log_CompletedResponse", LogLevel.Debug, Str);
634 // Add result from the processing.
635 mediator.AddCompletedResponse(Str);
637 break;
639 case NpgsqlMessageTypes_Ver_3.ParseComplete :
640 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ParseComplete");
641 // Just read up the message length.
642 PGUtil.ReadInt32(stream, inputBuffer);
643 readyForQuery = true;
644 break;
646 case NpgsqlMessageTypes_Ver_3.BindComplete :
647 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BindComplete");
648 // Just read up the message length.
649 PGUtil.ReadInt32(stream, inputBuffer);
650 readyForQuery = true;
651 break;
653 case NpgsqlMessageTypes_Ver_3.EmptyQueryResponse :
654 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "EmptyQueryResponse");
655 PGUtil.ReadInt32(stream, inputBuffer);
656 break;
658 case NpgsqlMessageTypes_Ver_3.NotificationResponse :
659 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "NotificationResponse");
661 // Eat the length
662 PGUtil.ReadInt32(stream, inputBuffer);
664 // Process ID sending notification
665 Int32 PID = PGUtil.ReadInt32(stream, inputBuffer);
666 // Notification string
667 String notificationResponse = PGUtil.ReadString( stream, context.Encoding );
668 // Additional info, currently not implemented by PG (empty string always), eat it
669 PGUtil.ReadString( stream, context.Encoding );
670 mediator.AddNotification(new NpgsqlNotificationEventArgs(PID, notificationResponse));
673 // Wait for ReadForQuery message
674 break;
676 case NpgsqlMessageTypes_Ver_3.ParameterStatus :
677 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ParameterStatus");
678 NpgsqlParameterStatus parameterStatus = new NpgsqlParameterStatus();
679 parameterStatus.ReadFromStream(stream, context.Encoding);
681 NpgsqlEventLog.LogMsg(resman, "Log_ParameterStatus", LogLevel.Debug, parameterStatus.Parameter, parameterStatus.ParameterValue);
683 mediator.AddParameterStatus(parameterStatus.Parameter, parameterStatus);
685 if (parameterStatus.Parameter == "server_version")
687 // Add this one under our own name so that if the parameter name
688 // changes in a future backend version, we can handle it here in the
689 // protocol handler and leave everybody else put of it.
690 mediator.AddParameterStatus("__npgsql_server_version", parameterStatus);
691 // context.ServerVersionString = parameterStatus.ParameterValue;
694 break;
695 case NpgsqlMessageTypes_Ver_3.NoData :
696 // This nodata message may be generated by prepare commands issued with queries which doesn't return rows
697 // for example insert, update or delete.
698 // Just eat the message.
699 NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ParameterStatus");
700 PGUtil.ReadInt32(stream, inputBuffer);
701 break;
704 default :
705 // This could mean a number of things
706 // We've gotten out of sync with the backend?
707 // We need to implement this type?
708 // Backend has gone insane?
709 // FIXME
710 // what exception should we really throw here?
711 throw new NotSupportedException(String.Format("Backend sent unrecognized response type: {0}", (Char)message));