joetjen.net
EN DE
Datenformat

Das Binärformat

.dxnb ist DXN in CBOR (RFC 8949) kodiert: ein Umschlag aus drei Bytes, dann ein selbstbeschreibendes CBOR-Element. Typen, die CBOR schon kennt, nutzen dessen Haupttypen oder registrierte Tags; der Rest nutzt einen kleinen Block privater Tags.

Format 1.0

Der Umschlag

Jede .dxnb-Datei beginnt mit den Bytes 44 58 — den Buchstaben DX — und einem Versionsbyte, derzeit 01. Anders als die Textkopfzeile ist der Umschlag Pflicht: Er kostet drei Bytes und lässt Werkzeuge die Datei an ihren ersten Bytes erkennen. Eine Endmarke folgt nicht; ein CBOR-Element weiß, wo es endet.

[1 2 3]
44 58 01        "DX", version 1
83 01 02 03     array(3): 1, 2, 3

Kommentare und verworfene Werte haben keine Binärform: Sie sind verschwunden, bevor es einen Wert zu kodieren gibt.

Typabbildung

TypCBORInhalt
nil, booleanSimple Values 22, 20, 21
GanzzahlHaupttyp 0/1; Tag 2/3 jenseits von 64 BitBignum-Bytes
GleitkommaHaupttyp 7IEEE-754-Double, inklusive NaN und Unendlichkeiten
Dezimalzahltag 4[exponent, mantissa]
Bruchtag 30[numerator, denominator]
ZeichenketteHaupttyp 3UTF-8
bytesHaupttyp 2rohe Bytes, kein Base64
ListeHaupttyp 4Elemente
mapHaupttyp 5Paare
ZeitstempelTag 1, nur GanzzahlMikrosekunden seit 1970
uritag 32Text
uuidtag 3716 rohe Bytes
alles andereprivate Tags 200–214siehe unten

Zwei Festlegungen hier halten Werte exakt. Ganzzahlen jenseits von 64 Bit müssen die Bignum-Tags nutzen, nie eine Gleitkommazahl. Zeitstempel müssen die Ganzzahlform von Tag 1 nutzen, nie die Gleitkommaform, die bei heutigen Daten Mikrosekunden verliert.

Private Tags

TagTypUmschließt
200Zeichenden Codepoint als Ganzzahl
201symbolText
202keywordText
203TupelArray
204arrayArray
205geordnete Mapmap
206MengeArray
207sortierte MengeArray, sortiert
208struct[name, field, …]
209DatumTage seit 1970-01-01
210ZeitMikrosekunden seit Mitternacht
211datetime[epoch microseconds, offset minutes]
212Dauereine Feld-Bitmaske, dann die vorhandenen Felder
213regex[pattern, flags]
214eigenes Tag[name, value]

Der Block liegt im Bereich mit einem Zusatzbyte, abseits der dicht belegten niedrigen Nummern. Er ist nicht bei der IANA registriert und daher nur unter DXN-Dokumenten kollisionsfrei.

Structs sind binär immer positionell — [name, feld-1, feld-2, …] —, denn Feldnamen helfen nur menschlichen Lesern. Die Textform mit Schlüsseln gibt es allein für sie.

Bit-Belegung

Die Bitmaske einer Dauer zeigt, welche Felder folgen, niedrigstes Bit zuerst; jedes vorhandene Feld folgt als vorzeichenbehaftete Ganzzahl, in Bit-Reihenfolge. Ein Regex trägt seine Flags ebenso als ein Byte. Bit 7 ist in beiden reserviert und muss 0 sein.

BitFeld der DauerRegex-Flag
0Jahrei
1Monatem
2Wochens
3Tageu
4Stundenx
5Minutenf
6Mikrosekunden (Sekunden eingerechnet)r

Byte für Byte

Jede der folgenden Ausgaben ist das, was dextrin für den Text darüber schreibt.

%{x: 1}
44 58 01        envelope
A1              map(1)
D8 CA 61 78     tag 202 (keyword) "x"
01              1
19.99M
44 58 01        envelope
C4 82           tag 4 (decimal), array(2)
21              exponent -2
19 07 CF        mantissa 1999
%Point[1, 2]
44 58 01                  envelope
D8 D0 83                  tag 208 (struct), array(3)
65 50 6F 69 6E 74         "Point"
01 02                     1, 2
@duration "P1Y2M10D"
44 58 01        envelope
D8 D4 84        tag 212 (duration), array(4)
0B              bitmask 0000 1011: years, months, days
01 02 0A        1, 2, 10
~U[2024-01-01 12:30:00Z]
44 58 01                        envelope
C1                              tag 1 (timestamp)
1B 00 06 0D E1 8A 56 A2 00      1704112200000000 µs
~r/^a+$/i
44 58 01        envelope
D8 D5 82        tag 213 (regex), array(2)
64 5E 61 2B 24  "^a+$"
01              flags 0000 0001: i

Wertteilung

Zwei optionale CBOR-Erweiterungen verkleinern Dokumente, die sich wiederholen. Encoder dürfen sie nutzen; Decoder müssen sie akzeptieren.

  • String-Referenzen — Tag 256 öffnet einen Bereich, Tag 25 verweist auf die n-te darin bereits gesehene Zeichenkette. Gedacht für wiederholte Symbole, Keywords und Struct-Namen.
  • Geteilte Werte — Tag 28 markiert ein Element als teilbar, Tag 29 verweist per Index auf ein markiertes Element. Funktioniert für jeden Wert: eine Map, eine Liste, ein Struct.

Teilen spart nur Bytes; es erzeugt nie Identität. Ein Decoder liefert für jeden Verweis eine unabhängige Kopie, das Ergebnis gleicht also einem Dokument, das alles ausschreibt. Ein Verweis auf ein noch nicht gesehenes Element oder einer, der einen Zyklus bildet, macht das Dokument ungültig: .dxnb-Werte sind immer Bäume.

Eine Adresse, dreimal verwendet — 129 Bytes, mit Teilen 75
%{
  "hq"       => %{"street" => "1 Main St", "city" => "Springfield"}
  "billing"  => %{"street" => "1 Main St", "city" => "Springfield"}
  "shipping" => %{"street" => "1 Main St", "city" => "Springfield"}
}
44 58 01                    envelope
A3                          map(3)
67 "billing"
D8 1C A2 …                  tag 28: the address map, marked (its strings too)
62 "hq"
D8 1D 04                    tag 29: marked item 4 — the map
68 "shipping"
D8 1D 04                    tag 29: marked item 4