Kodierung von Binärdaten in XML: Gibt es bessere Alternativen als base64?

8

Ich möchte Binärdaten in einer XML-Datei (mit Python, aber was auch immer) codieren und dekodieren. Ich muss mich der Tatsache stellen, dass ein XML-Tag-Inhalt illegale Zeichen hat. Die einzigen zulässigen sind in XML-Spezifikationen beschrieben:

%Vor%

Was bedeutet, dass die nicht erlaubten sind:

  • 29 Unicode-Steuerzeichen sind ungültig (0x00 - 0x20) dh ( 000xxxxx ) außer 0x09, 0x0A, 0x0D
  • Jede Unicode-Zeichendarstellung über 2 Byte (UTF-16 +) ist unzulässig (U + D800 - U + DFFF), dh ( 11011xxx )
  • Die speziellen Unicode-Non-Zeichen sind unzulässig (0xFFFE - 0xFFFF), dh ( 11111111 1111111x )
  • & lt ;, & gt; & amp; nach diesem Post für Entitäten Inhalt

1 Byte kann 256 mögliche codieren. Mit diesen Einschränkungen ist das erste Byte auf 256-29-8-1-3 = 215 Möglichkeiten beschränkt.

Von den 215 Möglichkeiten der ersten Bytes verwendet base64 nur 64 Möglichkeiten. Base64 generiert 33% Overhead (6 Bits werden zu 1 Byte, sobald sie mit base64 codiert wurden).

Meine Frage ist also einfach: Gibt es einen Algorithmus, der effizienter als base64 Binärdaten in XML kodiert? Wenn nicht, wo sollten wir anfangen, es zu erstellen? (Bibliotheken, etc.)

Hinweis: Sie würden diesen Beitrag nicht beantworten mit "Sie sollten XML nicht verwenden, um Binärdaten zu codieren, weil ...". Tu es einfach nicht. Sie könnten bestenfalls argumentieren, warum Sie nicht die 215 Möglichkeiten für die Unterstützung schlechter XML-Parser nutzen sollten.

NB2: Ich spreche nicht über das zweite Byte, aber es gibt sicherlich einige Überlegungen, die bezüglich der Anzahl der Möglichkeiten und der Tatsache, dass 10xxxxxx den UTF8-Standard einhalten sollte, wenn wir die zusätzlichen Unicode-Ebenen verwenden (was wenn nicht?).

    
KrisWebDev 25.06.2013, 15:52
quelle

3 Antworten

2

Ich habe das Konzept in einem C-Code entwickelt.

Das Projekt befindet sich auf GitHub und heißt schließlich BaseXML: Ссылка

Es hat 20% Overhead, was gut für eine binäre Version ist.

Ich hatte es schwer, es mit Expat, dem hinter der Szene liegenden XML-Parser von Python (der XML1.1 NICHT UNTERSTÜTZT!), arbeiten zu lassen. So finden Sie die Binary-sichere BaseXML1.0-Version für XML1.0.

Ich werde vielleicht später die "for XML1.1" -Version freigeben (sie ist auch binär sicher und hat einen Overhead von 14,7%), sie ist fertig und funktioniert zwar, aber nutzlos mit Python-integrierten XML-Parsern. Ich möchte (noch) Leute mit zu vielen Versionen verwirren.

    
KrisWebDev 04.07.2013, 22:11
quelle
6

Danke Aya für den Asci85 Link, da gibt es sehr gute Ideen.

Ich habe sie unten für unseren Fall entwickelt.

UTF-8 Zeichen possibilites:

Für 1 Byte Zeichen (0xxxxxxx): 96 Möglichkeiten pro Byte

  • + UTF-8 ASCII-Zeichen 0xxxxxxx = + 2 ^ 7
  • - UTF-8 Kontrollzeichen 000xxxxx = -2 ^ 5
  • + XML erlaubt UTF-8 Kontrollzeichen (00000009, 0000000A, 0000000D) = +3
  • - XML-Entity nicht erlaubte Zeichen (& lt ;, & gt ;, & amp;;) = -3

BEARBEITEN: Dies ist für XML1.0-Spezifikationen. XML 1.1-Spezifikationen ermöglicht die Verwendung von Steuerzeichen außer 0x00 ...

Für 2-Byte-Zeichen (110xxxxx 10xxxxxx): 1920 Möglichkeiten pro 2 Byte

  • + UTF-8 2-Byte-Zeichen 110xxxxx 10xxxxxx = + 2 ^ 11
  • - UTF-8 illegale nicht-kanonische Zeichen (1100000x 10xxxxxx) = -2 ^ 7

Für 3-Byte-Zeichen (1110xxxx 10xxxxxx 10xxxxxx): 61440 Möglichkeiten pro 3 Byte

  • + UTF-8 3-Byte-Zeichen 1110xxxx 10xxxxxx 10xxxxxx = + 2 ^ 16
  • - UTF-8 illegale nicht-kanonische Zeichen (11100000 100xxxxx 10xxxxxx) = -2 ^ 11
  • - Unicode reservierte UTF-16-Codepunkte (11101101 101xxxxx 10xxxxxx) = -2 ^ 11

Und ich werde die Berechnung für 4-Byte-Zeichen nicht machen, das ist sinnlos: die Anzahl der möglichen wäre vernachlässigbar und es gibt zu viele illegale UTF-8-Zeichen in diesem Bereich.

Die Kodierungsmöglichkeiten in einem 3-Byte-Raum

Sehen wir uns nun an, welche Kombinationen wir für einen 3-Byte-Bereich (24 Bit) machen können:

  • 0xxxxxxx 0xxxxxxx 0xxxxxxx: Das sind 96 * 96 * 96 = 884736 Möglichkeiten
  • 0xxxxxxx 110xxxxx 10xxxxxx: Das sind 96 * 1920 = 184320 Möglichkeiten
  • 110xxxxx 10xxxxxx 0xxxxxxx: Das sind 1920 * 96 = 184320 Möglichkeiten
  • 1110xxxx 10xxxxxx 10xxxxxx: Das sind 61440 = 61440 Möglichkeiten

Es gäbe andere Möglichkeiten (wie ein 3-Byte-Char-Ende oder das Starten im Raum, aber wie 4-Byte-Zeichen, das wäre schwer zu bewerten (für mich) und wahrscheinlich vernachlässigbar).

Gesamtzahl der Möglichkeiten:

  • Ein 3-Byte-Leerzeichen hat 2 ^ 24 = 16777216 Möglichkeiten.
  • UTF-8 Kompatible Möglichkeiten in diesem Raum ist 884736 + 2 * 184320 + 61440 = 1314816 Möglichkeiten.

Wie viel Aufwand bedeutet das?

  • 24-bit Platz verwendbare Bits: Log2 (16777216) = 24 (natürlich! Das ist für das mathematische Verständnis)
  • UTF-8 nützliche Bits dieses Raums: Log2 (1314816) = 20,32 nützliche Bits.
  • Das bedeutet, dass wir 24 Bits Platz benötigen, um 20,32 Bits nützlicher Information zu kodieren, d. Der theoretische Mindestaufwand ist 18% overhead . Viel besser als Base64's 33% Overhead und Ascii85's 25% Overhead!

BEARBEITEN: Dies ist für XML1.0-Spezifikationen. Bei XML1.1 (nicht allgemein unterstützt) beträgt der theoretische Overhead 12,55%. Es gelang mir, einen binären sicheren Algorithmus mit 14,7% Overhead für XML1.1 zu erstellen.

Wie erreiche ich diesen 18% Overhead?

Die schlechte Nachricht ist, dass wir nicht so leicht 18% erreichen können, ohne ein großes "Wörterbuch" (dh lange umklingende Sätze) zu verwenden. Aber es ist einfach, 20% zu bekommen, und ziemlich einfach, aber weniger praktisch, um 19% zu bekommen.

Gute Kandidaten für Codierungslängen:

  • 6 Bits können 5 Bits mit 20% Overhead (2 ^ (6 * 0,84) & gt; 2 ^ 5)
  • codieren
  • 12 Bits können 10 Bits mit 20% Overhead (2 ^ (12 * 0,84) & gt; 2 ^ 10)
  • codieren
  • 24 Bits können 20 Bits mit 20% Overhead (2 ^ (24 * 0,84) & gt; 2 ^ 20)
  • codieren
  • 25 Bits können 21 Bits mit 19% Overhead (2 ^ (25 * 0,84) & gt; 2 ^ 21)
  • codieren

NB: 0,84 ist die durchschnittliche "Nützlichkeit" eines Raumbits (20,32 / 24).

Wie man unseren Kodierungsalgorithmus erstellt?

Wir müssen ein "Dictionary" erstellen, das die "Space-Möglichkeiten" (randoms Sequenz von Bits, deren Länge 5, 10, 20 oder 21 Bits abhängig von der gewählten Codierungslänge für den Algorithmus ist - wählen Sie einfach) in die utf8-kompatiblen Sequenzen (utf8 Bitsequenz, deren Länge entsprechend 6, 12, 24 oder 25 Bit ist).

Der einfachste Ausgangspunkt wäre die Kodierung der 20-Bit-Sequenz in 24-Bit-kompatible UTF-8-Sequenzen: Das ist genau das Beispiel, das oben zur Berechnung der Möglichkeiten genommen wurde und das sind 3 UTF-8 Bytes lang (also müssen wir nicht Sorgen über nicht abgeschlossene UTF8 Zeichen).

Beachten Sie, dass wir die 2 Byte (oder mehr) UTF-8-Zeichen verwenden müssen, um den 20% Overhead zu erreichen. Mit nur 1 Byte langen UTF8-Zeichen können wir mit RADIX-24 nur 25% Overhead erreichen. Die 3 Byte langen UTF-8-Zeichen benötigen jedoch nicht den 20% Overhead.

Das ist die nächste Herausforderung für diese Frage. Wer möchte spielen?:)

Ein Vorschlag des Algorithmus, ich werde BaseUTF-8 für XML

nennen

20 binäre Bits zum Verschlüsseln: ABCDEFGHIJKLMNOPQRST

Resultierende UTF-8-Zeichenfolge mit dem Namen "encoded": 24 Bit lang

Mathematischer Kodieralgorithmus (nicht auf irgendeiner bekannten Programmiersprache basierend):

%Vor%

Und so erhalten Sie nur 20% Overhead.

Dieser Algorithmus bietet noch keine Möglichkeit, die String-Terminierung zu verwalten, wenn die zu codierende Zeichenkette kein Vielfaches von 20 ist. Der Dekodieralgorithmus muss ebenfalls bereitgestellt werden, aber das ist ziemlich einfach (vergessen Sie nicht, zu werfen Ausnahmen, um die Uneinheitlichkeit der Decodierung zu erzwingen.)

    
KrisWebDev 27.06.2013 22:56
quelle
1

Es ist schlimmer als das: Sie haben tatsächlich nicht 215 verschiedene Bytewerte, die Sie verwenden können. Die resultierenden Binärdaten müssen in jeder Codierung gültig sein, in der die XML dargestellt wird (was mit ziemlicher Sicherheit UTF-8 ist), was bedeutet, dass viele, viele Bytefolgen verboten sind. 0xc2 gefolgt von 0x41 wäre nur ein zufälliges Beispiel. XML ist Text (eine Folge von Unicode-Zeichen), keine binären Daten. Wenn es übertragen wird, wird es mit einer Codierung codiert (die fast immer UTF-8 ist). Wenn Sie versuchen, es als Binärdaten zu behandeln, dann fragen Sie meiner Meinung nach nach mehr Ärger als es wert ist, sich damit zu befassen.

Wenn Sie das immer noch tun möchten ...

XML ist Text. Versuchen wir also nicht, Ihre Binärdaten als Binärdaten zu kodieren. Das wird nicht zu einem einfachen oder offensichtlichen Weg führen, es in ein XML-Dokument zu übertragen. Versuchen wir stattdessen, Ihre Binärdaten als Text zu codieren!

Versuchen wir eine sehr einfache Kodierung:

  • Gruppiere deine Binärdaten in Blöcke von 20 Bits
  • Kodieren Sie jede Gruppe von 20 Bits als Unicode-Zeichen U + 10000 plus den numerischen Wert der 20 Bits.

Dies bedeutet, dass Sie ausschließlich Zeichen aus den Ebenen 1 bis 16 verwenden. Alle eingeschränkten Zeichen befinden sich in Ebene 0 (der BMP), so dass Sie hier sicher sind.

Wenn Sie dieses XML-Dokument dann als UTF-8 für die Übertragung codieren, benötigt jedes dieser Zeichen 4 Bytes zum Codieren. Sie verbrauchen also 32 Bits für jede 20-Bit-Originaldaten, was 60% Overhead im Vergleich zur reinen Binärcodierung der Originaldaten darstellt. Das ist schlimmer als die 33% von base64 , was es zu einer schrecklichen Idee macht.

Dieses Codierungsschema ist leicht verschwenderisch, da es keine BMP-Zeichen verwendet. Können wir BMP-Zeichen verwenden, um es besser zu machen? Nicht trivial. 20 ist die größte Größe, die wir für die Gruppen verwenden können ( log(0x10FFFF) ~ 20.09 ). Wir könnten Schemata so umgestalten, dass sie so wenig wie möglich als BMP-Zeichen verwenden, da sie weniger Speicherplatz für die Kodierung mit UTF-8 benötigen, aber das würde die Kodierung sehr erschweren (die verbotenen Zeichen sind verstreut) ), aber es kann nur für etwa 6,25% der Bitmuster (Anteil der Unicode-Zeichen, die in der BMP sind) zur Verbesserung führen, und für die Mehrheit dieser 6,25% würden wir nur ein Byte speichern. Bei zufälligen Daten verringert sich der Overhead von 60% auf etwa 55%. Das Ergebnis wäre immer noch viel schlechter als base64, abgesehen von einigen sehr komplizierten Daten . Beachten Sie, dass der Overhead jedoch datenabhängig ist. Bei 0,2% der Bitmuster erhalten Sie tatsächlich Komprimierung anstelle von Overhead (60% Komprimierung für 0,012% der Muster und 20% Komprimierung für 0,18% der Muster). Aber diese Fraktionen sind wirklich niedrig. Es ist es einfach nicht wert.

Um es anders auszudrücken: Wenn Sie alles mit 4-Byte-UTF-8-Sequenzen codieren wollen, müssen Sie natürlich 32 Bits pro Sequenz verwenden, aber 11 dieser Bits sind fest und unveränderbar: Die Bits müssen passen das Muster 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx und dort sind nur 21 x s drin. Dieser Overhead von 60% ist in UTF-8 integriert. Wenn Sie dies als Basis für eine Codierung verwenden möchten, die den Overhead von base64 verbessert, beginnen Sie von hinten!

Ich hoffe, dass Sie davon überzeugt sind, dass Sie die Dichte von base64 nicht mit irgendeinem Schema dieses Typs verbessern können.

    
Celada 25.06.2013 17:37
quelle