| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- //
- // TcpBinaryFrameManager.cs
- //
- // Author:
- // Atsushi Enomoto <[email protected]>
- //
- // Copyright (C) 2009 Novell, Inc (http://www.novell.com)
- // Copyright 2011 Xamarin Inc (http://xamarin.com)
- //
- // Permission is hereby granted, free of charge, to any person obtaining
- // a copy of this software and associated documentation files (the
- // "Software"), to deal in the Software without restriction, including
- // without limitation the rights to use, copy, modify, merge, publish,
- // distribute, sublicense, and/or sell copies of the Software, and to
- // permit persons to whom the Software is furnished to do so, subject to
- // the following conditions:
- //
- // The above copyright notice and this permission notice shall be
- // included in all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- //
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Net;
- using System.Net.Sockets;
- using System.Runtime.Serialization;
- using System.Runtime.Serialization.Formatters.Binary;
- using System.ServiceModel.Channels;
- using System.Text;
- using System.Threading;
- using System.Xml;
- namespace System.ServiceModel.Channels.NetTcp
- {
- // seealso: [MC-NMF] Windows Protocol document.
- class TcpBinaryFrameManager
- {
- class MyBinaryReader : BinaryReader
- {
- public MyBinaryReader (Stream s)
- : base (s)
- {
- }
- public int ReadVariableInt ()
- {
- return Read7BitEncodedInt ();
- }
- }
- class MyBinaryWriter : BinaryWriter
- {
- public MyBinaryWriter (Stream s)
- : base (s)
- {
- }
- public void WriteVariableInt (int value)
- {
- Write7BitEncodedInt (value);
- }
- public int GetSizeOfLength (int value)
- {
- int x = 0;
- do {
- value /= 0x100;
- x++;
- } while (value != 0);
- return x;
- }
- }
- class MyXmlBinaryWriterSession : XmlBinaryWriterSession
- {
- public override bool TryAdd (XmlDictionaryString value, out int key)
- {
- if (!base.TryAdd (value, out key))
- return false;
- List.Add (value);
- return true;
- }
- public List<XmlDictionaryString> List = new List<XmlDictionaryString> ();
- }
- public const byte VersionRecord = 0;
- public const byte ModeRecord = 1;
- public const byte ViaRecord = 2;
- public const byte KnownEncodingRecord = 3;
- public const byte ExtendingEncodingRecord = 4;
- public const byte UnsizedEnvelopeRecord = 5;
- public const byte SizedEnvelopeRecord = 6;
- public const byte EndRecord = 7;
- public const byte FaultRecord = 8;
- public const byte UpgradeRequestRecord = 9;
- public const byte UpgradeResponseRecord = 0xA;
- public const byte PreambleAckRecord = 0xB;
- public const byte PreambleEndRecord = 0xC;
- public const byte UnsizedMessageTerminator = 0;
- public const byte SingletonUnsizedMode = 1;
- public const byte DuplexMode = 2;
- public const byte SimplexMode = 3;
- public const byte SingletonSizedMode = 4;
- public const byte Soap11EncodingUtf8 = 0;
- public const byte Soap11EncodingUtf16 = 1;
- public const byte Soap11EncodingUtf16LE = 2;
- public const byte Soap12EncodingUtf8 = 3;
- public const byte Soap12EncodingUtf16 = 4;
- public const byte Soap12EncodingUtf16LE = 5;
- public const byte Soap12EncodingMtom = 6;
- public const byte Soap12EncodingBinary = 7;
- public const byte Soap12EncodingBinaryWithDictionary = 8;
- public const byte UseExtendedEncodingRecord = 0xFF;
- MyBinaryReader reader;
- MyBinaryWriter writer;
- public TcpBinaryFrameManager (int mode, Stream s, bool isServiceSide)
- {
- this.mode = mode;
- this.s = s;
- this.is_service_side = isServiceSide;
- reader = new MyBinaryReader (s);
- ResetWriteBuffer ();
- EncodingRecord = Soap12EncodingBinaryWithDictionary; // FIXME: it should depend on mode.
- }
- Stream s;
- MemoryStream buffer;
- bool is_service_side;
- int mode;
- public byte EncodingRecord { get; private set; }
- public string ExtendedEncodingRecord { get; private set; }
- public Uri Via { get; set; }
- static readonly char [] convtest = new char [1] {'A'};
- MessageEncoder encoder;
- public MessageEncoder Encoder {
- get { return encoder; }
- set {
- encoder = value;
- EncodingRecord = UseExtendedEncodingRecord;
- var be = encoder as BinaryMessageEncoder;
- if (be != null)
- EncodingRecord = be.UseSession ? Soap12EncodingBinaryWithDictionary : Soap12EncodingBinary;
- var te = encoder as TextMessageEncoder;
- if (te != null) {
- var u16 = te.Encoding as UnicodeEncoding;
- bool u16be = u16 != null && u16.GetBytes (convtest) [0] == 0;
- if (encoder.MessageVersion.Envelope.Equals (EnvelopeVersion.Soap11)) {
- if (u16 != null)
- EncodingRecord = u16be ? Soap11EncodingUtf16 : Soap11EncodingUtf16LE;
- else
- EncodingRecord = Soap11EncodingUtf8;
- } else {
- if (u16 != null)
- EncodingRecord = u16be ? Soap12EncodingUtf16 : Soap12EncodingUtf16LE;
- else
- EncodingRecord = Soap12EncodingUtf8;
- }
- }
- if (value is MtomMessageEncoder)
- EncodingRecord = Soap12EncodingMtom;
- if (EncodingRecord == UseExtendedEncodingRecord)
- ExtendedEncodingRecord = encoder.ContentType;
- }
- }
- void ResetWriteBuffer ()
- {
- this.buffer = new MemoryStream ();
- writer = new MyBinaryWriter (buffer);
- }
- static readonly byte [] empty_bytes = new byte [0];
- public byte [] ReadSizedChunk ()
- {
- lock (read_lock) {
- int length = reader.ReadVariableInt ();
- if (length == 0)
- return empty_bytes;
- byte [] buffer = new byte [length];
- for (int readSize = 0; readSize < length; )
- readSize += reader.Read (buffer, readSize, length - readSize);
- return buffer;
- }
- }
- void WriteSizedChunk (byte [] data, int index, int length)
- {
- writer.WriteVariableInt (length);
- writer.Write (data, index, length);
- }
- public void ProcessPreambleInitiator ()
- {
- ResetWriteBuffer ();
- buffer.WriteByte (VersionRecord);
- buffer.WriteByte (1);
- buffer.WriteByte (0);
- buffer.WriteByte (ModeRecord);
- buffer.WriteByte ((byte) mode);
- buffer.WriteByte (ViaRecord);
- writer.Write (Via.ToString ());
- buffer.WriteByte (KnownEncodingRecord); // FIXME
- buffer.WriteByte ((byte) EncodingRecord);
- buffer.WriteByte (PreambleEndRecord);
- buffer.Flush ();
- s.Write (buffer.GetBuffer (), 0, (int) buffer.Position);
- s.Flush ();
- }
- public void ProcessPreambleAckInitiator ()
- {
- int b = s.ReadByte ();
- switch (b) {
- case PreambleAckRecord:
- return; // success
- case FaultRecord:
- throw new FaultException (reader.ReadString ());
- default:
- throw new ProtocolException (String.Format ("Preamble Ack Record is expected, got {0:X}", b));
- }
- }
- public void ProcessPreambleAckRecipient ()
- {
- s.WriteByte (PreambleAckRecord);
- }
- public bool ProcessPreambleRecipient ()
- {
- return ProcessPreambleRecipient (-1);
- }
- bool ProcessPreambleRecipient (int initialByte)
- {
- bool preambleEnd = false;
- while (!preambleEnd) {
- int b = initialByte < 0 ? s.ReadByte () : initialByte;
- if (b < 0)
- return false;
- switch (b) {
- case VersionRecord:
- if (s.ReadByte () != 1)
- throw new ProtocolException ("Major version must be 1");
- if (s.ReadByte () != 0)
- throw new ProtocolException ("Minor version must be 0");
- break;
- case ModeRecord:
- if (s.ReadByte () != mode)
- throw new ProtocolException (String.Format ("Duplex mode is expected to be {0:X}", mode));
- break;
- case ViaRecord:
- Via = new Uri (reader.ReadString ());
- break;
- case KnownEncodingRecord:
- EncodingRecord = (byte) s.ReadByte ();
- break;
- case ExtendingEncodingRecord:
- throw new NotImplementedException ("ExtendingEncodingRecord");
- case UpgradeRequestRecord:
- throw new NotImplementedException ("UpgradeRequetRecord");
- case UpgradeResponseRecord:
- throw new NotImplementedException ("UpgradeResponseRecord");
- case PreambleEndRecord:
- preambleEnd = true;
- break;
- default:
- throw new ProtocolException (String.Format ("Unexpected record type {0:X2}", b));
- }
- }
- return true;
- }
- XmlBinaryReaderSession reader_session;
- int reader_session_items;
- object read_lock = new object ();
- object write_lock = new object ();
- public Message ReadSizedMessage ()
- {
- lock (read_lock) {
- // FIXME: implement full [MC-NMF].
- int packetType;
- try {
- packetType = s.ReadByte ();
- } catch (IOException) {
- // it is already disconnected
- return null;
- } catch (SocketException) {
- // it is already disconnected
- return null;
- }
- // FIXME: .NET never results in -1, so there may be implementation mismatch in Socket (but might be in other places)
- if (packetType == -1)
- return null;
- // FIXME: The client should wait for EndRecord, but if we try to send it, the socket blocks and becomes unable to work anymore.
- if (packetType == EndRecord)
- return null;
- if (packetType != SizedEnvelopeRecord) {
- if (is_service_side) {
- // reconnect
- ProcessPreambleRecipient (packetType);
- ProcessPreambleAckRecipient ();
- }
- else
- throw new NotImplementedException (String.Format ("Packet type {0:X} is not implemented", packetType));
- }
- byte [] buffer = ReadSizedChunk ();
- var ms = new MemoryStream (buffer, 0, buffer.Length);
- // FIXME: turned out that it could be either in-band dictionary ([MC-NBFSE]), or a mere xml body ([MC-NBFS]).
- bool inBandDic = false;
- XmlBinaryReaderSession session = null;
- switch (EncodingRecord) {
- case Soap11EncodingUtf8:
- case Soap11EncodingUtf16:
- case Soap11EncodingUtf16LE:
- case Soap12EncodingUtf8:
- case Soap12EncodingUtf16:
- case Soap12EncodingUtf16LE:
- if (!(Encoder is TextMessageEncoder))
- throw new InvalidOperationException (String.Format ("Unexpected message encoding value in the received message: {0:X}", EncodingRecord));
- break;
- case Soap12EncodingMtom:
- if (!(Encoder is MtomMessageEncoder))
- throw new InvalidOperationException (String.Format ("Unexpected message encoding value in the received message: {0:X}", EncodingRecord));
- break;
- default:
- throw new InvalidOperationException (String.Format ("Unexpected message encoding value in the received message: {0:X}", EncodingRecord));
- case Soap12EncodingBinaryWithDictionary:
- inBandDic = true;
- goto case Soap12EncodingBinary;
- case Soap12EncodingBinary:
- session = inBandDic ? (reader_session ?? new XmlBinaryReaderSession ()) : null;
- reader_session = session;
- if (inBandDic) {
- byte [] rsbuf = new TcpBinaryFrameManager (0, ms, is_service_side).ReadSizedChunk ();
- using (var rms = new MemoryStream (rsbuf, 0, rsbuf.Length)) {
- var rbr = new BinaryReader (rms, Encoding.UTF8);
- while (rms.Position < rms.Length)
- session.Add (reader_session_items++, rbr.ReadString ());
- }
- }
- break;
- }
- var benc = Encoder as BinaryMessageEncoder;
- lock (Encoder) {
- if (benc != null)
- benc.CurrentReaderSession = session;
- // FIXME: supply maxSizeOfHeaders.
- Message msg = Encoder.ReadMessage (ms, 0x10000);
- if (benc != null)
- benc.CurrentReaderSession = null;
- return msg;
- }
-
- }
- }
- // FIXME: support timeout
- public Message ReadUnsizedMessage (TimeSpan timeout)
- {
- lock (read_lock) {
- // Encoding type 7 is expected
- if (EncodingRecord != Soap12EncodingBinary)
- throw new NotImplementedException (String.Format ("Message encoding {0:X} is not implemented yet", EncodingRecord));
- var packetType = s.ReadByte ();
- if (packetType == EndRecord)
- return null;
- if (packetType != UnsizedEnvelopeRecord)
- throw new NotImplementedException (String.Format ("Packet type {0:X} is not implemented", packetType));
- var ms = new MemoryStream ();
- while (true) {
- byte [] buffer = ReadSizedChunk ();
- if (buffer.Length == 0) // i.e. it is UnsizedMessageTerminator (which is '0')
- break;
- ms.Write (buffer, 0, buffer.Length);
- }
- ms.Seek (0, SeekOrigin.Begin);
- // FIXME: supply correct maxSizeOfHeaders.
- Message msg = Encoder.ReadMessage (ms, (int) ms.Length);
- return msg;
-
- }
- }
- byte [] eof_buffer = new byte [1];
- MyXmlBinaryWriterSession writer_session;
- public void WriteSizedMessage (Message message)
- {
- lock (write_lock) {
- ResetWriteBuffer ();
- buffer.WriteByte (SizedEnvelopeRecord);
- MemoryStream ms = new MemoryStream ();
- var session = writer_session ?? new MyXmlBinaryWriterSession ();
- writer_session = session;
- int writer_session_count = session.List.Count;
- var benc = Encoder as BinaryMessageEncoder;
- try {
- if (benc != null)
- benc.CurrentWriterSession = session;
- Encoder.WriteMessage (message, ms);
- } finally {
- if (benc != null)
- benc.CurrentWriterSession = null;
- }
- // dictionary
- if (EncodingRecord == Soap12EncodingBinaryWithDictionary) {
- MemoryStream msd = new MemoryStream ();
- BinaryWriter dw = new BinaryWriter (msd);
- for (int i = writer_session_count; i < session.List.Count; i++)
- dw.Write (session.List [i].Value);
- dw.Flush ();
- int length = (int) (msd.Position + ms.Position);
- var msda = msd.ToArray ();
- int sizeOfLength = writer.GetSizeOfLength (msda.Length);
- writer.WriteVariableInt (length + sizeOfLength); // dictionary array also involves the size of itself.
- WriteSizedChunk (msda, 0, msda.Length);
- }
- else
- writer.WriteVariableInt ((int) ms.Position);
- // message body
- var arr = ms.GetBuffer ();
- writer.Write (arr, 0, (int) ms.Position);
- writer.Flush ();
- s.Write (buffer.GetBuffer (), 0, (int) buffer.Position);
- s.Flush ();
- }
- }
- // FIXME: support timeout
- public void WriteUnsizedMessage (Message message, TimeSpan timeout)
- {
- lock (write_lock) {
- ResetWriteBuffer ();
- s.WriteByte (UnsizedEnvelopeRecord);
- s.Flush ();
- Encoder.WriteMessage (message, buffer);
- new MyBinaryWriter (s).WriteVariableInt ((int) buffer.Position);
- s.Write (buffer.GetBuffer (), 0, (int) buffer.Position);
- s.WriteByte (UnsizedMessageTerminator); // terminator
- s.Flush ();
- }
- }
- public void WriteEndRecord ()
- {
- lock (write_lock) {
- s.WriteByte (EndRecord); // it is required
- s.Flush ();
- }
- }
- public void ReadEndRecord ()
- {
- lock (read_lock) {
- int b;
- if ((b = s.ReadByte ()) != EndRecord)
- throw new ProtocolException (String.Format ("EndRecord message was expected, got {0:X}", b));
- }
- }
- }
- }
|