Zum Hauptinhalt springen

2. Der Stil der Datenstrukturspezifikation

CDDL konzentriert sich auf Spezifikationsstile, die in der Gemeinschaft verwendet werden, die das Datenmodell verwendet, wie es von JSON eingeführt und jetzt in CBOR verfeinert wurde.

Es gibt eine Reihe von mehr oder weniger atomaren Elementen eines CBOR-Datenmodells, wie Zahlen, einfache Werte (false, true, nil), Textzeichenfolgen und Bytezeichenfolgen; CDDL konzentriert sich nicht darauf, ihre Struktur zu spezifizieren. CDDL erlaubt natürlich auch das Hinzufügen eines CBOR-Tags zu einem Datenelement.

Jenseits dieser atomaren Elemente sind weitere Komponenten einer Datenstruktur-Definitionssprache die für die Komposition verwendeten Datentypen: Arrays und Maps in CBOR (in JSON "Arrays" und "Objekte" genannt). Während dies nur zwei Darstellungsformate sind, werden sie verwendet, um vier lose unterscheidbare Kompositionsstile zu spezifizieren:

  • Ein Vektor (Vector): Ein Array von Elementen, die größtenteils dieselbe Semantik haben. Der Satz von Signaturen, der einem signierten Datenelement zugeordnet ist, ist eine typische Anwendung eines Vektors.

  • Ein Datensatz (Record): Ein Array, dessen Elemente unterschiedliche, positionsbezogene Semantiken haben, wie in der Datenstrukturdefinition detailliert beschrieben. Ein 2D-Punkt, der als Array einer x-Koordinate (die zuerst kommt) und einer y-Koordinate (die an zweiter Stelle kommt) angegeben ist, ist ein Beispiel für einen Datensatz, ebenso wie das Paar aus Exponent (erster) und Mantisse (zweiter) in einem CBOR-Dezimalbruch.

  • Eine Tabelle (Table): Eine Map von einer Domäne von Map-Schlüsseln auf eine Domäne von Map-Werten, die größtenteils dieselbe Semantik haben. Ein Satz von Sprach-Tags, die jeweils auf eine in diese spezifische Sprache übersetzte Textzeichenfolge abgebildet werden, ist ein Beispiel für eine Tabelle. Die Schlüsseldomäne ist normalerweise nicht durch die Spezifikation auf einen bestimmten Satz beschränkt, sondern für die Anwendung offen, z. B. versucht die Spezifikation in einer Tabelle, die IP-Adressen auf Media Access Control (MAC)-Adressen abbildet, nicht, alle möglichen IP-Adressen vorherzusehen. In einer Sprache wie JavaScript würde oft eine "Map" (im Gegensatz zu einem einfachen "Object") verwendet, um die Allgemeinheit der Schlüsseldomäne zu erreichen.

  • Eine Struktur (Struct): Eine Map von einer Domäne von Map-Schlüsseln, wie durch die Spezifikation definiert, auf eine Domäne von Map-Werten, deren Semantik jeweils an einen bestimmten Map-Schlüssel gebunden ist. Das haben viele Leute im Sinn, wenn sie an JSON-Objekte denken; CBOR fügt die Fähigkeit hinzu, Map-Schlüssel zu verwenden, die nicht nur Textzeichenfolgen sind. Strukturen können verwendet werden, um Probleme zu lösen, die denen ähnlich sind, für die Datensätze verwendet werden; die Verwendung expliziter Map-Schlüssel erleichtert Optionalität und Erweiterbarkeit.

Zwei wichtige Konzepte bilden die Grundlage für CDDL:

  1. Anstatt alle vier Arten der Komposition in CDDL separat zu definieren oder sogar eine Art für Arrays (Vektoren und Datensätze) und eine Art für Maps (Tabellen und Strukturen) zu definieren, gibt es in CDDL nur eine Art der Komposition: die Gruppe (Abschnitt 2.1).

  2. Das andere wichtige Konzept ist das eines Typs. Die gesamte CDDL-Spezifikation definiert einen Typ (denjenigen, der durch ihre erste Regel definiert wird), der formal die Menge der CBOR-Datenelemente ist, die als "Instanzen" für diese Spezifikation akzeptabel sind. CDDL definiert eine Reihe von Basistypen wie "uint" (vorzeichenlose Ganzzahl) oder "tstr" (Textzeichenfolge) vor, wobei häufig eine einfache formale Notation für CBOR-Datenelemente verwendet wird. Jeder Wert, der als CBOR-Datenelement ausgedrückt werden kann, ist auch ein Typ für sich, z. B. "1". Ein Typ kann als Auswahl anderer Typen aufgebaut werden, z. B. ist ein "int" entweder ein "uint" oder ein "nint" (negative Ganzzahl). Schließlich kann ein Typ als Array oder Map aus einer Gruppe aufgebaut werden.

Der Rest dieses Abschnitts führt in eine Reihe grundlegender Konzepte von CDDL ein, und Abschnitt 3 definiert zusätzliche Syntax. Anhang C gibt eine prägnante Zusammenfassung der Semantik von CDDL.

2.1. Gruppen und Komposition in CDDL

CDDL-Gruppen sind Listen von Gruppeneinträgen, von denen jeder ein Name/Wert-Paar oder ein komplexerer Gruppenausdruck sein kann (der dann wiederum für eine Folge von Name/Wert-Paaren steht). Eine CDDL-Gruppe ist eine Produktion in einer Grammatik, die mit bestimmten Folgen von Name/Wert-Paaren übereinstimmt, mit anderen jedoch nicht. Die Grammatik basiert auf den Konzepten der Parsing Expression Grammars (PEGs) (siehe Anhang A).

In einem Array-Kontext wird nur der Wert des Name/Wert-Paares dargestellt; der Name ist nur eine Anmerkung (und kann aus der Gruppenspezifikation weggelassen werden, wenn er nicht benötigt wird). In einem Map-Kontext werden die Namen zu den Map-Schlüsseln ("Mitgliederschlüssel").

In einem Array-Kontext ist die tatsächliche Reihenfolge der Elemente in der Gruppe wichtig, da diese Reihenfolge die Information ist, die es ermöglicht, tatsächliche Array-Elemente mit Einträgen in der Gruppe zu verknüpfen. In einem Map-Kontext ist die Reihenfolge der Einträge in einer Gruppe nicht relevant (aber es besteht immer noch die Notwendigkeit, Gruppeneinträge in einer Reihenfolge aufzuschreiben).

Ein Array stimmt mit einer als Gruppe angegebenen Spezifikation überein, wenn die Gruppe mit einer Folge von Name/Wert-Paaren übereinstimmt, deren Wertteile genau mit den Elementen des Arrays in der Reihenfolge übereinstimmen.

Eine Map stimmt mit einer als Gruppe angegebenen Spezifikation überein, wenn die Gruppe mit einer Folge von Name/Wert-Paaren übereinstimmt, sodass alle diese Name/Wert-Paare in der Map vorhanden sind und die Map kein Name/Wert-Paar hat, das nicht von der Gruppe abgedeckt wird.

Ein einfaches Beispiel für die direkte Verwendung einer Gruppe in einer Map-Definition ist:

person = {
age: int,
name: tstr,
employer: tstr,
}

Abbildung 1: Direkte Verwendung einer Gruppe in einer Map

Die drei Einträge der Gruppe werden zwischen die geschweiften Klammern geschrieben, die die Map erstellen: Hier sind "age", "name" und "employer" die Namen, die zu den Map-Schlüssel-Textzeichenfolgen werden, und "int" und "tstr" (Textzeichenfolge) sind die Typen der Map-Werte unter diesen Schlüsseln.

Eine Gruppe für sich (ohne eine Map um sie herum zu erstellen) kann in (runde) Klammern gesetzt und durch Verwendung in einer Regel benannt werden:

pii = (
age: int,
name: tstr,
employer: tstr,
)

Abbildung 2: Eine Basisgruppe

Diese separate, benannte Gruppendefinition ermöglicht es uns, Abbildung 1 umzuformulieren als:

person = {
pii
}

Abbildung 3: Verwendung einer Gruppe nach Namen

Beachten Sie, dass die (geschweiften) Klammern die Erstellung einer Map bedeuten; die Gruppen selbst sind neutral, ob sie in einer Map oder einem Array verwendet werden.

Wie in Abbildung 1 gezeigt, sind die Klammern für Gruppen optional, wenn ein anderer Satz von Klammern vorhanden ist. Beachten Sie, dass sie immer noch verwendet werden können, was zu diesem nicht ganz realistischen, aber vollkommen gültigen Beispiel führt:

person = {(
age: int,
name: tstr,
employer: tstr,
)}

Abbildung 4: Verwendung einer eingeklammerten Gruppe in einer Map

Gruppen können verwendet werden, um gemeinsame Teile von Strukturen auszufaktorisieren, z. B. kann man anstatt Spezifikationen im Copy/Paste-Stil zu schreiben, wie in Abbildung 5, die gemeinsame Untergruppe ausfaktorisieren, einen Namen dafür wählen und nur die spezifischen Teile in die individuellen Maps schreiben (Abbildung 6).

person = {
age: int,
name: tstr,
employer: tstr,
}

dog = {
age: int,
name: tstr,
leash-length: float,
}

Abbildung 5: Maps mit Copy/Paste

person = {
identity,
employer: tstr,
}

dog = {
identity,
leash-length: float,
}

identity = (
age: int,
name: tstr,
)

Abbildung 6: Verwendung einer Gruppe zur Faktorisierung

Beachten Sie, dass die Listen innerhalb der geschweiften Klammern in den obigen Definitionen (anonyme) Gruppen bilden, während "identity" eine benannte Gruppe ist, die dann als Teil anderer Gruppen (anonym wie im Beispiel oder selbst benannt) eingefügt werden kann.

2.1.1. Verwendung

Gruppen sind das Instrument, das beim Komponieren von Datenstrukturen mit CDDL verwendet wird. Es ist eine Frage des Stils bei der Definition dieser Strukturen, ob Gruppen (anonym) direkt in ihren Kontexten definiert werden oder ob sie in einer separaten Regel definiert und mit ihrem jeweiligen Namen (möglicherweise mehr als einmal) referenziert werden.

Damit ist es einem erlaubt, alle kleinen Teile seiner Datenstrukturen zu definieren und größere Protokolldateneinheiten mit diesen zusammenzusetzen oder nur eine große Protokolldateneinheit zu haben, die alle Definitionen ad hoc dort hat, wo sie benötigt werden.

2.1.2. Syntax

Die Kompositionssyntax soll prägnant und leicht zu lesen sein:

  • Anfang und Ende einer Gruppe können durch "(" und ")" markiert werden.

  • Definitionen von Einträgen innerhalb einer Gruppe werden wie folgt notiert: keytype => valuetype, (lese "keytype bildet ab auf valuetype"). Das Komma ist eigentlich optional (nicht nur im letzten Eintrag), aber es gilt als guter Stil, es zu setzen. Der Doppelpfeil kann im üblichen Fall der direkten Verwendung einer Textzeichenfolge oder eines Ganzzahlliterals als Schlüssel durch einen Doppelpunkt ersetzt werden; siehe Abschnitt 3.5.1. Dies ist auch die übliche Art, Elemente eines Arrays nur zur Dokumentation zu benennen; siehe Abschnitt 3.4.

Ein grundlegender Eintrag besteht aus einem keytype und einem valuetype, die beide Typen sind (Abschnitt 2.2); dieser Eintrag stimmt mit jedem Name/Wert-Paar überein, dessen Name im keytype und dessen Wert im valuetype liegt.

Eine als Folge von Gruppeneinträgen definierte Gruppe stimmt mit jeder Folge von Name/Wert-Paaren überein, die durch Verkettung in der Reihenfolge dessen zusammengesetzt ist, womit die Einträge übereinstimmen.

Eine Gruppendefinition kann auch Auswahlen zwischen Gruppen enthalten; siehe Abschnitt 2.2.2.

2.2. Typen

2.2.1. Werte

Werte wie Zahlen und Zeichenfolgen können anstelle eines Typs verwendet werden. (Zum Beispiel ist dies für einen Schlüsseltyp sehr üblich, so üblich, dass CDDL dafür zusätzliche Komfortsyntax bietet.)

Die Wertnotation basiert auf der Sprache C, bietet jedoch nicht alle syntaktischen Variationen (siehe Anhang B für Details). Die Wertnotation für Zahlen erbt von C die Unterscheidung zwischen ganzzahligen Werten (kein Bruchteil oder Exponent angegeben -- NR1 [ISO6093]; "NR" steht für "numerical representation" (numerische Darstellung)) und Gleitkommawerten (wo ein Bruchteil, ein Exponent oder beides vorhanden sind -- NR2 oder NR3), sodass der Typ "1" keine Gleitkommazahlen enthält, während die Typen "1e3" und "1.5" beide Gleitkommazahlen sind und keine ganzen Zahlen enthalten.

2.2.2. Auswahlen (Choices)

Viele Stellen, die einen Typ zulassen, erlauben auch eine Auswahl zwischen Typen, abgegrenzt durch einen "/" (Schrägstrich). Das gesamte Auswahlkonstrukt kann in Klammern gesetzt werden, wenn dies erforderlich ist, um die Konstruktion eindeutig zu machen (bitte sehen Sie Anhang B für Details der CDDL-Grammatik).

Auswahlen von Werten können verwendet werden, um Aufzählungen auszudrücken:

attire = "bow tie" / "necktie" / "Internet attire"
protocol = 6 / 17

Analog zu Typen erlaubt CDDL auch Auswahlen zwischen Gruppen, abgegrenzt durch einen "//" (doppelter Schrägstrich). Beachten Sie, dass der "//"-Operator viel schwächer bindet als die anderen CDDL-Operatoren, sodass jede Zeile innerhalb von "delivery" im folgenden Beispiel ihre eigene Alternative in der Gruppenauswahl ist:

address = { delivery }

delivery = (
street: tstr, ? number: uint, city //
po-box: uint, city //
per-pickup: true
)

city = (
name: tstr, zip-code: uint
)

Eine Gruppenauswahl stimmt mit der Vereinigung der Mengen von Name/Wert-Paar-Folgen überein, die die Alternativen in der Auswahl können.

Sowohl für Typauswahlen als auch für Gruppenauswahlen können einer Regel später zusätzliche Alternativen in separaten Regeln hinzugefügt werden, indem "/=" bzw. "//=" anstelle von "=" verwendet wird:

attire /= "swimwear"

delivery //= (
lat: float, long: float, drone-type: tstr
)

Es ist kein Fehler, wenn ein Name zuerst mit einem "/=" oder "//=" verwendet wird (es besteht keine Notwendigkeit, ihn zuerst mit "=" zu "erstellen").

2.2.2.1. Bereiche (Ranges)

Anstatt alle Werte zu benennen, die eine Auswahl ausmachen, erlaubt CDDL das Erstellen eines Bereichs aus zwei Werten, die in einer Ordnungsbeziehung stehen: einer unteren Grenze (erster Wert) und einer oberen Grenze (zweiter Wert). Ein Bereich kann beide angegebenen Grenzen einschließen (gekennzeichnet durch Verbinden zweier Werte mit "..") oder er kann die untere Grenze einschließen und die obere Grenze ausschließen (gekennzeichnet durch Verwendung von "..."). Wenn die untere Grenze die obere Grenze überschreitet, ist der resultierende Typ die leere Menge (dieses Verhalten kann wünschenswert sein, wenn Generics (Abschnitt 3.10) verwendet werden).

device-address = byte
max-byte = 255
byte = 0..max-byte ; inklusiver Bereich
first-non-byte = 256
byte1 = 0...first-non-byte ; byte1 ist äquivalent zu byte

CDDL erlaubt derzeit nur Bereiche zwischen Ganzzahlen (übereinstimmend mit ganzzahligen Werten) oder zwischen Gleitkommawerten (übereinstimmend mit Gleitkommawerten). Wenn beides in einem Typ benötigt wird, kann eine Typauswahl zwischen den beiden Arten von Bereichen (umständlich) verwendet werden:

int-range = 0..10 ; nur Ganzzahlen stimmen überein
float-range = 0.0..10.0 ; nur Floats stimmen überein
BAD-range1 = 0..10.0 ; NICHT DEFINIERT
BAD-range2 = 0.0..10 ; NICHT DEFINIERT
numeric-range = int-range / float-range

(Siehe auch die Steuerungsoperatoren .lt/.ge und .le/.gt in Abschnitt 3.8.6.)

Beachten Sie, dass der Punkt ein gültiges Namensfortsetzungszeichen in CDDL ist, also ist

min..max

kein Bereichsausdruck, sondern ein einzelner Name. Wenn Sie einen Namen als linke Seite eines Bereichsoperators verwenden, verwenden Sie Abstände wie in

min .. max

um den Bereichsoperator abzutrennen.

2.2.2.2. Eine Gruppe in eine Auswahl verwandeln

Einige Auswahlen werden aus einer großen Anzahl von Werten erstellt, oft Ganzzahlen, denen in der Spezifikation am besten jeweils ein semantischer Name gegeben wird. Anstatt jede dieser Ganzzahlen zu benennen und sie dann in einer Auswahl anzusammeln, erlaubt CDDL das Erstellen einer Auswahl aus einer Gruppe, indem ihr ein "&"-Zeichen vorangestellt wird:

terminal-color = &basecolors
basecolors = (
black: 0, red: 1, green: 2, yellow: 3,
blue: 4, magenta: 5, cyan: 6, white: 7,
)
extended-color = &(
basecolors,
orange: 8, pink: 9, purple: 10, brown: 11,
)

Wie bei der Verwendung von Gruppen in Arrays (Abschnitt 3.4) haben die Mitgliedernamen nur dokumentarischen Wert (insbesondere könnten sie von einem Tool verwendet werden, wenn Ganzzahlen angezeigt werden, die aus dieser Auswahl stammen).

2.2.3. Repräsentationstypen

CDDL erlaubt die Spezifikation eines Datenelementtyps unter Bezugnahme auf die CBOR-Darstellung (insbesondere auf Haupttypen und Zusatzinformationen; siehe Abschnitt 2 von [RFC7049]). Wie dies verwendet wird, sollte aus dem Präludium (Anhang D) ersichtlich sein: ein Rautezeichen ("#"), optional gefolgt von einer Zahl von 0 bis 7, die den Haupttyp identifiziert, der dann von einem Punkt und einer Zahl gefolgt werden kann, die die Zusatzinformation spezifiziert. Diese Konstruktion spezifiziert die Menge der Werte, die in CBOR serialisiert werden können (d. h. "any"), durch den angegebenen Haupttyp, wenn einer angegeben ist, oder durch den angegebenen Haupttyp mit der Zusatzinformation, wenn beide angegeben sind. Wo ein Haupttyp von 6 (Tag) verwendet wird, kann der Typ des getaggten Elements durch Anhängen in Klammern spezifiziert werden.

Beachten Sie, dass diese Notation zwar auf der CBOR-Serialisierung basiert, es sich aber um eine Menge von Werten auf der Ebene des Datenmodells handelt, z. B. spezifiziert "#7.25" die Menge der Werte, die als Gleitkommazahlen mit halber Genauigkeit dargestellt werden können; es schreibt nicht vor, dass diese Werte auch als Gleitkommazahlen mit halber Genauigkeit serialisiert werden müssen: CDDL bietet keine sprachlichen Mittel, um die Wahl der Serialisierungsvarianten einzuschränken. Dies ermöglicht auch die Verwendung von CDDL mit JSON, das eine grundlegend andere Art der Serialisierung (einiger) derselben Werte verwendet.

Es kann notwendig sein, Repräsentationstypen außerhalb des Präludiums zu verwenden, z. B. könnte eine Spezifikation damit beginnen, ein vorhandenes Tag auf eine spezifischere Weise zu verwenden oder ein neues, im Präludium nicht definiertes Tag zu definieren:

my_breakfast = #6.55799(breakfast)   ; cbor-any ist zu allgemein!
breakfast = cereal / porridge
cereal = #6.998(tstr)
porridge = #6.999([liquid, solid])
liquid = milk / water
milk = 0
water = 1
solid = tstr

2.2.4. Wurzeltyp (Root Type)

Es gibt keine spezielle Syntax, um die Wurzel einer CDDL-Datenstrukturdefinition zu identifizieren: Diese Rolle wird einfach von der ersten in der Datei definierten Regel übernommen.

Dies ist durch den üblichen Top-Down-Ansatz zur Definition von Datenstrukturen motiviert, bei dem eine große Datenstruktureinheit in kleinere Teile zerlegt wird; abgesehen vom Wurzeltyp besteht jedoch keine Notwendigkeit, dieser Reihenfolge strikt zu folgen.

(Beachten Sie, dass es keine Möglichkeit gibt, eine Gruppe als Wurzel zu verwenden – es muss ein Typ sein.)