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