Oktale Notation für UTF-8 (und Unicode)

Disclaimer: Ich habe über dieses Thema in einem anderen Blog zuerst gelesen. Ich kann das Posting aber einfach nicht mehr finden. Solltest du, sozusagen der originale Autor, das hier lesen, dann schreib’ mich einfach an und ich füge einen Link zu deinem Posting hinzu.

Okay, wir haben jetzt 2016 und ich habe mir endlich mal die Zeit genommen, einen einfachen Encoder und Decoder für UTF-8 zu schreiben. Ich finde, das ist eine wichtige Sache, die jeder mal gemacht haben sollte. Es fördert das Verständnis.

Wenn man anfängt, sich mit der Thematik zu beschäftigen, stößt man schnell auf Notationen wie diese hier in der Wikipedia:

hex

Hexadezimale Schreibweise. Natürlich, warum auch nicht, verwendet man ja oft, wenn man mit „rohen“ Daten zu tun hat. Es ist gängige Praxis. Leider verkompliziert hex viele Dinge, wenn es um UTF-8 geht. Schauen wir uns dazu ganz kurz an, wie UTF-8 funktioniert.

In UTF-8 gibt es „Start-Bytes“ und „Folge-Bytes“. Ein Start-Byte signalisiert, dass an dieser Stelle eine Multibytesequenz beginnt. Unmittelbar danach folgen einige Folge-Bytes. Nimmt man eine solche Folge, dann kann man sie dekodieren und erhält einen Unicode-Codepoint.

Eine Multibytesequenz kann irgendwie so aussehen:

Das war’s eigentlich auch schon. In der Theorie kann UTF-8 zwar Sequenzen aus bis zu 8 Bytes enthalten, RFC 3629 hat die erlaubte Anzahl aber auf 4 Bytes reduziert.

Die x-e oben können dann beliebige Bits enthalten. Diese Bits geben den Codepoint in Unicode an. (Manche Codepoints sind „verboten“, aber das ignorieren wir mal.) Im Wesentlichen funktioniert das so, dass der Codepoint irgendeine abstrakte Nummer ist und die Bits dieser Nummer werden dann auf Start- und Folge-Bytes verteilt. Ist der Codepoint also eine niedrige Zahl, dann braucht man nur wenige Bytes (oder nur eines) – ist er eine große Zahl, benötigt man entsprechend längere Sequenzen.

Okay, jetzt genau hinschauen. Alle Folge-Bytes enthalten immer genau 6 Bits an Information. In oktaler Schreibweise sind das genau zwei Ziffern. Die führenden Bits 10... werden immer zu einer führenden 2 in oktal.

Etwas ähnliches kann man auch mit den Start-Bytes machen. Fangen diese mit 110... oder 11110... an, dann fängt die oktale Zahl mit 3 oder 36 an und die restlichen oktalen Ziffern sind direkt Teil des Codepoints. Fängt das Start-Byte mit 1110... an, dann wird es etwas komplizierter, aber dazu unten mehr.

Was bringt das jetzt? Naja, wenn man UTF-8-kodierte Daten in oktaler Schreibweise liest, dann kann man direkt den Unicode-Codepoint ablesen, ohne etwas dekodieren zu müssen. Ein paar Beispiele:

Eine kleine Falle gibt es aber. Wenn man es mit 3-Byte-Sequenzen zu tun hat und der Codepoint oberhalb von hex <U+7FFF> liegt, dann muss man auch die zweite oktale Ziffer beachten – gewissermaßen. Beispiel:

Insgesamt ergeben sich folgende einfache Regeln:

Diese paar einfachen Regeln sollten ausreichen, um im folgenden oktalen Dump die UTF-8-Sequenzen erkennen und sogar „dekodieren“ zu können:

$ od -t o1 <data
0000000 124 150 141 164 342 200 231 163 040 141 156 040 145 170 145 155
0000020 160 154 141 162 171 040 144 165 155 160 056 040 342 200 234 110
0000040 141 154 154 303 266 143 150 145 156 054 342 200 235 040 150 145
0000060 040 163 141 151 144 056 012

Wäre die Konvention, um Unicode-Codepoints zu notieren ein oktales <U+372115> statt einem hexadezimalen <U+1F44D>, dann wäre das schon sehr praktisch. Leider ist das nicht der Fall. Natürlich wusste damals noch niemand, dass UTF-8 mal das Encoding für Unicode werden würde, also kann man den Leuten da keinen Vorwurf machen.

Das Wissen um dieses Verhalten ist trotzdem hilfreich. Wenn man oktale Dumps liest, kann man sehr einfach die Ziffern der Codepoints erfassen, muss sie dann nur nach hex umrechnen und fertig. Und selbst wenn man überhaupt nicht daran interessiert ist, etwas zu „dekodieren“, kann es hilfreich sein, das alles zu wissen: Bytes, die mit 3 oder 2 anfangen sind sehr leicht vom Rest unterscheidbar – und schon weiß man, dass das eine UTF-8-Sequenz ist. In hex ist das viel schwieriger.