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.
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
| Typ | CBOR | Inhalt |
|---|---|---|
| nil, boolean | Simple Values 22, 20, 21 | |
| Ganzzahl | Haupttyp 0/1; Tag 2/3 jenseits von 64 Bit | Bignum-Bytes |
| Gleitkomma | Haupttyp 7 | IEEE-754-Double, inklusive NaN und Unendlichkeiten |
| Dezimalzahl | tag 4 | [exponent, mantissa] |
| Bruch | tag 30 | [numerator, denominator] |
| Zeichenkette | Haupttyp 3 | UTF-8 |
| bytes | Haupttyp 2 | rohe Bytes, kein Base64 |
| Liste | Haupttyp 4 | Elemente |
| map | Haupttyp 5 | Paare |
| Zeitstempel | Tag 1, nur Ganzzahl | Mikrosekunden seit 1970 |
| uri | tag 32 | Text |
| uuid | tag 37 | 16 rohe Bytes |
| alles andere | private Tags 200–214 | siehe 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
| Tag | Typ | Umschließt |
|---|---|---|
| 200 | Zeichen | den Codepoint als Ganzzahl |
| 201 | symbol | Text |
| 202 | keyword | Text |
| 203 | Tupel | Array |
| 204 | array | Array |
| 205 | geordnete Map | map |
| 206 | Menge | Array |
| 207 | sortierte Menge | Array, sortiert |
| 208 | struct | [name, field, …] |
| 209 | Datum | Tage seit 1970-01-01 |
| 210 | Zeit | Mikrosekunden seit Mitternacht |
| 211 | datetime | [epoch microseconds, offset minutes] |
| 212 | Dauer | eine Feld-Bitmaske, dann die vorhandenen Felder |
| 213 | regex | [pattern, flags] |
| 214 | eigenes 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.
| Bit | Feld der Dauer | Regex-Flag |
|---|---|---|
| 0 | Jahre | i |
| 1 | Monate | m |
| 2 | Wochen | s |
| 3 | Tage | u |
| 4 | Stunden | x |
| 5 | Minuten | f |
| 6 | Mikrosekunden (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.
%{
"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