Может ли XmlDictionaryReader действительно обрабатывать двоичный XML? Если нет, то что?

Я пытаюсь написать инструмент отладки, который позволяет пользователю для просмотра нового двоичного формата XML WCF (application/soap +msbin1) в виде простого текста. Когда я нашел класс XmlDictionaryReader, я подумал: Я бы сделал в считанные минуты, но это не работает, как ожидалось.

private string DecodeBinaryXML(byte[] binaryBuffer)
{
    if (binaryBuffer == null)
    {
        return "";
    }

    try
    {
        var doc = new XmlDocument();
        using (var binaryReader = XmlDictionaryReader.CreateBinaryReader(binaryBuffer, XmlDictionaryReaderQuotas.Max))
        {                    
            doc.Load(binaryReader);
            binaryReader.Close();
        }

        var textBuffer = new StringBuilder();
        var settings = new XmlWriterSettings()
        {
            // lots of code not relevant to the question
        };
        using (var writer = XmlWriter.Create(textBuffer, settings))
        {
            doc.Save(writer);
            writer.Close();
        }

        return textBuffer.ToString();
    }
    catch (Exception ex)
    {
        // just display errors in the text viewer
        return ex.ToString();
    }
}

Каждый образец «soap+msbin1», который я нашел в Интернете или сгенерировал самостоятельно, вызывает исключение синтаксического анализа в doc.Load().

Чтобы увидеть, что происходит, я создал простое тестовое приложение и атаковал проблему с другой стороны.

// client
static void Main(string[] args)
{
    var binding = new CustomBinding(new TextMessageEncodingBindingElement(), 
                                    new HttpTransportBindingElement());            
    var proxy = ChannelFactory<IService1>.CreateChannel(binding, 
               new EndpointAddress("http://ipv4.fiddler:25381/Service1.svc"));
    Console.WriteLine(proxy.Echo("asdf"));
}

// shared interface
[ServiceContract()]
public interface IService1
{
    [OperationContract]
    string Echo(string input);
}

// server
public class Service1 : IService1
{
    public string Echo(string input)
    {
        return "WCF says hi to: " + input;
    }
}

Запуск запускает http-запрос, который выглядит так:

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" 
            xmlns:a="http://www.w3.org/2005/08/addressing">
  <s:Header>
     <a:Action s:mustUnderstand="1">http://tempuri.org/IService1/Echo</a:Action>
     <a:MessageID>urn:uuid:21a33e81-bfab-424f-a2e5-5116101a7319</a:MessageID>
     <a:ReplyTo>
        <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
     </a:ReplyTo>
     <a:To s:mustUnderstand="1">http://ipv4.fiddler:25381/Service1.svc</a:To>
  </s:Header>

  <s:Body>
      <Echo xmlns="http://tempuri.org/">
          <input>asdf</input>
      </Echo>
  </s:Body>
</s:Envelope>

Я преобразовал этот XML в двоичный файл двумя разными способами. Во-первых, используя XmlDictionaryWriter:

$fs = [system.io.file]::Create("c:\temp\soap.bin")
$writer = [system.xml.xmldictionarywriter]::CreateBinaryWriter($fs)
$xml = [xml] (gc C:\temp\soap.xml)
$xml.Save($writer)
$writer.Close(); $fs.Close()

Затем с помощью WCF и того же сетевого сниффера:

    @@ -1,7 +1,7 @@
     // client
     static void Main(string[] args)
     {
-        var binding = new CustomBinding(new TextMessageEncodingBindingElement(), 
+        var binding = new CustomBinding(new BinaryMessageEncodingBindingElement(), 
                                         new HttpTransportBindingElement()); 

Метод № 1 дал 397 байт бинарного мусора. Метод №2 показывает 169 байт очень разного бинарного мусора. За исключением нескольких строк, которые появляются в обоих выходных данных, я не вижу большого сходства в этих двух кодировках. Неудивительно, что XmlDictionaryReader не может понять вывод службы WCF!

Есть ли какой-то секрет расшифровки этого формата, или я вообще на неверном пути?


person Richard Berg    schedule 18.08.2009    source источник


Ответы (4)


Получил многообещающий ответ от Carlos Figueira @ MS.

WCF использует «статический словарь», который кодирует некоторые известные строки в (небольшие) идентификаторы. Например, строки «Конверт», «http://www.w3.org/2003/05/soap-envelope", "http://www.w3.org/2005/08/addressing" и так далее представлены всего несколькими байтами. Таким образом, чтобы иметь возможность анализировать запросы, отправляемые WCF, вам необходимо передать этот словарь (IXmlDictionary) в метод XmlDictionaryReader.CreateBinaryReader.

Весь словарь задокументирован по адресу http://msdn.microsoft.com/en-us/library/cc219175(PROT.10).aspx. Код для чтения запроса должен выглядеть примерно так:

public class Post_e9208540_7877_4318_909d_92eb8490ab58
{
    static XmlDictionary dictionary;
    static XmlDictionary GetDictionary()
    {
        if (dictionary == null)
        {
            XmlDictionary temp = new XmlDictionary();
            dictionary = temp;
            temp.Add("mustUnderstand");
            temp.Add("Envelope");
            temp.Add("http://www.w3.org/2003/05/soap-envelope");
            temp.Add("http://www.w3.org/2005/08/addressing");
            ...
        }
        return dictionary;
    }
    public static void DecodeBinaryMessage(byte[] message)
    {
        XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(message, 0, message.Length, GetDictionary(), XmlDictionaryReaderQuotas.Max);
        Console.WriteLine(reader.ReadOuterXml());
    }
} 

Я обновлю этот ответ более подробно, если он приведет к рабочему решению.

редактировать: да, работает как шарм! Единственная проблема с решением Карлоса заключается в том, что ReadOuterXml() не работает. Чтение в XmlDocument, а затем запись Stream в любом случае позволяет гораздо лучше контролировать форматирование, поэтому я придерживался этого.

Примечание: репликация словаря в спецификации MS занимает около 500 строк кода. Я бы рекомендовал скопировать мой, если вы не мазохист - http://tfstoys.codeplex.com/sourcecontrol/changeset/view/26191?projectName=tfstoys#499486

person Richard Berg    schedule 19.08.2009

Двоичный мусор..... ну, вы используете BinaryEncoding!

var binding = new CustomBinding(new BinaryMessageEncodingBindingElement(), 
                                new HttpTransportBindingElement());   

Можете ли вы - просто ради аргумента - попробовать вместо этого использовать TextEncoding и посмотреть, работает ли это ?? Кроме того, по умолчанию WCF будет шифровать и подписывать каждое сообщение, поэтому, если вы перехватите провод, вы должны видеть только двоичный мусор! :-)

Кроме того, в какой момент связи WCF вы пытаетесь перехватить эти сообщения?

Если вы перехватите их «по проводу» между клиентом и сервером, они будут бинарно закодированы в вашей настройке — вы получите gooblydeguck.

Однако WCF предлагает отличную возможность расширения, поэтому вы можете захватывать сообщения до их двоичного кодирования (на клиенте) или после их декодирования (на клиенте). сервер, входящий). Загляните в Инспекторы сообщений — они позволяют вам просматривать сообщения, проходящие через стек WCF, когда они создаются на клиенте и распаковываются на сервере!

Посмотрите отличные ресурсы:

Марк

person marc_s    schedule 18.08.2009
comment
Грязь фигуральная -- шестнадцатеричным редактором конечно пользовался :) Я их перехватываю по проводу (ну по http прокси). Ожидается, что он будет двоичным. Чего я не ожидал, так это того, что классы XmlDictionary* будут использовать ДРУГОЕ двоичное кодирование, чем классы WCF; Я думал, что одно построено на другом. Я изучу ссылки, но я очень надеюсь на инструмент, который работает по сети без вмешательства клиента или сервера. - person Richard Berg; 18.08.2009
comment
Ну, как я уже сказал - по умолчанию (если вы специально не отключите это сами) данные на проводе для WCF зашифрованы и подписаны - не уверен, что вы легко это обойдете :-( - person marc_s; 18.08.2009
comment
Действительно? Наиболее широко используемая привязка (basicHttpBinding) определенно не шифрует; Я все время обнюхиваю его, поэтому хочется повторно использовать эти инструменты для перехвата. Я на 90% уверен, что привязка, определенная моим кодом выше, также не включает шифрование. Если у вас есть документация по этому поводу, я бы хотел ее увидеть! - person Richard Berg; 20.08.2009

На данный момент я борюсь с этим, но я придумал более короткий обходной путь для конструкции Dictionary, используя отражение, чтобы получить статический словарь в сборке ServiceModel:

var serviceModelAssembly = Assembly.GetAssembly(typeof (System.ServiceModel.ActionNotSupportedException));
var serviceModelDictionaryType = serviceModelAssembly.GetTypes().Single(t => t.Name.Equals("ServiceModelDictionary"));
var currentVersionProperty = serviceModelDictionaryType.GetProperty("CurrentVersion");
var serviceModelDictionary = (IXmlDictionary)currentVersionProperty.GetValue(null, null);
// Now use serviceModelDictionary as argument for reader
person Mark Rendle    schedule 15.11.2010

Помимо ответа, данного marc_s, имейте в виду, что XmlDictionaryReader — это просто абстрактный класс, расширяющий интерфейс для XmlReader (то же самое относится к XmlDictionaryWriter). Они по-прежнему имеют дело исключительно с InfoSet, а не с его конкретным представлением.

С точки зрения фактического чтения/записи двоичного формата xml, используемого BinaryMessageEncoder, это выполняется двумя внутренними классами, реализованными WCF: XmlBinaryReader и XmlBinaryWriter. Я предполагаю, что вы могли бы использовать их напрямую, если бы вы могли использовать некоторое отражение, но кроме этого, они действительно предназначены для косвенного использования через BinaryMessageEncoder.

Кстати, вы, безусловно, можете использовать кодировщик напрямую, как показано в эта запись в блоге.

person tomasr    schedule 18.08.2009
comment
Я использую классы XmlBinaryReader/Writer. Поясню вопрос более подробно. - person Richard Berg; 18.08.2009
comment
Сообщение в блоге полезно. Использование кодировщика, как вы описываете, дает результат ближе к методу № 2. Есть ли соответствующий декодер? Самое близкое, что я могу найти, это mebe.GetProperty‹T›. У этого метода есть две ловушки: (а) нужно создать весь BindingContext (б) нужно знать строгий тип элементов данных. Я не против сделать (а), если придется [хотя это делает инструмент менее общеприменимым, чем я думал]. Но (b) выглядит как шоу-стоппер. - person Richard Berg; 18.08.2009
comment
Классы MessageEncoder в WCF выполняют как кодирование, так и декодирование. Вы также можете прочитать сообщение, используя его, используя методы ReadMessage()(). - person tomasr; 18.08.2009
comment
Поигрался с этим еще. Это близко к тому, что я ищу. К сожалению, каждый способ, который я нашел для использования MessageEncoder, имеет некоторую точку в стеке, где я не могу продолжить, не зная заранее строгого типа данных (уловка-22). - person Richard Berg; 20.08.2009