2 // TcpBinaryFrameManager.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2009 Novell, Inc (http://www.novell.com)
8 // Copyright 2011 Xamarin Inc (http://xamarin.com)
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:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
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.
31 using System.Collections.Generic;
34 using System.Net.Sockets;
35 using System.Runtime.Serialization;
36 using System.Runtime.Serialization.Formatters.Binary;
37 using System.ServiceModel.Channels;
39 using System.Threading;
42 namespace System.ServiceModel.Channels.NetTcp
44 // seealso: [MC-NMF] Windows Protocol document.
45 class TcpBinaryFrameManager
47 class MyBinaryReader : BinaryReader
49 public MyBinaryReader (Stream s)
54 public int ReadVariableInt ()
56 return Read7BitEncodedInt ();
60 class MyBinaryWriter : BinaryWriter
62 public MyBinaryWriter (Stream s)
67 public void WriteVariableInt (int value)
69 Write7BitEncodedInt (value);
72 public int GetSizeOfLength (int value)
83 class MyXmlBinaryWriterSession : XmlBinaryWriterSession
85 public override bool TryAdd (XmlDictionaryString value, out int key)
87 if (!base.TryAdd (value, out key))
93 public List<XmlDictionaryString> List = new List<XmlDictionaryString> ();
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;
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;
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;
127 MyBinaryReader reader;
128 MyBinaryWriter writer;
130 public TcpBinaryFrameManager (int mode, Stream s, bool isServiceSide)
134 this.is_service_side = isServiceSide;
135 reader = new MyBinaryReader (s);
138 EncodingRecord = Soap12EncodingBinaryWithDictionary; // FIXME: it should depend on mode.
143 bool is_service_side;
147 public byte EncodingRecord { get; private set; }
148 public string ExtendedEncodingRecord { get; private set; }
150 public Uri Via { get; set; }
152 static readonly char [] convtest = new char [1] {'A'};
153 MessageEncoder encoder;
154 public MessageEncoder Encoder {
155 get { return encoder; }
158 EncodingRecord = UseExtendedEncodingRecord;
159 var be = encoder as BinaryMessageEncoder;
161 EncodingRecord = be.UseSession ? Soap12EncodingBinaryWithDictionary : Soap12EncodingBinary;
162 var te = encoder as TextMessageEncoder;
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)) {
168 EncodingRecord = u16be ? Soap11EncodingUtf16 : Soap11EncodingUtf16LE;
170 EncodingRecord = Soap11EncodingUtf8;
173 EncodingRecord = u16be ? Soap12EncodingUtf16 : Soap12EncodingUtf16LE;
175 EncodingRecord = Soap12EncodingUtf8;
178 if (value is MtomMessageEncoder)
179 EncodingRecord = Soap12EncodingMtom;
181 if (EncodingRecord == UseExtendedEncodingRecord)
182 ExtendedEncodingRecord = encoder.ContentType;
186 void ResetWriteBuffer ()
188 this.buffer = new MemoryStream ();
189 writer = new MyBinaryWriter (buffer);
192 static readonly byte [] empty_bytes = new byte [0];
194 public byte [] ReadSizedChunk ()
198 int length = reader.ReadVariableInt ();
202 byte [] buffer = new byte [length];
203 for (int readSize = 0; readSize < length; )
204 readSize += reader.Read (buffer, readSize, length - readSize);
210 void WriteSizedChunk (byte [] data, int index, int length)
212 writer.WriteVariableInt (length);
213 writer.Write (data, index, length);
216 public void ProcessPreambleInitiator ()
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);
231 s.Write (buffer.GetBuffer (), 0, (int) buffer.Position);
235 public void ProcessPreambleAckInitiator ()
237 int b = s.ReadByte ();
239 case PreambleAckRecord:
242 throw new FaultException (reader.ReadString ());
244 throw new ProtocolException (String.Format ("Preamble Ack Record is expected, got {0:X}", b));
248 public void ProcessPreambleAckRecipient ()
250 s.WriteByte (PreambleAckRecord);
253 public bool ProcessPreambleRecipient ()
255 return ProcessPreambleRecipient (-1);
257 bool ProcessPreambleRecipient (int initialByte)
259 bool preambleEnd = false;
260 while (!preambleEnd) {
261 int b = initialByte < 0 ? s.ReadByte () : initialByte;
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");
272 if (s.ReadByte () != mode)
273 throw new ProtocolException (String.Format ("Duplex mode is expected to be {0:X}", mode));
276 Via = new Uri (reader.ReadString ());
278 case KnownEncodingRecord:
279 EncodingRecord = (byte) s.ReadByte ();
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:
291 throw new ProtocolException (String.Format ("Unexpected record type {0:X2}", b));
297 XmlBinaryReaderSession reader_session;
298 int reader_session_items;
300 object read_lock = new object ();
301 object write_lock = new object ();
303 public Message ReadSizedMessage ()
307 // FIXME: implement full [MC-NMF].
311 packetType = s.ReadByte ();
312 } catch (IOException) {
313 // it is already disconnected
315 } catch (SocketException) {
316 // it is already disconnected
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)
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)
325 if (packetType != SizedEnvelopeRecord) {
326 if (is_service_side) {
328 ProcessPreambleRecipient (packetType);
329 ProcessPreambleAckRecipient ();
332 throw new NotImplementedException (String.Format ("Packet type {0:X} is not implemented", packetType));
335 byte [] buffer = ReadSizedChunk ();
336 var ms = new MemoryStream (buffer, 0, buffer.Length);
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));
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));
356 throw new InvalidOperationException (String.Format ("Unexpected message encoding value in the received message: {0:X}", EncodingRecord));
357 case Soap12EncodingBinaryWithDictionary:
359 goto case Soap12EncodingBinary;
360 case Soap12EncodingBinary:
361 session = inBandDic ? (reader_session ?? new XmlBinaryReaderSession ()) : null;
362 reader_session = session;
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 ());
373 var benc = Encoder as BinaryMessageEncoder;
376 benc.CurrentReaderSession = session;
378 // FIXME: supply maxSizeOfHeaders.
379 Message msg = Encoder.ReadMessage (ms, 0x10000);
381 benc.CurrentReaderSession = null;
388 // FIXME: support timeout
389 public Message ReadUnsizedMessage (TimeSpan timeout)
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));
397 var packetType = s.ReadByte ();
399 if (packetType == EndRecord)
401 if (packetType != UnsizedEnvelopeRecord)
402 throw new NotImplementedException (String.Format ("Packet type {0:X} is not implemented", packetType));
404 var ms = new MemoryStream ();
406 byte [] buffer = ReadSizedChunk ();
407 if (buffer.Length == 0) // i.e. it is UnsizedMessageTerminator (which is '0')
409 ms.Write (buffer, 0, buffer.Length);
411 ms.Seek (0, SeekOrigin.Begin);
413 // FIXME: supply correct maxSizeOfHeaders.
414 Message msg = Encoder.ReadMessage (ms, (int) ms.Length);
421 byte [] eof_buffer = new byte [1];
422 MyXmlBinaryWriterSession writer_session;
424 public void WriteSizedMessage (Message message)
430 buffer.WriteByte (SizedEnvelopeRecord);
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;
439 benc.CurrentWriterSession = session;
440 Encoder.WriteMessage (message, ms);
443 benc.CurrentWriterSession = null;
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);
454 int length = (int) (msd.Position + ms.Position);
455 var msda = msd.ToArray ();
456 int sizeOfLength = writer.GetSizeOfLength (msda.Length);
458 writer.WriteVariableInt (length + sizeOfLength); // dictionary array also involves the size of itself.
459 WriteSizedChunk (msda, 0, msda.Length);
462 writer.WriteVariableInt ((int) ms.Position);
465 var arr = ms.GetBuffer ();
466 writer.Write (arr, 0, (int) ms.Position);
470 s.Write (buffer.GetBuffer (), 0, (int) buffer.Position);
476 // FIXME: support timeout
477 public void WriteUnsizedMessage (Message message, TimeSpan timeout)
483 s.WriteByte (UnsizedEnvelopeRecord);
486 Encoder.WriteMessage (message, buffer);
487 new MyBinaryWriter (s).WriteVariableInt ((int) buffer.Position);
488 s.Write (buffer.GetBuffer (), 0, (int) buffer.Position);
490 s.WriteByte (UnsizedMessageTerminator); // terminator
496 public void WriteEndRecord ()
500 s.WriteByte (EndRecord); // it is required
506 public void ReadEndRecord ()
511 if ((b = s.ReadByte ()) != EndRecord)
512 throw new ProtocolException (String.Format ("EndRecord message was expected, got {0:X}", b));