2002-10-21 Tim Coleman (tim@timcoleman.com)
[mono.git] / mcs / class / Mono.Data.TdsClient / Mono.Data.TdsClient.Internal / Tds.cs
1 //
2 // Mono.Data.TdsClient.Internal.Tds.cs
3 //
4 // Author:
5 //   Tim Coleman (tim@timcoleman.com)
6 //
7 // Copyright (C) 2002 Tim Coleman
8 //
9
10 using System;
11 using System.Data;
12 using System.Net;
13 using System.Net.Sockets;
14 using System.Text;
15
16 namespace Mono.Data.TdsClient.Internal {
17         internal class Tds
18         {
19                 #region Fields
20
21                 TdsVersion tdsVersion;
22
23                 string applicationName;
24                 string database = String.Empty;
25                 string connectDB;
26                 string charset;
27                 string hostname = "tux";
28                 string server;
29                 string language;
30                 string libraryName;
31                 int packetSize;
32                 string password;
33                 string progName;
34                 string user;
35
36                 string databaseProductName;
37                 string databaseProductVersion;
38                 int databaseMajorVersion;
39
40                 DataTable table = new DataTable ();
41
42                 bool moreResults;
43                 bool moreResults2;
44
45                 TdsMessage lastServerMessage;
46
47                 Encoding encoder;
48                 TdsServerType serverType;
49                 TdsComm comm;
50                 TdsConnectionParameters parms;
51                 //TdsCommand command;
52                 IsolationLevel isolationLevel;
53                 bool autoCommit;
54                 Socket socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
55
56                 #endregion // Fields
57
58                 #region Properties
59
60                 //public TdsCommandInternal Command {
61                         //get { return command; }
62                         //set { command = value; }
63                 //}
64
65                 public string Database {
66                         get { return database; }
67                         set { database = value; }
68                 }
69
70                 public TdsVersion TdsVersion {
71                         get { return tdsVersion; }
72                 }
73
74                 #endregion // Properties
75
76                 #region Constructors
77
78                 public Tds (TdsConnectionParameters parms)
79                 {
80                         applicationName = parms.ApplicationName;
81                         connectDB = parms.Database;
82                         encoder = Encoding.GetEncoding (parms.Encoding);
83                         charset = parms.Encoding;
84                         server = parms.Host;
85                         language = parms.Language;
86                         libraryName = parms.LibraryName;
87                         packetSize = parms.PacketSize;
88                         password = parms.Password;
89                         progName = parms.ProgName;
90                         tdsVersion = parms.TdsVersion;
91                         user = parms.User;
92
93                         IPHostEntry hostEntry = Dns.GetHostByName (parms.Host);
94                         IPAddress[] addresses = hostEntry.AddressList;
95
96                         IPEndPoint endPoint;
97
98                         foreach (IPAddress address in addresses) {
99                                 endPoint = new IPEndPoint (address, parms.Port);
100                                 socket.Connect (endPoint);
101
102                                 if (socket.Connected)
103                                         break;
104                         }
105         
106                         comm = new TdsComm (encoder, socket, packetSize, tdsVersion);
107                 }       
108
109                 #endregion // Constructors
110
111                 #region Methods
112
113                 public void ChangeDatabase (string databaseName)
114                 {
115                         string query = String.Format ("use {0}", databaseName);
116                         comm.StartPacket (TdsPacketType.Query);
117                         comm.Append (query);
118                         comm.SendPacket ();
119                 }
120
121                 public void ChangeSettings (bool autoCommit, IsolationLevel isolationLevel)
122                 {
123                         /*string query = SqlStatementForSettings (autoCommit, isolationLevel);
124                         if (query != null)
125                                 ChangeSettings (query);
126                                 */
127                 }
128
129                 private bool ChangeSettings (string query)
130                 {
131                         TdsPacketResult result;
132                         bool isOkay = true;
133                         if (query.Length == 0)
134                                 return true;
135
136                         comm.StartPacket (TdsPacketType.Query);
137                         comm.Append (query);
138                         comm.SendPacket ();
139
140                         bool done = false;
141                         while (!done) {
142                                 result = ProcessSubPacket ();
143                                 done = (result is TdsPacketEndTokenResult) && !((TdsPacketEndTokenResult) result).MoreResults;
144                                 if (result is TdsPacketErrorResult) {
145                                         done = true;
146                                         isOkay = false;
147                                 }
148                         }
149
150                         return isOkay;
151                 }
152
153                 private object GetCharValue (bool wideChars, bool outputParam)
154                 {
155                         object result = null;
156                         bool shortLen = (tdsVersion == TdsVersion.tds70) && (wideChars || !outputParam);
157                         int len = shortLen ? comm.GetTdsShort () : (comm.GetByte () & 0xff);
158
159                         if ((tdsVersion < TdsVersion.tds70 && len == 0) || (tdsVersion == TdsVersion.tds70 && len == 0xffff))
160                                 result = null;
161                         else if (len >= 0) {
162                                 if (wideChars)
163                                         result = comm.GetString (len / 2);
164                                 else
165                                         result = encoder.GetString (comm.GetBytes (len, false), 0, len);
166
167                                 if ((byte) tdsVersion < (byte) TdsVersion.tds70 && result == " ")
168                                         result = "";
169                         }
170                         else
171                                 throw new TdsException ("");
172                         return result;
173                 }
174
175                 [System.MonoTODO]
176                 private object GetDecimalValue (int scale)
177                 {
178                         throw new NotImplementedException ();
179                 }
180
181                 [System.MonoTODO]
182                 private object GetDateTimeValue (TdsColumnType type)
183                 {
184                         throw new NotImplementedException ();
185                 }
186
187                 private object GetImageValue ()
188                 {
189                         byte[] result;
190                         byte hasValue = comm.GetByte ();
191                         if (hasValue == 0)
192                                 return null;
193                         
194                         comm.Skip (24);
195                         int len = comm.GetTdsInt ();
196                         if (len >= 0) 
197                                 result = comm.GetBytes (len, true);
198                         else
199                                 throw new TdsException ("");
200                         return result;
201                 }
202
203                 private object GetIntValue (TdsColumnType type)
204                 {
205                         object result;
206                         int len;
207
208                         switch (type) {
209                         case TdsColumnType.IntN :
210                                 len = comm.GetByte ();
211                                 break;
212                         case TdsColumnType.Int4 :
213                                 len = 4; 
214                                 break;
215                         case TdsColumnType.Int2 :
216                                 len = 2; 
217                                 break;
218                         case TdsColumnType.Int1 :
219                                 len = 1; 
220                                 break;
221                         default:
222                                 throw new TdsException ("");
223                         }
224
225                         switch (len) {
226                         case 4 :
227                                 result = comm.GetTdsInt ();
228                                 break;
229                         case 2 :
230                                 result = comm.GetTdsShort ();
231                                 break;
232                         case 1 :
233                                 result = (byte) comm.GetByte ();
234                                 break;
235                         case 0 :
236                                 result = null;
237                                 break;
238                         default:
239                                 throw new TdsException ("Bad integer length");
240                         }
241
242                         return result;
243                 }
244
245                 [System.MonoTODO]
246                 private object GetMoneyValue (TdsColumnType type)
247                 {
248                         int len;
249                         object result;
250
251                         switch (type) {
252                         case TdsColumnType.SmallMoney :
253                         case TdsColumnType.Money4 :
254                                 len = 4;
255                                 break;
256                         case TdsColumnType.Money :
257                                 len = 8;
258                                 break;
259                         case TdsColumnType.MoneyN :
260                                 len = comm.GetByte ();
261                                 break;
262                         default:
263                                 throw new TdsException ("not a money value");
264                         }
265
266                         if (len == 0)
267                                 result = null;
268                         else {
269                                 throw new NotImplementedException ();
270                         }
271
272                         return result;
273                 }
274
275                 private int GetSubPacketLength ()
276                 {
277                         return comm.GetTdsShort ();
278                 }
279
280                 private object GetTextValue (bool wideChars)
281                 {
282                         string result;
283                         byte hasValue = comm.GetByte ();
284
285                         if (hasValue == 0)
286                                 return null;
287
288                         comm.Skip (24);
289                         int len = comm.GetTdsInt ();
290
291                         if (len >= 0) {
292                                 if (wideChars)
293                                         result = comm.GetString (len / 2);
294                                 else
295                                         result = encoder.GetString (comm.GetBytes (len, false), 0, len);
296
297                                 if ((byte) tdsVersion < (byte) TdsVersion.tds70 && result == " ")
298                                         result = "";
299                         } 
300                         else 
301                                 throw new TdsException ("");
302
303                         return result;
304                 }
305
306                 private bool IsFixedSizeColumn (TdsColumnType columnType)
307                 {
308                         switch (columnType) {
309                                 case TdsColumnType.Int1 :
310                                 case TdsColumnType.Int2 :
311                                 case TdsColumnType.Int4 :
312                                 case TdsColumnType.Float8 :
313                                 case TdsColumnType.DateTime :
314                                 case TdsColumnType.Bit :
315                                 case TdsColumnType.Money :
316                                 case TdsColumnType.Money4 :
317                                 case TdsColumnType.SmallMoney :
318                                 case TdsColumnType.Real :
319                                 case TdsColumnType.DateTime4 :
320                                         return true;
321                                 case TdsColumnType.IntN :
322                                 case TdsColumnType.MoneyN :
323                                 case TdsColumnType.VarChar :
324                                 case TdsColumnType.NVarChar :
325                                 case TdsColumnType.DateTimeN :
326                                 case TdsColumnType.FloatN :
327                                 case TdsColumnType.Char :
328                                 case TdsColumnType.NChar :
329                                 case TdsColumnType.NText :
330                                 case TdsColumnType.Image :
331                                 case TdsColumnType.VarBinary :
332                                 case TdsColumnType.Binary :
333                                 case TdsColumnType.Decimal :
334                                 case TdsColumnType.Numeric :
335                                 case TdsColumnType.BitN :
336                                 case TdsColumnType.UniqueIdentifier :
337                                         return false;
338                                 default :
339                                         throw new TdsException ("bad type");
340                         }
341                 }
342
343                 [System.MonoTODO]
344                 private TdsPacketRowResult LoadRow (TdsPacketRowResult result)
345                 {
346                         throw new NotImplementedException ();
347                 }
348
349                 private int LookupBufferSize (TdsColumnType columnType)
350                 {
351                         switch (columnType) {
352                                 case TdsColumnType.Int1 :
353                                 case TdsColumnType.Bit :
354                                         return 1;
355                                 case TdsColumnType.Int2 :
356                                         return 2;
357                                 case TdsColumnType.Int4 :
358                                 case TdsColumnType.Real :
359                                 case TdsColumnType.DateTime4 :
360                                 case TdsColumnType.Money4 :
361                                 case TdsColumnType.SmallMoney :
362                                         return 4;
363                                 case TdsColumnType.Float8 :
364                                 case TdsColumnType.DateTime :
365                                 case TdsColumnType.Money :
366                                         return 8;
367                                 default :
368                                         throw new TdsException ("");
369                         }
370                 }
371
372                 private int LookupDisplaySize (TdsColumnType columnType) 
373                 {
374                         switch (columnType) {
375                                 case TdsColumnType.Int1 :
376                                         return 3;
377                                 case TdsColumnType.Int2 :
378                                         return 6;
379                                 case TdsColumnType.Int4 :
380                                         return 11;
381                                 case TdsColumnType.Real :
382                                         return 14;
383                                 case TdsColumnType.Float8 :
384                                         return 24;
385                                 case TdsColumnType.DateTime :
386                                         return 23;
387                                 case TdsColumnType.DateTime4 :
388                                         return 16;
389                                 case TdsColumnType.Bit :
390                                         return 1;
391                                 case TdsColumnType.Money :
392                                         return 21;
393                                 case TdsColumnType.Money4 :
394                                 case TdsColumnType.SmallMoney :
395                                         return 12;
396                                 default:
397                                         throw new TdsException ("");
398                         }
399                 }
400
401                 private TdsPacketColumnNamesResult ProcessColumnNames ()
402                 {
403                         int totalLength = comm.GetTdsShort ();
404                         int bytesRead = 0;
405                         int i = 0;
406
407                         bool newTable = (table.Columns.Count > 0);
408
409                         while (bytesRead < totalLength) {
410                                 int columnNameLength = comm.GetByte ();
411                                 string columnName = encoder.GetString (comm.GetBytes (columnNameLength, false), 0, columnNameLength);
412                                 bytesRead = bytesRead + 1 + columnNameLength;
413                                 if (newTable)
414                                         table.Columns.Add (columnName);
415                                 else
416                                         table.Columns[i].ColumnName = columnName;
417                                 i += 1;
418                         }
419
420                         return new TdsPacketColumnNamesResult (table.Columns);
421                 }
422
423                 private TdsPacketColumnInfoResult ProcessColumnInfo ()
424                 {
425                         int precision;
426                         int scale;
427                         int totalLength = comm.GetTdsShort ();
428                         int bytesRead = 0;
429                         int numColumns = 0;
430
431                         bool newTable = (table.Columns.Count > 0);
432
433                         while (bytesRead < totalLength) {
434                                 scale = -1;
435                                 precision = -1;
436
437                                 int bufLength = -1;
438                                 int dispSize = -1;
439                                 byte[] flagData = new byte[4];
440                                 for (int i = 0; i < 4; i += 1) {
441                                         flagData[i] = comm.GetByte ();
442                                         bytesRead += 1;
443                                 }
444                                 bool nullable = (flagData[2] & 0x01) > 0;
445                                 bool caseSensitive = (flagData[2] & 0x02) > 0;
446                                 bool writable = (flagData[2] & 0x0c) > 0;
447                                 bool autoIncrement = (flagData[2] & 0x10) > 0;
448                                 string tableName = String.Empty;
449                                 TdsColumnType columnType = (TdsColumnType) comm.GetByte ();
450                                 bytesRead += 1;
451
452                                 if (columnType == TdsColumnType.Text || columnType == TdsColumnType.Image) {
453                                         comm.Skip (4);
454                                         bytesRead += 4;
455
456                                         int tableNameLength = comm.GetTdsShort ();
457                                         bytesRead += 2;
458                                         tableName = encoder.GetString (comm.GetBytes (tableNameLength, false), 0, tableNameLength);
459                                         bytesRead += tableNameLength;
460                                         bufLength = 2 << 31 - 1;
461                                 }
462                                 else if (columnType == TdsColumnType.Decimal || columnType == TdsColumnType.Numeric) {
463                                         bufLength = comm.GetByte ();
464                                         bytesRead += 1;
465                                         precision = comm.GetByte ();
466                                         bytesRead += 1;
467                                         scale = comm.GetByte ();
468                                         bytesRead += 1;
469                                 }
470                                 else if (IsFixedSizeColumn (columnType))
471                                         bufLength = LookupBufferSize (columnType);
472                                 else {
473                                         bufLength = (int) comm.GetByte () & 0xff;
474                                         bytesRead += 1;
475                                 }
476
477                                 DataColumn column;
478
479                                 if (newTable) 
480                                         column = table.Columns.Add ();
481                                 else
482                                         column = table.Columns[numColumns];
483
484                                 numColumns += 1;
485
486                                 column.AllowDBNull = nullable;
487                                 column.AutoIncrement = autoIncrement;
488                                 column.ReadOnly = !writable;
489                         }
490
491                         int skipLength = totalLength - bytesRead;
492                         if (skipLength != 0)
493                                 throw new TdsException ("skipping");
494                         return new TdsPacketColumnInfoResult (table.Columns);
495                 }
496
497                 [System.MonoTODO]
498                 private TdsPacketEndTokenResult ProcessEndToken (TdsPacketSubType type)
499                 {
500                         byte status = comm.GetByte ();
501                         comm.GetByte ();
502                         byte op = comm.GetByte ();
503                         comm.GetByte ();
504                         int rowCount = comm.GetTdsInt ();
505                         if (op == (byte) 0xc1) 
506                                 rowCount = 0;
507
508                         if (type == TdsPacketSubType.DoneInProc) 
509                                 rowCount = -1;
510
511                         TdsPacketEndTokenResult result = new TdsPacketEndTokenResult (type, status, rowCount);
512
513                         moreResults = result.MoreResults;
514
515                         // FIXME: Finish the query
516
517                         return result;
518                 }
519
520                 private TdsPacketResult ProcessEnvChange ()
521                 {
522                         int len = GetSubPacketLength ();
523                         TdsEnvPacketSubType type = (TdsEnvPacketSubType) comm.GetByte ();
524                         int cLen;
525
526                         switch (type) {
527                         case TdsEnvPacketSubType.BlockSize :
528                                 string blockSize;
529                                 cLen = comm.GetByte () & 0xff;
530                                 if (tdsVersion == TdsVersion.tds70) {
531                                         blockSize = comm.GetString (cLen);
532                                         comm.Skip (len - 2 - cLen * 2);
533                                 }
534                                 else {
535                                         blockSize = encoder.GetString (comm.GetBytes (cLen, false), 0, cLen);
536                                         comm.Skip (len - 2 - cLen);
537                                 }
538
539                                 comm.ResizeOutBuf (Int32.Parse (blockSize));
540                                 break;
541                         case TdsEnvPacketSubType.CharSet :
542                                 cLen = comm.GetByte () & 0xff;
543                                 if (tdsVersion == TdsVersion.tds70) {
544                                         this.language = comm.GetString (cLen);
545                                         comm.Skip (len - 2 - cLen * 2);
546                                 }
547                                 else {
548                                         this.charset = encoder.GetString (comm.GetBytes (cLen, false), 0, cLen);
549                                         comm.Skip (len - 2 - cLen);
550                                         SetCharset (charset);
551                                 }
552
553                                 break;
554                         case TdsEnvPacketSubType.Database :
555                                 cLen = comm.GetByte () & 0xff;
556                                 string newDB = tdsVersion == TdsVersion.tds70 ? comm.GetString (cLen) : encoder.GetString (comm.GetBytes (cLen, false), 0, cLen);
557                                 cLen = comm.GetByte () & 0xff;
558                                 string oldDB = tdsVersion == TdsVersion.tds70 ? comm.GetString (cLen) : encoder.GetString (comm.GetBytes (cLen, false), 0, cLen);
559                                 if (database != String.Empty && database != oldDB)
560                                         throw new TdsException ("Database mismatch.");
561                                 database = newDB;
562                                 break;
563                         default:
564                                 comm.Skip (len - 1);
565                                 break;
566                         }
567
568                         return new TdsPacketResult (TdsPacketSubType.EnvChange);
569                 }
570
571                 private TdsPacketResult ProcessLoginAck ()
572                 {
573                         GetSubPacketLength ();
574
575                         if (tdsVersion == TdsVersion.tds70) {
576                                 comm.Skip (5);
577                                 int nameLength = comm.GetByte ();
578                                 databaseProductName = comm.GetString (nameLength);
579                                 databaseMajorVersion = comm.GetByte ();
580                                 databaseProductVersion = String.Format ("0{0}.0{1}.0{2}", databaseMajorVersion, comm.GetByte (), ((256 * (comm.GetByte () + 1)) + comm.GetByte ()));
581                         }
582                         else {
583                                 comm.Skip (5);
584                                 short nameLength = comm.GetByte ();
585                                 databaseProductName = comm.GetString (nameLength);
586                                 comm.Skip (1);
587                                 databaseMajorVersion = comm.GetByte ();
588                                 databaseProductVersion = String.Format ("{0}.{1}", databaseMajorVersion, comm.GetByte ());
589                                 comm.Skip (1);
590                         }
591                         Console.WriteLine ("Connected to {0} {1}", databaseProductName, databaseProductVersion);
592
593                         if (databaseProductName.Length > 1 && -1 != databaseProductName.IndexOf ('\0')) {
594                                 int last = databaseProductName.IndexOf ('\0');
595                                 databaseProductName = databaseProductName.Substring (0, last);
596                         }
597
598                         return new TdsPacketResult (TdsPacketSubType.LoginAck);
599                 }
600
601                 private TdsPacketMessageResult ProcessMessage (TdsPacketSubType subType)
602                 {
603                         TdsMessage message = new TdsMessage ();
604                         GetSubPacketLength ();
605                         int len;
606
607                         message.Number = comm.GetTdsInt ();
608                         message.State = comm.GetByte ();
609                         message.Severity = comm.GetByte ();
610                         
611                         len = comm.GetTdsShort ();
612
613                         message.Message = comm.GetString (len);
614
615                         len = comm.GetByte ();
616
617                         message.Server = comm.GetString (len);
618
619                         if (subType == TdsPacketSubType.Error | subType == TdsPacketSubType.Info) {
620                                 len = comm.GetByte ();
621                                 message.ProcName = comm.GetString (len);
622                         }
623                         else 
624                                 throw new TdsException ("Invalid subtype");
625                         message.Line = comm.GetByte ();
626                         comm.GetByte ();
627
628                         Console.WriteLine (message.ToString ());
629
630                         lastServerMessage = message;
631                         if (subType == TdsPacketSubType.Error)
632                                 return new TdsPacketErrorResult (subType, message);
633                         else
634                                 return new TdsPacketMessageResult (subType, message);
635                 }
636
637                 private TdsPacketOutputParam ProcessOutputParam ()
638                 {
639                         GetSubPacketLength ();
640                         comm.GetString (comm.GetByte () & 0xff);
641                         comm.Skip (5);
642                         TdsColumnType colType = (TdsColumnType) comm.GetByte ();
643
644                         object element = null;
645                         switch (colType) {
646                         case TdsColumnType.IntN :
647                                 comm.GetByte ();
648                                 element = GetIntValue (colType);
649                                 break;
650                         case TdsColumnType.Int1 :
651                         case TdsColumnType.Int2 :
652                         case TdsColumnType.Int4 :
653                                 element = GetIntValue (colType);
654                                 break;
655                         case TdsColumnType.Image :
656                                 comm.GetByte ();
657                                 element = GetImageValue ();
658                                 break;
659                         case TdsColumnType.Text :
660                                 comm.GetByte ();
661                                 element = GetTextValue (false);
662                                 break;
663                         case TdsColumnType.NText :
664                                 comm.GetByte ();
665                                 element = GetTextValue (true);
666                                 break;
667                         case TdsColumnType.Char :
668                         case TdsColumnType.VarChar :
669                                 comm.GetByte ();
670                                 element = GetCharValue (false, true);
671                                 break;
672                         case TdsColumnType.BigVarBinary :
673                                 comm.GetTdsShort ();
674                                 int len = comm.GetTdsShort ();
675                                 element = comm.GetBytes (len, true);
676                                 break;
677                         case TdsColumnType.BigVarChar :
678                                 comm.GetTdsShort ();
679                                 element = GetCharValue (false, false);
680                                 break;
681                         case TdsColumnType.NChar :
682                         case TdsColumnType.NVarChar :
683                                 comm.GetByte ();
684                                 element = GetCharValue (true, true);
685                                 break;
686                         case TdsColumnType.Real :
687                                 element = ReadFloatN (4);
688                                 break;
689                         case TdsColumnType.Float8 :
690                                 element = ReadFloatN (8);
691                                 break;
692                         case TdsColumnType.FloatN :
693                                 comm.GetByte ();
694                                 int actualSize = comm.GetByte ();
695                                 element = ReadFloatN (actualSize);
696                                 break;
697                         case TdsColumnType.SmallMoney :
698                         case TdsColumnType.Money :
699                         case TdsColumnType.MoneyN :
700                                 comm.GetByte ();
701                                 element = GetMoneyValue (colType);
702                                 break;
703                         case TdsColumnType.Numeric :
704                         case TdsColumnType.Decimal :
705                                 comm.GetByte ();
706                                 comm.GetByte ();
707                                 int scale = comm.GetByte ();
708                                 element = GetDecimalValue (scale);
709                                 break;
710                         case TdsColumnType.DateTimeN :
711                                 comm.GetByte ();
712                                 element = GetDateTimeValue (colType);
713                                 break;
714                         case TdsColumnType.DateTime4 :
715                         case TdsColumnType.DateTime :
716                                 element = GetDateTimeValue (colType);
717                                 break;
718                         case TdsColumnType.VarBinary :
719                         case TdsColumnType.Binary :
720                                 comm.GetByte ();
721                                 int len = (comm.GetByte () & 0xff);
722                                 element = comm.GetBytes (len, true);
723                                 break;
724                         case TdsColumnType.BitN :
725                                 comm.GetByte ();
726                                 if (comm.GetByte () == 0)
727                                         element = null;
728                                 else
729                                         element = (comm.GetByte() != 0);
730                                 break;
731                         case TdsColumnType.Bit :
732                                 int columnSize = comm.GetByte ();
733                                 element = (columnSize != 0);
734                                 break;
735                         case TdsColumnType.UniqueIdentifier :
736                                 int len = comm.GetByte () & 0xff;
737                                 element = (len == 0 ? null : new Guid (comm.GetBytes (len, false)));
738                                 break;
739                         default :
740                                 throw new TdsException ("");
741                         }
742
743                         return new TdsPacketOutputParam (element);
744                 }
745
746                 private TdsPacketResult ProcessProcId ()
747                 {
748                         comm.Skip (8);
749                         return new TdsPacketResult (TdsPacketSubType.ProcId);
750                 }
751
752                 private TdsPacketRetStatResult ProcessReturnStatus ()
753                 {
754                         return new TdsPacketRetStatResult (comm.GetTdsInt ());
755                 }
756
757                 [System.MonoTODO]
758                 private TdsPacketResult ProcessSubPacket ()
759                 {
760                         TdsPacketResult result = null;
761                         moreResults = false;
762
763                         TdsPacketSubType subType = (TdsPacketSubType) comm.GetByte ();
764
765                         switch (subType) {
766                         case TdsPacketSubType.EnvChange :
767                                 result = ProcessEnvChange ();
768                                 break;
769                         case TdsPacketSubType.Error :
770                         case TdsPacketSubType.Info :
771                         case TdsPacketSubType.Msg50Token :
772                                 result = ProcessMessage (subType);
773                                 break;
774                         case TdsPacketSubType.Param :
775                                 result = ProcessOutputParam ();
776                                 break;
777                         case TdsPacketSubType.LoginAck :
778                                 result = ProcessLoginAck ();
779                                 break;
780                         case TdsPacketSubType.ReturnStatus :
781                                 result = ProcessReturnStatus ();
782                                 break;
783                         case TdsPacketSubType.ProcId :
784                                 result = ProcessProcId ();
785                                 break;
786                         case TdsPacketSubType.Done :
787                         case TdsPacketSubType.DoneProc :
788                         case TdsPacketSubType.DoneInProc :
789                                 result = ProcessEndToken (subType);
790                                 moreResults2 = ((TdsPacketEndTokenResult) result).MoreResults;
791                                 break;
792                         case TdsPacketSubType.ColumnNameToken :
793                                 result = ProcessColumnNames ();
794                                 break;
795                         case TdsPacketSubType.ColumnInfoToken :
796                                 result = ProcessColumnInfo ();
797                                 break;
798                         case TdsPacketSubType.Unknown0xA5 :
799                         case TdsPacketSubType.Unknown0xA7 :
800                         case TdsPacketSubType.Unknown0xA8 :
801                                 comm.Skip (comm.GetTdsShort ());
802                                 result = new TdsPacketUnknown (subType);
803                                 break;
804                         case TdsPacketSubType.TableName :
805                                 result = ProcessTableName ();
806                                 break;
807                         case TdsPacketSubType.Order :
808                                 comm.Skip (comm.GetTdsShort ());
809                                 result = new TdsPacketColumnOrderResult ();
810                                 break;
811                         case TdsPacketSubType.Control :
812                                 comm.Skip (comm.GetTdsShort ());
813                                 result = new TdsPacketControlResult ();
814                                 break;
815                         case TdsPacketSubType.Row :
816                                 result = LoadRow (new TdsPacketRowResult (null));
817                                 break;
818                         case TdsPacketSubType.ColumnMetadata :
819                                 result = ProcessTds7Result ();
820                                 break;
821                         default:
822                                 throw new TdsException ("oops!");
823                         }
824
825                         return result;
826                 }
827
828                 private TdsPacketTableNameResult ProcessTableName ()
829                 {
830                         int totalLength = comm.GetTdsShort ();
831                         comm.Skip (totalLength);
832                         return new TdsPacketTableNameResult ();
833                 }
834
835                 [System.MonoTODO ("complete")]
836                 private TdsPacketResult ProcessTds7Result ()
837                 {
838                         int numColumns = comm.GetTdsShort ();
839
840                         return null;
841
842                 }
843
844                 private object ReadFloatN (int len)
845                 {
846                         object tmp;
847                         long l;
848                         switch (len) {
849                         case 8 :
850                                 l = comm.GetTdsInt64 ();
851                                 tmp = BitConverter.Int64BitsToDouble (l);
852                                 break;
853                         case 4 :
854                                 l = comm.GetTdsInt ();
855                                 tmp = BitConverter.Int64BitsToDouble (l);
856                                 break;
857                         case 0 :
858                                 tmp = null;
859                                 break;
860                         default:
861                                 throw new TdsException ("");
862                         }
863
864                         return tmp;
865                 }
866
867                 private void SetCharset (string charset)
868                 {
869                         if (charset == null || charset.Length > 30)
870                                 charset = "iso-8859-1";
871                         if (this.charset != charset) {
872                                 encoder = Encoding.GetEncoding (charset);
873                                 this.charset = charset;
874                         }
875                 }
876
877                 public bool Logon (TdsConnectionParameters parms)
878                 {
879                         byte pad = (byte) 0;
880                         byte[] empty = new byte[0];
881                         bool isOkay = true;
882
883                         if (tdsVersion == TdsVersion.tds70) {
884                                 Send70Logon (parms);
885                         } else {
886                                 comm.StartPacket (TdsPacketType.Logon);
887
888                                 // hostname (offset 0)
889                                 byte[] tmp = comm.Append (hostname, 30, pad);
890                                 comm.Append ((byte) (tmp.Length < 30 ? tmp.Length : 30));
891
892                                 // username (offset 31 0x1f)
893                                 tmp = comm.Append (user, 30, pad);
894                                 comm.Append ((byte) (tmp.Length < 30 ? tmp.Length : 30));
895
896                                 // password (offset 62 0x3e)
897                                 tmp = comm.Append (password, 30, pad);
898                                 comm.Append ((byte) (tmp.Length < 30 ? tmp.Length : 30));
899
900                                 // hostproc (offset 93 0x5d)
901                                 comm.Append ("00000116", 8, pad);
902
903                                 // unused (offset 109 0x6d)
904                                 comm.Append (empty, (30-14), pad);
905
906                                 // apptype 
907                                 comm.Append ((byte) 0x0);
908                                 comm.Append ((byte) 0xa0);
909                                 comm.Append ((byte) 0x24);
910                                 comm.Append ((byte) 0xcc);
911                                 comm.Append ((byte) 0x50);
912                                 comm.Append ((byte) 0x12);
913
914                                 // hostproc length 
915                                 comm.Append ((byte) 8);
916
917                                 // type of int2
918                                 comm.Append ((byte) 3);
919
920                                 // type of int4
921                                 comm.Append ((byte) 1);
922
923                                 // type of char
924                                 comm.Append ((byte) 6);
925
926                                 // type of flt
927                                 comm.Append ((byte) 10);
928
929                                 // type of date
930                                 comm.Append ((byte) 9);
931                                 
932                                 // notify of use db
933                                 comm.Append ((byte) 1);
934
935                                 // disallow dump/load and bulk insert
936                                 comm.Append ((byte) 1);
937
938                                 // sql interface type
939                                 comm.Append ((byte) 0);
940
941                                 // type of network connection
942                                 comm.Append ((byte) 0);
943
944
945                                 // spare [7]
946                                 comm.Append (empty, 7, pad);
947                                 // appname
948                                 tmp = comm.Append (applicationName, 30, pad);
949                                 comm.Append ((byte) (tmp.Length < 30 ? tmp.Length : 30));
950
951                                 // server name
952                                 tmp = comm.Append (server, 30, pad);
953                                 comm.Append ((byte) (tmp.Length < 30 ? tmp.Length : 30));
954
955                                 // remote passwords
956                                 comm.Append (empty, 2, pad);
957                                 tmp = comm.Append (password, 253, pad);
958                                 comm.Append ((byte) (tmp.Length < 253 ? tmp.Length + 2 : 253 + 2));
959
960                                 // tds version
961                                 comm.Append ((byte) (((byte) tdsVersion) / 10));
962                                 comm.Append ((byte) (((byte) tdsVersion) % 10));
963                                 comm.Append ((byte) 0);
964                                 comm.Append ((byte) 0);
965
966                                 // prog name
967                                 tmp = comm.Append (progName, 10, pad);
968                                 comm.Append ((byte) (tmp.Length < 10 ? tmp.Length : 10));
969
970                                 // prog version
971                                 comm.Append ((byte) 6);
972
973                                 // Tell the server we can handle SQLServer version 6
974                                 comm.Append ((byte) 0);
975
976                                 // Send zero to tell the server we can't handle any other version
977                                 comm.Append ((byte) 0);
978                                 comm.Append ((byte) 0);
979
980                                 // auto convert short
981                                 comm.Append ((byte) 0);
982
983                                 // type of flt4
984                                 comm.Append ((byte) 0x0d);
985
986                                 // type of date4
987                                 comm.Append ((byte) 0x11);
988
989                                 // language
990                                 tmp = comm.Append (language, 30, pad);
991                                 comm.Append ((byte) (tmp.Length < 30 ? tmp.Length : 30));
992
993                                 // notify on lang change
994                                 comm.Append ((byte) 1);
995
996                                 // security label hierarchy
997                                 comm.Append ((short) 0);
998
999                                 // security components
1000                                 comm.Append (empty, 8, pad);
1001
1002                                 // security spare
1003                                 comm.Append ((short) 0);
1004
1005                                 // security login role
1006                                 comm.Append ((byte) 0);
1007
1008                                 // charset
1009                                 tmp = comm.Append (charset, 30, pad);
1010                                 comm.Append ((byte) (tmp.Length < 30 ? tmp.Length : 30));
1011
1012                                 // notify on charset change
1013                                 comm.Append ((byte) 1);
1014
1015                                 // length of tds packets
1016                                 tmp = comm.Append (packetSize.ToString (), 6, pad);
1017                                 comm.Append ((byte) 3);
1018
1019                                 // pad out to a longword
1020                                 comm.Append (empty, 8, pad);
1021                         }
1022
1023                         comm.SendPacket ();
1024
1025                         TdsPacketResult result;
1026
1027                         while (!((result = ProcessSubPacket()) is TdsPacketEndTokenResult)) {
1028                                 if (result is TdsPacketErrorResult) {
1029                                         isOkay = false;
1030                                 }
1031                                 // XXX Should really process some more types of packets.
1032                         }
1033
1034                         if (isOkay) {
1035                                 // XXX Should we move this to the Connection class?
1036                                 //isOkay = initSettings(_database);
1037                         }
1038
1039                         // XXX Possible bug.  What happend if this is cancelled before the logon
1040                         // takes place?  Should isOkay be false?
1041                         return isOkay;
1042                         
1043                 }
1044
1045                 // This packet is documented at 
1046                 // http://www.freetds.org/tds.htm#login7
1047                 public void Send70Logon (TdsConnectionParameters parms)
1048                 {
1049                         byte[] empty = new byte[0];
1050                         byte pad = (byte) 0;
1051
1052                         byte[] magic1 = {0x06, 0x83, 0xf2, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x88, 0xff, 0xff, 0xff, 0x36, 0x04, 0x00, 0x00};
1053                         byte[] magic2 = {0x00, 0x40, 0x33, 0x9a, 0x6b, 0x50};
1054                         byte[] magic3 = {0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50}; // NTLMSSP
1055                         short partialPacketSize = (short) (86 + 2 * (
1056                                         hostname.Length + 
1057                                         user.Length + 
1058                                         applicationName.Length + 
1059                                         password.Length + 
1060                                         server.Length +
1061                                         libraryName.Length +
1062                                         language.Length +
1063                                         connectDB.Length)); 
1064                         short totalPacketSize = (short) (partialPacketSize + 48);
1065                         comm.StartPacket (TdsPacketType.Logon70);
1066                         comm.Append (totalPacketSize);
1067                         comm.Append (empty, 5, pad);
1068
1069                         if (tdsVersion == TdsVersion.tds80)
1070                                 comm.Append ((byte) 0x80);
1071                         else
1072                                 comm.Append ((byte) 0x70);
1073
1074                         comm.Append (empty, 7, pad);
1075                         comm.Append (magic1);
1076
1077                         short curPos = 86;
1078
1079                         // Hostname 
1080                         comm.Append (curPos);
1081                         comm.Append ((short) hostname.Length);
1082                         curPos += (short) (hostname.Length * 2);
1083
1084                         // Username
1085                         comm.Append (curPos);
1086                         comm.Append ((short) user.Length);
1087                         curPos += (short) (user.Length * 2);
1088
1089                         // Password
1090                         comm.Append (curPos);
1091                         comm.Append ((short) password.Length);
1092                         curPos += (short) (password.Length * 2);
1093
1094                         // AppName
1095                         comm.Append (curPos);
1096                         comm.Append ((short) applicationName.Length);
1097                         curPos += (short) (applicationName.Length * 2);
1098
1099                         // Server Name
1100                         comm.Append (curPos);
1101                         comm.Append ((short) server.Length);
1102                         curPos += (short) (server.Length * 2);
1103
1104                         // Unknown
1105                         comm.Append ((short) 0);
1106                         comm.Append ((short) 0);
1107
1108                         // Library Name
1109                         comm.Append (curPos);
1110                         comm.Append ((short) libraryName.Length);
1111                         curPos += (short) (libraryName.Length * 2);
1112
1113                         // Character Set
1114                         comm.Append (curPos);
1115                         comm.Append ((short) language.Length);
1116                         curPos += (short) (language.Length * 2);
1117
1118                         // Database
1119                         comm.Append (curPos);
1120                         comm.Append ((short) connectDB.Length);
1121                         curPos += (short) (connectDB.Length * 2);
1122
1123                         comm.Append (magic2);
1124                         comm.Append (partialPacketSize);
1125                         comm.Append ((short) 48);
1126                         comm.Append (totalPacketSize);
1127                         comm.Append ((short) 0);
1128
1129                         string scrambledPwd = Tds7CryptPass (password);
1130
1131                         comm.Append (hostname);
1132                         comm.Append (user);
1133                         comm.Append (scrambledPwd);
1134                         comm.Append (applicationName);
1135                         comm.Append (server);
1136                         comm.Append (libraryName);
1137                         comm.Append (language);
1138                         comm.Append (connectDB);
1139                         comm.Append (magic3);
1140
1141                         comm.Append ((byte) 0x0);
1142                         comm.Append ((byte) 0x1);
1143                         comm.Append (empty, 3, pad);
1144                         comm.Append ((byte) 0x6);
1145                         comm.Append ((byte) 0x82);
1146                         comm.Append (empty, 22, pad);
1147                         comm.Append ((byte) 0x30);
1148                         comm.Append (empty, 7, pad);
1149                         comm.Append ((byte) 0x30);
1150                         comm.Append (empty, 3, pad);
1151                 }
1152
1153                 private string SqlStatementForSettings (bool autoCommit, IsolationLevel isolationLevel)
1154                 {
1155                         if (autoCommit == this.autoCommit && isolationLevel == this.isolationLevel)
1156                                 return null;
1157                         StringBuilder res = new StringBuilder ();
1158                         if (autoCommit != this.autoCommit) {
1159                                 this.autoCommit = autoCommit;
1160                                 res.Append (SqlStatementToSetCommit ());
1161                                 res.Append (' ');
1162                         }
1163                         if (isolationLevel != this.isolationLevel) {
1164                                 this.isolationLevel = isolationLevel;
1165                                 res.Append (SqlStatementToSetIsolationLevel ());
1166                                 res.Append (' ');
1167                         }
1168                         return res.ToString ();
1169                 }
1170
1171                 private string SqlStatementToSetCommit ()
1172                 {
1173                         string result;
1174                         if (serverType == TdsServerType.Sybase) {
1175                                 if (autoCommit) 
1176                                         result = "set CHAINED off";
1177                                 else
1178                                         result = "set CHAINED on";
1179                         }
1180                         else {
1181                                 if (autoCommit)
1182                                         result = "set implicit_transactions off";
1183                                 else
1184                                         result = "set implicit_transactions on";
1185                         }
1186                         return result;
1187                 }
1188
1189                 [System.MonoTODO]
1190                 private string SqlStatementToSetIsolationLevel ()
1191                 {
1192                         string result = "";
1193                         return result;
1194                 }
1195
1196                 private static string Tds7CryptPass (string pass)
1197                 {
1198                         int xormask = 0x5a5a;
1199                         int len = pass.Length;
1200                         char[] chars = new char[len];
1201
1202                         for (int i = 0; i < len; ++i) {
1203                                 int c = ((int) (pass[i])) ^ xormask;
1204                                 int m1 = (c >> 4) & 0x0f0f;
1205                                 int m2 = (c << 4) & 0xf0f0;
1206                                 chars[i] = (char) (m1 | m2);
1207                         }
1208
1209                         return new String (chars);
1210                 }
1211
1212                 #endregion // Methods
1213         }
1214 }