[System.ServiceModel] Don't use DateTime.Now for measuring elapsed time
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Channels.NetTcp / TcpBinaryFrameManager.cs
1 // 
2 // TcpBinaryFrameManager.cs
3 // 
4 // Author: 
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 // 
7 // Copyright (C) 2009 Novell, Inc (http://www.novell.com)
8 // Copyright 2011 Xamarin Inc (http://xamarin.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 using System;
31 using System.Collections.Generic;
32 using System.IO;
33 using System.Net;
34 using System.Net.Sockets;
35 using System.Runtime.Serialization;
36 using System.Runtime.Serialization.Formatters.Binary;
37 using System.ServiceModel.Channels;
38 using System.Text;
39 using System.Threading;
40 using System.Xml;
41
42 namespace System.ServiceModel.Channels.NetTcp
43 {
44         // seealso: [MC-NMF] Windows Protocol document.
45         class TcpBinaryFrameManager
46         {
47                 class MyBinaryReader : BinaryReader
48                 {
49                         public MyBinaryReader (Stream s)
50                                 : base (s)
51                         {
52                         }
53
54                         public int ReadVariableInt ()
55                         {
56                                 return Read7BitEncodedInt ();
57                         }
58                 }
59
60                 class MyBinaryWriter : BinaryWriter
61                 {
62                         public MyBinaryWriter (Stream s)
63                                 : base (s)
64                         {
65                         }
66
67                         public void WriteVariableInt (int value)
68                         {
69                                 Write7BitEncodedInt (value);
70                         }
71
72                         public int GetSizeOfLength (int value)
73                         {
74                                 int x = 0;
75                                 do {
76                                         value /= 0x100;
77                                         x++;
78                                 } while (value != 0);
79                                 return x;
80                         }
81                 }
82
83                 class MyXmlBinaryWriterSession : XmlBinaryWriterSession
84                 {
85                         public override bool TryAdd (XmlDictionaryString value, out int key)
86                         {
87                                 if (!base.TryAdd (value, out key))
88                                         return false;
89                                 List.Add (value);
90                                 return true;
91                         }
92
93                         public List<XmlDictionaryString> List = new List<XmlDictionaryString> ();
94                 }
95
96                 public const byte VersionRecord = 0;
97                 public const byte ModeRecord = 1;
98                 public const byte ViaRecord = 2;
99                 public const byte KnownEncodingRecord = 3;
100                 public const byte ExtendingEncodingRecord = 4;
101                 public const byte UnsizedEnvelopeRecord = 5;
102                 public const byte SizedEnvelopeRecord = 6;
103                 public const byte EndRecord = 7;
104                 public const byte FaultRecord = 8;
105                 public const byte UpgradeRequestRecord = 9;
106                 public const byte UpgradeResponseRecord = 0xA;
107                 public const byte PreambleAckRecord = 0xB;
108                 public const byte PreambleEndRecord = 0xC;
109
110                 public const byte UnsizedMessageTerminator = 0;
111                 public const byte SingletonUnsizedMode = 1;
112                 public const byte DuplexMode = 2;
113                 public const byte SimplexMode = 3;
114                 public const byte SingletonSizedMode = 4;
115
116                 public const byte Soap11EncodingUtf8 = 0;
117                 public const byte Soap11EncodingUtf16 = 1;
118                 public const byte Soap11EncodingUtf16LE = 2;
119                 public const byte Soap12EncodingUtf8 = 3;
120                 public const byte Soap12EncodingUtf16 = 4;
121                 public const byte Soap12EncodingUtf16LE = 5;
122                 public const byte Soap12EncodingMtom = 6;
123                 public const byte Soap12EncodingBinary = 7;
124                 public const byte Soap12EncodingBinaryWithDictionary = 8;
125                 public const byte UseExtendedEncodingRecord = 0xFF;
126
127                 MyBinaryReader reader;
128                 MyBinaryWriter writer;
129
130                 public TcpBinaryFrameManager (int mode, Stream s, bool isServiceSide)
131                 {
132                         this.mode = mode;
133                         this.s = s;
134                         this.is_service_side = isServiceSide;
135                         reader = new MyBinaryReader (s);
136                         ResetWriteBuffer ();
137
138                         EncodingRecord = Soap12EncodingBinaryWithDictionary; // FIXME: it should depend on mode.
139                 }
140
141                 Stream s;
142                 MemoryStream buffer;
143                 bool is_service_side;
144
145                 int mode;
146
147                 public byte EncodingRecord { get; private set; }
148                 public string ExtendedEncodingRecord { get; private set; }
149
150                 public Uri Via { get; set; }
151
152                 static readonly char [] convtest = new char [1] {'A'};
153                 MessageEncoder encoder;
154                 public MessageEncoder Encoder {
155                         get { return encoder; }
156                         set {
157                                 encoder = value;
158                                 EncodingRecord = UseExtendedEncodingRecord;
159                                 var be = encoder as BinaryMessageEncoder;
160                                 if (be != null)
161                                         EncodingRecord = be.UseSession ? Soap12EncodingBinaryWithDictionary : Soap12EncodingBinary;
162                                 var te = encoder as TextMessageEncoder;
163                                 if (te != null) {
164                                         var u16 = te.Encoding as UnicodeEncoding;
165                                         bool u16be = u16 != null && u16.GetBytes (convtest) [0] == 0;
166                                         if (encoder.MessageVersion.Envelope.Equals (EnvelopeVersion.Soap11)) {
167                                                 if (u16 != null)
168                                                         EncodingRecord = u16be ? Soap11EncodingUtf16 : Soap11EncodingUtf16LE;
169                                                 else
170                                                         EncodingRecord = Soap11EncodingUtf8;
171                                         } else {
172                                                 if (u16 != null)
173                                                         EncodingRecord = u16be ? Soap12EncodingUtf16 : Soap12EncodingUtf16LE;
174                                                 else
175                                                         EncodingRecord = Soap12EncodingUtf8;
176                                         }
177                                 }
178                                 if (value is MtomMessageEncoder)
179                                         EncodingRecord = Soap12EncodingMtom;
180
181                                 if (EncodingRecord == UseExtendedEncodingRecord)
182                                         ExtendedEncodingRecord = encoder.ContentType;
183                         }
184                 }
185
186                 void ResetWriteBuffer ()
187                 {
188                         this.buffer = new MemoryStream ();
189                         writer = new MyBinaryWriter (buffer);
190                 }
191
192                 static readonly byte [] empty_bytes = new byte [0];
193
194                 public byte [] ReadSizedChunk ()
195                 {
196                         lock (read_lock) {
197
198                         int length = reader.ReadVariableInt ();
199                         if (length == 0)
200                                 return empty_bytes;
201
202                         byte [] buffer = new byte [length];
203                         for (int readSize = 0; readSize < length; )
204                                 readSize += reader.Read (buffer, readSize, length - readSize);
205                         return buffer;
206
207                         }
208                 }
209
210                 void WriteSizedChunk (byte [] data, int index, int length)
211                 {
212                         writer.WriteVariableInt (length);
213                         writer.Write (data, index, length);
214                 }
215
216                 public void ProcessPreambleInitiator ()
217                 {
218                         ResetWriteBuffer ();
219
220                         buffer.WriteByte (VersionRecord);
221                         buffer.WriteByte (1);
222                         buffer.WriteByte (0);
223                         buffer.WriteByte (ModeRecord);
224                         buffer.WriteByte ((byte) mode);
225                         buffer.WriteByte (ViaRecord);
226                         writer.Write (Via.ToString ());
227                         buffer.WriteByte (KnownEncodingRecord); // FIXME
228                         buffer.WriteByte ((byte) EncodingRecord);
229                         buffer.WriteByte (PreambleEndRecord);
230                         buffer.Flush ();
231                         s.Write (buffer.GetBuffer (), 0, (int) buffer.Position);
232                         s.Flush ();
233                 }
234
235                 public void ProcessPreambleAckInitiator ()
236                 {
237                         int b = s.ReadByte ();
238                         switch (b) {
239                         case PreambleAckRecord:
240                                 return; // success
241                         case FaultRecord:
242                                 throw new FaultException (reader.ReadString ());
243                         default:
244                                 throw new ProtocolException (String.Format ("Preamble Ack Record is expected, got {0:X}", b));
245                         }
246                 }
247
248                 public void ProcessPreambleAckRecipient ()
249                 {
250                         s.WriteByte (PreambleAckRecord);
251                 }
252
253                 public bool ProcessPreambleRecipient ()
254                 {
255                         return ProcessPreambleRecipient (-1);
256                 }
257                 bool ProcessPreambleRecipient (int initialByte)
258                 {
259                         bool preambleEnd = false;
260                         while (!preambleEnd) {
261                                 int b = initialByte < 0 ? s.ReadByte () : initialByte;
262                                 if (b < 0)
263                                         return false;
264                                 switch (b) {
265                                 case VersionRecord:
266                                         if (s.ReadByte () != 1)
267                                                 throw new ProtocolException ("Major version must be 1");
268                                         if (s.ReadByte () != 0)
269                                                 throw new ProtocolException ("Minor version must be 0");
270                                         break;
271                                 case ModeRecord:
272                                         if (s.ReadByte () != mode)
273                                                 throw new ProtocolException (String.Format ("Duplex mode is expected to be {0:X}", mode));
274                                         break;
275                                 case ViaRecord:
276                                         Via = new Uri (reader.ReadString ());
277                                         break;
278                                 case KnownEncodingRecord:
279                                         EncodingRecord = (byte) s.ReadByte ();
280                                         break;
281                                 case ExtendingEncodingRecord:
282                                         throw new NotImplementedException ("ExtendingEncodingRecord");
283                                 case UpgradeRequestRecord:
284                                         throw new NotImplementedException ("UpgradeRequetRecord");
285                                 case UpgradeResponseRecord:
286                                         throw new NotImplementedException ("UpgradeResponseRecord");
287                                 case PreambleEndRecord:
288                                         preambleEnd = true;
289                                         break;
290                                 default:
291                                         throw new ProtocolException (String.Format ("Unexpected record type {0:X2}", b));
292                                 }
293                         }
294                         return true;
295                 }
296
297                 XmlBinaryReaderSession reader_session;
298                 int reader_session_items;
299
300                 object read_lock = new object ();
301                 object write_lock = new object ();
302
303                 public Message ReadSizedMessage ()
304                 {
305                         lock (read_lock) {
306
307                         // FIXME: implement full [MC-NMF].
308
309                         int packetType;
310                         try {
311                                 packetType = s.ReadByte ();
312                         } catch (IOException) {
313                                 // it is already disconnected
314                                 return null;
315                         } catch (SocketException) {
316                                 // it is already disconnected
317                                 return null;
318                         }
319                         // FIXME: .NET never results in -1, so there may be implementation mismatch in Socket (but might be in other places)
320                         if (packetType == -1)
321                                 return null;
322                         // FIXME: The client should wait for EndRecord, but if we try to send it, the socket blocks and becomes unable to work anymore.
323                         if (packetType == EndRecord)
324                                 return null;
325                         if (packetType != SizedEnvelopeRecord) {
326                                 if (is_service_side) {
327                                         // reconnect
328                                         ProcessPreambleRecipient (packetType);
329                                         ProcessPreambleAckRecipient ();
330                                 }
331                                 else
332                                         throw new NotImplementedException (String.Format ("Packet type {0:X} is not implemented", packetType));
333                         }
334
335                         byte [] buffer = ReadSizedChunk ();
336                         var ms = new MemoryStream (buffer, 0, buffer.Length);
337
338                         // FIXME: turned out that it could be either in-band dictionary ([MC-NBFSE]), or a mere xml body ([MC-NBFS]).
339                         bool inBandDic = false;
340                         XmlBinaryReaderSession session = null;
341                         switch (EncodingRecord) {
342                         case Soap11EncodingUtf8:
343                         case Soap11EncodingUtf16:
344                         case Soap11EncodingUtf16LE:
345                         case Soap12EncodingUtf8:
346                         case Soap12EncodingUtf16:
347                         case Soap12EncodingUtf16LE:
348                                 if (!(Encoder is TextMessageEncoder))
349                                         throw new InvalidOperationException (String.Format ("Unexpected message encoding value in the received message: {0:X}", EncodingRecord));
350                                 break;
351                         case Soap12EncodingMtom:
352                                 if (!(Encoder is MtomMessageEncoder))
353                                         throw new InvalidOperationException (String.Format ("Unexpected message encoding value in the received message: {0:X}", EncodingRecord));
354                                 break;
355                         default:
356                                         throw new InvalidOperationException (String.Format ("Unexpected message encoding value in the received message: {0:X}", EncodingRecord));
357                         case Soap12EncodingBinaryWithDictionary:
358                                 inBandDic = true;
359                                 goto case Soap12EncodingBinary;
360                         case Soap12EncodingBinary:
361                                 session = inBandDic ? (reader_session ?? new XmlBinaryReaderSession ()) : null;
362                                 reader_session = session;
363                                 if (inBandDic) {
364                                         byte [] rsbuf = new TcpBinaryFrameManager (0, ms, is_service_side).ReadSizedChunk ();
365                                         using (var rms = new MemoryStream (rsbuf, 0, rsbuf.Length)) {
366                                                 var rbr = new BinaryReader (rms, Encoding.UTF8);
367                                                 while (rms.Position < rms.Length)
368                                                         session.Add (reader_session_items++, rbr.ReadString ());
369                                         }
370                                 }
371                                 break;
372                         }
373                         var benc = Encoder as BinaryMessageEncoder;
374                         lock (Encoder) {
375                                 if (benc != null)
376                                         benc.CurrentReaderSession = session;
377
378                                 // FIXME: supply maxSizeOfHeaders.
379                                 Message msg = Encoder.ReadMessage (ms, 0x10000);
380                                 if (benc != null)
381                                         benc.CurrentReaderSession = null;
382                                 return msg;
383                         }
384                         
385                         }
386                 }
387
388                 // FIXME: support timeout
389                 public Message ReadUnsizedMessage (TimeSpan timeout)
390                 {
391                         lock (read_lock) {
392
393                         // Encoding type 7 is expected
394                         if (EncodingRecord != Soap12EncodingBinary)
395                                 throw new NotImplementedException (String.Format ("Message encoding {0:X} is not implemented yet", EncodingRecord));
396
397                         var packetType = s.ReadByte ();
398
399                         if (packetType == EndRecord)
400                                 return null;
401                         if (packetType != UnsizedEnvelopeRecord)
402                                 throw new NotImplementedException (String.Format ("Packet type {0:X} is not implemented", packetType));
403
404                         var ms = new MemoryStream ();
405                         while (true) {
406                                 byte [] buffer = ReadSizedChunk ();
407                                 if (buffer.Length == 0) // i.e. it is UnsizedMessageTerminator (which is '0')
408                                         break;
409                                 ms.Write (buffer, 0, buffer.Length);
410                         }
411                         ms.Seek (0, SeekOrigin.Begin);
412
413                         // FIXME: supply correct maxSizeOfHeaders.
414                         Message msg = Encoder.ReadMessage (ms, (int) ms.Length);
415
416                         return msg;
417                         
418                         }
419                 }
420
421                 byte [] eof_buffer = new byte [1];
422                 MyXmlBinaryWriterSession writer_session;
423
424                 public void WriteSizedMessage (Message message)
425                 {
426                         lock (write_lock) {
427
428                         ResetWriteBuffer ();
429
430                         buffer.WriteByte (SizedEnvelopeRecord);
431
432                         MemoryStream ms = new MemoryStream ();
433                         var session = writer_session ?? new MyXmlBinaryWriterSession ();
434                         writer_session = session;
435                         int writer_session_count = session.List.Count;
436                         var benc = Encoder as BinaryMessageEncoder;
437                         try {
438                                 if (benc != null)
439                                         benc.CurrentWriterSession = session;
440                                 Encoder.WriteMessage (message, ms);
441                         } finally {
442                                 if (benc != null)
443                                         benc.CurrentWriterSession = null;
444                         }
445
446                         // dictionary
447                         if (EncodingRecord == Soap12EncodingBinaryWithDictionary) {
448                                 MemoryStream msd = new MemoryStream ();
449                                 BinaryWriter dw = new BinaryWriter (msd);
450                                 for (int i = writer_session_count; i < session.List.Count; i++)
451                                         dw.Write (session.List [i].Value);
452                                 dw.Flush ();
453
454                                 int length = (int) (msd.Position + ms.Position);
455                                 var msda = msd.ToArray ();
456                                 int sizeOfLength = writer.GetSizeOfLength (msda.Length);
457
458                                 writer.WriteVariableInt (length + sizeOfLength); // dictionary array also involves the size of itself.
459                                 WriteSizedChunk (msda, 0, msda.Length);
460                         }
461                         else
462                                 writer.WriteVariableInt ((int) ms.Position);
463
464                         // message body
465                         var arr = ms.GetBuffer ();
466                         writer.Write (arr, 0, (int) ms.Position);
467
468                         writer.Flush ();
469
470                         s.Write (buffer.GetBuffer (), 0, (int) buffer.Position);
471                         s.Flush ();
472
473                         }
474                 }
475
476                 // FIXME: support timeout
477                 public void WriteUnsizedMessage (Message message, TimeSpan timeout)
478                 {
479                         lock (write_lock) {
480
481                         ResetWriteBuffer ();
482
483                         s.WriteByte (UnsizedEnvelopeRecord);
484                         s.Flush ();
485
486                         Encoder.WriteMessage (message, buffer);
487                         new MyBinaryWriter (s).WriteVariableInt ((int) buffer.Position);
488                         s.Write (buffer.GetBuffer (), 0, (int) buffer.Position);
489
490                         s.WriteByte (UnsizedMessageTerminator); // terminator
491                         s.Flush ();
492
493                         }
494                 }
495
496                 public void WriteEndRecord ()
497                 {
498                         lock (write_lock) {
499
500                         s.WriteByte (EndRecord); // it is required
501                         s.Flush ();
502
503                         }
504                 }
505
506                 public void ReadEndRecord ()
507                 {
508                         lock (read_lock) {
509
510                         int b;
511                         if ((b = s.ReadByte ()) != EndRecord)
512                                 throw new ProtocolException (String.Format ("EndRecord message was expected, got {0:X}", b));
513
514                         }
515                 }
516         }
517 }