Xamarin Public Jenkins (auto-signing) e46a49ecf1 Imported Upstream version 5.10.0.47
Former-commit-id: d0813289fa2d35e1f8ed77530acb4fb1df441bc0
2018-01-24 17:04:36 +00:00

518 lines
15 KiB
C#

//
// TcpBinaryFrameManager.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
// 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 /= 0x80;
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));
}
}
}
}