3. Création de protocoles basés sur CBOR
Les formats de données tels que CBOR sont souvent utilisés dans des environnements où il n'y a aucune négociation de format. Un objectif de conception spécifique de CBOR est de n'avoir besoin d'aucun schéma inclus ou supposé : un décodeur peut prendre un élément CBOR et le décoder sans autre connaissance.
Bien entendu, dans les implémentations réelles, l'encodeur et le décodeur partageront une vue commune de ce qui doit se trouver dans un élément de données CBOR. Par exemple, un format convenu pourrait être « l'élément est un tableau dont la première valeur est une chaîne UTF-8, la deuxième valeur est un entier, et les valeurs suivantes sont zéro ou plusieurs nombres à virgule flottante » ou « l'élément est une table qui a des chaînes d'octets pour clés et contient au moins une paire dont la clé est 0xab01 ».
Cette spécification n'impose aucune restriction aux protocoles basés sur CBOR. Un encodeur peut être capable d'encoder autant ou aussi peu de types de valeurs que requis par le protocole dans lequel il est utilisé ; un décodeur peut être capable de comprendre autant ou aussi peu de types de valeurs que requis par les protocoles dans lesquels il est utilisé. Cette absence de restrictions permet d'utiliser CBOR dans des environnements extrêmement contraints.
Cette section examine certaines considérations relatives à la création de protocoles basés sur CBOR. Elle est purement consultative et exclut explicitement tout langage de la [RFC 2119] autre que les mots qui pourraient être interprétés comme « MAY » au sens de la [RFC 2119].
3.1. CBOR dans les applications en flux continu
Dans une application en flux continu, un flux de données peut être composé d'une séquence d'éléments de données CBOR concaténés bout à bout. Dans un tel environnement, le décodeur commence immédiatement à décoder un nouvel élément de données si des données sont trouvées après la fin d'un élément de données précédent.
Il se peut que tous les octets constituant un élément de données ne soient pas immédiatement disponibles pour le décodeur ; certains décodeurs mettront en tampon des données supplémentaires jusqu'à ce qu'un élément de données complet puisse être présenté à l'application. D'autres décodeurs peuvent présenter des informations partielles sur un élément de données de premier niveau à une application, telles que les éléments de données imbriqués qui ont déjà pu être décodés, ou même des parties d'une chaîne d'octets qui n'est pas encore complètement arrivée.
Notons que certaines applications et certains protocoles ne voudront pas utiliser le codage de longueur indéfinie. L'utilisation du codage de longueur indéfinie permet à un encodeur de ne pas avoir à rassembler toutes les données pour les compter, mais elle exige qu'un décodeur alloue des quantités croissantes de mémoire en attendant la fin de l'élément. Cela peut convenir à certaines applications mais pas à d'autres.
3.2. Encodeurs et décodeurs génériques
Un décodeur CBOR générique peut décoder toutes les données CBOR bien formées et les présenter à une application. Les données CBOR sont bien formées si elles utilisent les octets initiaux, ainsi que les chaînes d'octets et/ou les éléments de données impliqués par leurs valeurs, de la manière définie par CBOR, et qu'aucune donnée superflue ne suit (Appendix C).
Même si CBOR s'efforce de minimiser ces cas, toutes les données CBOR bien formées ne sont pas valides : par exemple, le format exclut les valeurs simples inférieures à 32 qui sont encodées avec un octet d'extension. De même, des étiquettes spécifiques peuvent imposer des contraintes sémantiques susceptibles d'être violées, par exemple en incluant une étiquette dans une étiquette bignum ou en faisant suivre une chaîne d'octets à l'intérieur d'une étiquette de date. Enfin, les données peuvent être non valides, comme des chaînes UTF-8 non valides ou des chaînes de date non conformes à la [RFC3339]. Il n'est pas exigé que les encodeurs et décodeurs génériques fassent des choix non naturels pour leur interface applicative afin de permettre le traitement de données non valides. Les encodeurs et décodeurs génériques sont censés transmettre les valeurs simples et les étiquettes même si leurs points de code spécifiques ne sont pas enregistrés au moment où l'encodeur/décodeur est écrit (Section 3.5).
Les décodeurs génériques offrent des moyens de présenter à une application les valeurs CBOR bien formées, valides comme non valides. La notation diagnostique (Section 6) peut être utilisée pour présenter des valeurs CBOR bien formées à des êtres humains.
Les encodeurs génériques offrent une interface applicative qui permet à l'application de spécifier toute valeur bien formée, y compris les valeurs simples et les étiquettes inconnues de l'encodeur.
3.3. Erreurs de syntaxe
Un décodeur qui rencontre un élément de données CBOR qui n'est pas bien formé peut généralement choisir de faire échouer complètement le décodage (émettre une erreur et/ou arrêter complètement le traitement), de substituer les données et éléments de données problématiques en utilisant une convention propre au décodeur qui indique clairement qu'il y a eu un problème, ou de prendre une autre action.
3.3.1. Éléments de données CBOR incomplets
La représentation d'un élément de données CBOR a une longueur spécifique, déterminée par ses octets initiaux et par la structure des éventuels éléments de données contenus dans les éléments de données. Si moins de données sont disponibles, cela peut être traité comme une erreur de syntaxe. Un décodeur peut aussi mettre en œuvre une analyse incrémentale, c'est-à-dire décoder l'élément de données autant que les données sont disponibles et présenter les données trouvées jusqu'ici (par exemple dans une interface basée sur les événements), avec la possibilité de poursuivre le décodage une fois que d'autres données sont disponibles.
Voici des exemples d'éléments de données incomplets :
-
Un décodeur attend un certain nombre d'entrées de tableau ou de table mais rencontre à la place la fin des données.
-
Un décodeur traite ce qu'il pense être la dernière paire d'une table et arrive à la fin des données.
-
Un décodeur vient de voir une étiquette et rencontre ensuite la fin des données.
-
Un décodeur a vu le début d'un élément de longueur indéfinie mais rencontre la fin des données avant de voir le code d'arrêt « break ».
3.3.2. Éléments de longueur indéfinie malformés
Voici des exemples d'éléments de données de longueur indéfinie malformés :
-
Dans une chaîne d'octets ou un texte de longueur indéfinie, un décodeur trouve un élément qui n'est pas du type majeur approprié avant de trouver le code d'arrêt « break ».
-
Dans une table de longueur indéfinie, un décodeur rencontre le code d'arrêt « break » immédiatement après avoir lu une clé (la valeur est manquante).
Une autre erreur consiste à trouver un code d'arrêt « break » à un point des données où il n'y a aucun élément de longueur indéfinie immédiatement englobant (non fermé).
3.3.3. Valeurs d'information additionnelle inconnues
Au moment de la rédaction, certaines valeurs d'information additionnelle ne sont pas attribuées et sont réservées aux versions futures de ce document (voir Section 5.2). Comme la syntaxe globale de ces valeurs d'information additionnelle n'est pas encore définie, un décodeur qui voit une valeur d'information additionnelle qu'il ne comprend pas ne peut pas poursuivre l'analyse.
3.4. Autres erreurs de décodage
Un élément de données CBOR peut être syntaxiquement bien formé mais présenter un problème d'interprétation des données qui y sont encodées dans le modèle de données CBOR. De manière générale, un décodeur qui trouve un élément de données présentant un tel problème peut émettre un avertissement, peut arrêter complètement le traitement, peut gérer l'erreur et mettre la valeur problématique à la disposition de l'application en tant que telle, ou prendre un autre type d'action.
Ces problèmes peuvent inclure :
Clés en double dans une table : Les décodeurs génériques (Section 3.2) mettent les données à la disposition des applications en utilisant le modèle de données CBOR natif. Ce modèle de données comprend des tables (mappages clé-valeur à clés uniques), et non des multitables (mappages clé-valeur où plusieurs entrées peuvent avoir la même clé). Ainsi, un décodeur générique qui reçoit un élément de table CBOR ayant des clés en double décodera vers une table ne contenant qu'une seule instance de cette clé, ou bien il peut arrêter complètement le traitement. D'un autre côté, un « décodeur en flux continu » peut même ne pas être capable de le remarquer (Section 3.7).
Type inadmissible sur la valeur qui suit une étiquette : Les étiquettes (Section 2.4) spécifient quel type d'élément de données est censé suivre l'étiquette ; par exemple, les étiquettes pour les bignums positifs ou négatifs sont censées être apposées sur des chaînes d'octets. Un décodeur qui décode l'élément de données étiqueté en une représentation native (un entier big natif dans cet exemple) est censé vérifier le type de l'élément de données étiqueté. Même les décodeurs qui ne disposent pas de telles représentations natives dans leur environnement peuvent effectuer la vérification sur les étiquettes qui leur sont connues et réagir de manière appropriée.
Chaîne UTF-8 non valide : Un décodeur peut vouloir ou non vérifier que la séquence d'octets d'une chaîne UTF-8 (type majeur 3) est réellement de l'UTF-8 valide et réagir de manière appropriée.
3.5. Traitement des valeurs simples et des étiquettes inconnues
Un décodeur qui rencontre une valeur simple (Section 2.3) qu'il ne reconnaît pas, telle qu'une valeur qui a été ajoutée au registre IANA après le déploiement du décodeur ou une valeur que le décodeur a choisi de ne pas implémenter, peut émettre un avertissement, peut arrêter complètement le traitement, peut gérer l'erreur en mettant la valeur inconnue à la disposition de l'application en tant que telle (comme il est attendu des décodeurs génériques), ou prendre un autre type d'action.
Un décodeur qui rencontre une étiquette (Section 2.4) qu'il ne reconnaît pas, telle qu'une étiquette qui a été ajoutée au registre IANA après le déploiement du décodeur ou une étiquette que le décodeur a choisi de ne pas implémenter, peut émettre un avertissement, peut arrêter complètement le traitement, peut gérer l'erreur et présenter la valeur d'étiquette inconnue avec l'élément de données contenu à l'application (comme il est attendu des décodeurs génériques), peut ignorer l'étiquette et simplement présenter à l'application l'élément de données contenu uniquement, ou prendre un autre type d'action.
3.6. Nombres
Aux fins de la présente spécification, toutes les représentations numériques d'une même valeur numérique sont équivalentes. Cela signifie qu'un encodeur peut encoder une valeur à virgule flottante de 0,0 sous la forme de l'entier 0. Cela signifie toutefois aussi qu'une application qui s'attend à ne trouver que des valeurs entières peut trouver des valeurs à virgule flottante si l'encodeur juge celles-ci préférables, par exemple lorsque la valeur à virgule flottante est plus compacte qu'un entier de 64 bits.
Une application ou un protocole qui utilise CBOR peut restreindre les représentations des nombres. Par exemple, un protocole qui ne traite que des entiers peut stipuler que les nombres à virgule flottante ne doivent pas être utilisés et que les décodeurs de ce protocole n'ont pas besoin de pouvoir traiter les nombres à virgule flottante. De même, un protocole ou une application qui utilise CBOR peut stipuler que les décodeurs doivent pouvoir traiter l'un ou l'autre type de nombre.
Les protocoles basés sur CBOR devraient prendre en compte le fait que différents environnements de langage imposent différentes restrictions sur la plage et la précision des nombres représentables. Par exemple, le système numérique de JavaScript traite tous les nombres comme étant à virgule flottante, ce qui peut entraîner une perte silencieuse de précision lors du décodage d'entiers ayant plus de 53 bits significatifs. Un protocole qui utilise des nombres devrait définir ses attentes quant au traitement des nombres non triviaux dans les décodeurs et les applications réceptrices.
Un protocole basé sur CBOR qui inclut des nombres à virgule flottante peut restreindre les formats (demi-précision, simple précision et double précision) qui doivent être pris en charge. Pour une application qui n'utilise que des entiers, un protocole peut vouloir exclure complètement l'utilisation de valeurs à virgule flottante.
Un protocole basé sur CBOR conçu pour la compacité peut vouloir exclure des encodages d'entiers spécifiques qui sont plus longs que nécessaire pour l'application, par exemple pour éviter d'avoir à implémenter des entiers de 64 bits. Il est attendu que les encodeurs utilisent la représentation d'entier la plus compacte capable de représenter une valeur donnée. Toutefois, une application compacte devrait accepter les valeurs qui utilisent un encodage plus long que nécessaire (comme l'encodage de « 0 » en 0b000_11101 suivi de deux octets de 0x00) tant que l'application peut décoder un entier de la taille donnée.
3.7. Spécification des clés pour les tables
Les applications d'encodage et de décodage doivent s'accorder sur les types de clés qui vont être utilisés dans les tables. Dans les applications qui doivent interagir avec des applications basées sur JSON, les clés devraient probablement être limitées aux seules chaînes UTF-8 ; sinon, il faut spécifier un mappage des autres types CBOR vers les caractères Unicode, ce qui conduit souvent à des erreurs d'implémentation. Dans les applications où les clés sont de nature numérique et où l'ordre numérique des clés est important pour l'application, l'utilisation directe des nombres comme clés est utile.
Si plusieurs types de clés doivent être utilisés, il convient de réfléchir à la manière dont ces types seraient représentés dans les environnements de programmation spécifiques utilisés. Par exemple, dans les objets JavaScript, une clé entière 1 ne peut pas être distinguée d'une clé chaîne « 1 ». Cela signifie que, si des clés entières sont utilisées, l'usage simultané de clés chaîne qui ressemblent à des nombres doit être évité. Là encore, cela conduit à la conclusion que les clés devraient être d'un seul type CBOR.
Les décodeurs qui délivrent les éléments de données imbriqués dans un élément de données CBOR immédiatement après les avoir décodés (« décodeurs en flux continu ») ne conservent souvent pas l'état nécessaire pour vérifier l'unicité d'une clé dans une table. De même, un encodeur qui peut commencer à encoder des éléments de données avant que l'élément de données englobant ne soit complètement disponible (« encodeur en flux continu ») peut vouloir réduire considérablement sa charge en s'appuyant sur sa source de données pour maintenir l'unicité.
Un protocole basé sur CBOR devrait prendre une décision intentionnelle quant à ce qu'il faut faire lorsqu'une application réceptrice voit effectivement plusieurs clés identiques dans une table. La règle qui en résulte dans le protocole devrait respecter le modèle de données CBOR : elle ne peut pas prescrire un traitement spécifique des entrées ayant des clés identiques, si ce n'est qu'elle peut avoir une règle stipulant que la présence de clés identiques dans une table indique une table malformée et que le décodeur doit s'arrêter avec une erreur. Les clés en double sont également interdites par les décodeurs CBOR qui utilisent le mode strict (Section 3.10).
Le modèle de données CBOR pour les tables ne permet pas d'attribuer une sémantique à l'ordre des paires clé/valeur dans la représentation de la table. Ainsi, ce serait une très mauvaise pratique de définir un protocole basé sur CBOR de telle manière que changer l'ordre des paires clé/valeur dans une table changerait la sémantique, hormis pour des aspects triviaux (utilisation du cache, etc.). (Un protocole basé sur CBOR peut prescrire un ordre de sérialisation spécifique, par exemple pour la canonicalisation.)
Les applications pour appareils contraints qui ont des tables avec 24 clés fréquemment utilisées ou moins devraient envisager d'utiliser de petits entiers (et celles ayant jusqu'à 48 clés fréquemment utilisées devraient envisager d'utiliser aussi de petits entiers négatifs) parce que les clés peuvent alors être encodées en un seul octet.
3.8. Valeurs non définies
Dans certains protocoles basés sur CBOR, la valeur simple (Section 2.3) Undefined peut être utilisée par un encodeur comme substitut d'un élément de données ayant un problème d'encodage, afin de permettre au reste des éléments de données englobants d'être encodés sans dommage.
3.9. CBOR canonique
Certains protocoles peuvent vouloir que les encodeurs n'émettent du CBOR que dans un format canonique particulier ; ces protocoles peuvent aussi demander aux décodeurs de vérifier que leur entrée est canonique. Ces protocoles sont libres de définir ce qu'ils entendent par format canonique et ce que les encodeurs et décodeurs sont censés faire. Cette section énumère quelques suggestions pour de tels protocoles.
Si un protocole considère que « canonique » signifie que deux implémentations d'encodeur partant des mêmes données d'entrée produiront la même sortie CBOR, les quatre règles suivantes suffiraient :
-
Les entiers doivent être aussi petits que possible.
-
0 à 23 et -1 à -24 doivent être exprimés dans le même octet que le type majeur ;
-
24 à 255 et -25 à -256 doivent être exprimés uniquement avec un uint8_t additionnel ;
-
256 à 65535 et -257 à -65536 doivent être exprimés uniquement avec un uint16_t additionnel ;
-
65536 à 4294967295 et -65537 à -4294967296 doivent être exprimés uniquement avec un uint32_t additionnel.
-
-
L'expression des longueurs dans les types majeurs 2 à 5 doit être aussi courte que possible. Les règles pour ces longueurs suivent la règle ci-dessus pour les entiers.
-
Les clés dans chaque table doivent être triées de la valeur la plus basse à la valeur la plus haute. Le tri est effectué sur les octets de la représentation des éléments de données de clé sans tenir compte de la répartition des bits 3/5 pour les types majeurs. (Notons que cette règle autorise les tables qui ont des clés de types différents, même si c'est probablement une mauvaise pratique qui pourrait conduire à des erreurs dans certaines implémentations de canonicalisation.) Les règles de tri sont :
-
Si deux clés ont des longueurs différentes, la plus courte est triée en premier ;
-
Si deux clés ont la même longueur, celle qui a la valeur la plus basse dans l'ordre lexical (octet par octet) est triée en premier.
-
-
Les éléments de longueur indéfinie doivent être convertis en éléments de longueur définie.
Si un protocole autorise les flottants IEEE, des règles de canonicalisation supplémentaires pourraient devoir être ajoutées. Une règle à titre d'exemple pourrait être que tous les flottants commencent comme un flottant de 64 bits, puis effectuent une conversion d'essai en un flottant de 32 bits ; si le résultat est la même valeur numérique, utiliser la valeur la plus courte et répéter le processus avec une conversion d'essai en un flottant de 16 bits. (Cette règle sélectionne le flottant de 16 bits pour l'infini positif et négatif également.) De même, il existe de nombreuses représentations pour NaN. Si NaN est une valeur autorisée, elle doit toujours être représentée comme 0xf97e00.
Les étiquettes CBOR présentent des considérations supplémentaires pour la canonicalisation. L'absence ou la présence d'étiquettes dans un format canonique est déterminée par le caractère optionnel des étiquettes dans le protocole. Dans un protocole basé sur CBOR qui autorise l'étiquetage optionnel partout, le format canonique ne doit pas les autoriser. Dans un protocole qui exige des étiquettes à certains endroits, l'étiquette doit apparaître dans le format canonique. Un protocole basé sur CBOR qui utilise la canonicalisation peut plutôt stipuler que toutes les étiquettes qui apparaissent dans un message doivent être conservées, qu'elles soient optionnelles ou non.
3.10. Mode strict
Certains domaines d'application de CBOR n'exigent pas la canonicalisation (Section 3.9) mais peuvent exiger que différents décodeurs parviennent aux mêmes résultats (sémantiquement équivalents), même en présence de données potentiellement malveillantes. Cela peut être exigé si une application (telle qu'un pare-feu ou une autre entité protectrice) prend une décision basée sur les données sur lesquelles une autre application, qui décode les données de manière indépendante, s'appuie.
Normalement, il incombe à l'expéditeur d'éviter les données décodables de manière ambiguë. Toutefois, l'expéditeur pourrait être un attaquant fabriquant spécialement des données CBOR de telle sorte qu'elles seront interprétées différemment par différents décodeurs, afin de tenter d'exploiter cela comme une vulnérabilité. Les décodeurs génériques utilisés dans des applications où cela pourrait poser problème doivent prendre en charge un mode strict dans lequel il incombe également au récepteur de rejeter les données décodables de manière ambiguë. Il est attendu que les pare-feu et autres systèmes de sécurité qui décodent du CBOR ne décodent qu'en mode strict.
Un décodeur en mode strict rejettera de manière fiable toute donnée qui pourrait être interprétée par d'autres décodeurs de différentes manières. Il rejettera de manière fiable les éléments de données comportant des erreurs de syntaxe (Section 3.3). Il déploiera également les efforts nécessaires pour détecter de manière fiable les autres erreurs de décodage (Section 3.4). En particulier, un décodeur strict doit disposer d'une API qui signale une erreur (et ne renvoie pas de données) pour un élément de données CBOR qui contient l'un des éléments suivants :
-
une table (type majeur 5) qui a plus d'une entrée avec la même clé
-
une étiquette qui est utilisée sur un élément de données de type incorrect
-
un élément de données qui est formaté de manière incorrecte pour le type qui lui est donné, tel qu'un UTF-8 non valide ou des données qui ne peuvent pas être interprétées avec l'étiquette spécifique dont elles ont été étiquetées
Un décodeur en mode strict peut faire l'une des deux choses suivantes lorsqu'il rencontre une étiquette ou une valeur simple qu'il ne reconnaît pas :
-
Il peut signaler une erreur (et ne pas renvoyer de données).
-
Il peut émettre l'élément inconnu (type, valeur et, pour les étiquettes, l'élément de données étiqueté décodé) vers l'application qui appelle le décodeur, avec une indication que le décodeur n'a pas reconnu cette étiquette ou cette valeur simple.
Cette dernière approche, qui convient également aux décodeurs non stricts, prend en charge la compatibilité ascendante avec les étiquettes et valeurs simples nouvellement enregistrées sans qu'il soit nécessaire de mettre à jour l'encodeur en même temps que l'application appelante. (Pour cela, l'API du décodeur doit disposer d'un moyen de marquer les éléments inconnus afin que l'application appelante puisse les traiter de manière appropriée pour le programme.)
Comme une partie de ce traitement peut avoir un coût appréciable (en particulier avec la détection des doublons pour les tables), la prise en charge du mode strict n'est pas une exigence imposée à tous les décodeurs CBOR.
Certains encodeurs s'appuieront sur leurs applications pour fournir des données d'entrée de telle manière qu'il en résulte un CBOR décodable de manière non ambiguë. Un encodeur générique peut aussi vouloir fournir un mode strict dans lequel il limite de manière fiable sa sortie à du CBOR décodable de manière non ambiguë, indépendamment du fait que son application fournit ou non des données conformes à l'API.