3. Creazione di protocolli basati su CBOR
Formati di dati come CBOR sono spesso usati in ambienti in cui non vi è negoziazione del formato. Un obiettivo di progettazione specifico di CBOR è non aver bisogno di alcuno schema incluso o presunto: un decoder può prendere un elemento CBOR e decodificarlo senza altra conoscenza.
Naturalmente, nelle implementazioni del mondo reale, l'encoder e il decoder avranno una visione condivisa di ciò che dovrebbe essere in un elemento dati CBOR. Ad esempio, un formato concordato potrebbe essere "l'elemento è un array il cui primo valore è una stringa UTF-8, il secondo valore è un intero, e i valori successivi sono zero o più numeri in virgola mobile" oppure "l'elemento è una mappa che ha stringhe di byte come chiavi e contiene almeno una coppia la cui chiave è 0xab01".
Questa specifica non impone restrizioni ai protocolli basati su CBOR. Un encoder può essere in grado di codificare tanti o pochi tipi di valori quanti ne richiede il protocollo in cui è usato; un decoder può essere in grado di comprendere tanti o pochi tipi di valori quanti ne richiedono i protocolli in cui è usato. Questa assenza di restrizioni consente di usare CBOR in ambienti estremamente vincolati.
Questa sezione discute alcune considerazioni nella creazione di protocolli basati su CBOR. È puramente consultiva ed esclude esplicitamente qualsiasi linguaggio di RFC 2119 diverso dalle parole che potrebbero essere interpretate come "MAY" nel senso di RFC 2119.
3.1. CBOR nelle applicazioni di streaming
In un'applicazione di streaming, un flusso di dati può essere composto da una sequenza di elementi dati CBOR concatenati uno dopo l'altro. In tale ambiente, il decoder inizia immediatamente a decodificare un nuovo elemento dati se viene trovato del dato dopo la fine di un elemento dati precedente.
Non tutti i byte che compongono un elemento dati possono essere immediatamente disponibili al decoder; alcuni decoder memorizzeranno nel buffer dati aggiuntivi finché un elemento dati completo non può essere presentato all'applicazione. Altri decoder possono presentare all'applicazione informazioni parziali su un elemento dati di livello superiore, come gli elementi dati annidati che erano già decodificabili, o persino parti di una stringa di byte non ancora arrivata completamente.
Si noti che alcune applicazioni e protocolli non vorranno usare la codifica a lunghezza indefinita. L'uso della codifica a lunghezza indefinita consente a un encoder di non dover organizzare tutti i dati per il conteggio, ma richiede a un decoder di allocare quantità crescenti di memoria mentre attende la fine dell'elemento. Ciò potrebbe andare bene per alcune applicazioni ma non per altre.
3.2. Encoder e decoder generici
Un decoder CBOR generico può decodificare tutti i dati CBOR ben formati e presentarli a un'applicazione. I dati CBOR sono ben formati se utilizzano i byte iniziali, nonché le stringhe di byte e/o gli elementi dati implicati dai loro valori, nel modo definito da CBOR, e non seguono dati estranei (Appendice C).
Sebbene CBOR cerchi di ridurre al minimo questi casi, non tutti i dati CBOR ben formati sono validi: ad esempio, il formato esclude i valori semplici inferiori a 32 che sono codificati con un byte di estensione. Inoltre, tag specifici possono imporre vincoli semantici che potrebbero essere violati, come includendo un tag in un tag bignum o facendo seguire una stringa di byte all'interno di un tag data. Infine, i dati possono essere invalidi, come stringhe UTF-8 non valide o stringhe di data che non sono conformi a [RFC3339]. Non vi è alcun requisito che gli encoder e i decoder generici facciano scelte innaturali per la loro interfaccia applicativa al fine di consentire l'elaborazione di dati invalidi. Ci si aspetta che gli encoder e i decoder generici inoltrino i valori semplici e i tag anche se i loro specifici codepoint non sono registrati al momento in cui l'encoder/decoder viene scritto (Sezione 3.5).
I decoder generici forniscono modi per presentare a un'applicazione valori CBOR ben formati, sia validi sia invalidi. La notazione diagnostica (Sezione 6) può essere usata per presentare valori CBOR ben formati agli esseri umani.
Gli encoder generici forniscono un'interfaccia applicativa che consente all'applicazione di specificare qualsiasi valore ben formato, inclusi valori semplici e tag sconosciuti all'encoder.
3.3. Errori di sintassi
Un decoder che incontra un elemento dati CBOR non ben formato può generalmente scegliere di far fallire completamente la decodifica (emettere un errore e/o interrompere del tutto l'elaborazione), sostituire i dati e gli elementi dati problematici usando una convenzione specifica del decoder che indichi chiaramente che c'è stato un problema, o intraprendere qualche altra azione.
3.3.1. Elementi dati CBOR incompleti
La rappresentazione di un elemento dati CBOR ha una lunghezza specifica, determinata dai suoi byte iniziali e dalla struttura di eventuali elementi dati racchiusi negli elementi dati. Se è disponibile meno dato, ciò può essere trattato come un errore di sintassi. Un decoder può anche implementare l'analisi incrementale, cioè decodificare l'elemento dati per quanto è disponibile e presentare i dati trovati finora (ad esempio in un'interfaccia basata su eventi), con l'opzione di continuare la decodifica una volta che sono disponibili ulteriori dati.
Esempi di elementi dati incompleti includono:
-
Un decoder si aspetta un certo numero di voci di array o mappa ma invece incontra la fine dei dati.
-
Un decoder elabora quello che si aspetta sia l'ultimo paio in una mappa e giunge alla fine dei dati.
-
Un decoder ha appena visto un tag e poi incontra la fine dei dati.
-
Un decoder ha visto l'inizio di un elemento a lunghezza indefinita ma incontra la fine dei dati prima di vedere il codice di arresto "break".
3.3.2. Elementi a lunghezza indefinita malformati
Esempi di elementi dati a lunghezza indefinita malformati includono:
-
All'interno di una stringa di byte o di testo a lunghezza indefinita, un decoder trova un elemento che non è del tipo principale appropriato prima di trovare il codice di arresto "break".
-
All'interno di una mappa a lunghezza indefinita, un decoder incontra il codice di arresto "break" immediatamente dopo aver letto una chiave (il valore è mancante).
Un altro errore è trovare un codice di arresto "break" in un punto dei dati in cui non vi è alcun elemento a lunghezza indefinita che lo racchiuda immediatamente (non chiuso).
3.3.3. Valori di informazioni aggiuntive sconosciuti
Al momento della stesura, alcuni valori delle informazioni aggiuntive non sono assegnati e sono riservati a versioni future di questo documento (vedere la Sezione 5.2). Poiché la sintassi complessiva per questi valori delle informazioni aggiuntive non è ancora definita, un decoder che vede un valore delle informazioni aggiuntive che non comprende non può continuare l'analisi.
3.4. Altri errori di decodifica
Un elemento dati CBOR può essere sintatticamente ben formato ma presentare un problema nell'interpretazione dei dati in esso codificati nel modello dati CBOR. In generale, un decoder che trova un elemento dati con tale problema potrebbe emettere un avviso, potrebbe interrompere del tutto l'elaborazione, potrebbe gestire l'errore e rendere il valore problematico disponibile all'applicazione come tale, o intraprendere qualche altro tipo di azione.
Tali problemi potrebbero includere:
Chiavi duplicate in una mappa: I decoder generici (Sezione 3.2) rendono i dati disponibili alle applicazioni usando il modello dati CBOR nativo. Tale modello dati include le mappe (mappature chiave-valore con chiavi univoche), non le multimappe (mappature chiave-valore in cui più voci possono avere la stessa chiave). Quindi, un decoder generico che riceve un elemento mappa CBOR con chiavi duplicate decodificherà in una mappa con una sola istanza di quella chiave, oppure potrebbe interrompere del tutto l'elaborazione. D'altra parte, un "decoder di streaming" potrebbe persino non essere in grado di accorgersene (Sezione 3.7).
Tipo inammissibile sul valore che segue un tag: I tag (Sezione 2.4) specificano quale tipo di elemento dati dovrebbe seguire il tag; ad esempio, i tag per i bignum positivi o negativi dovrebbero essere posti su stringhe di byte. Un decoder che decodifica l'elemento dati etichettato in una rappresentazione nativa (un big integer nativo in questo esempio) dovrebbe verificare il tipo dell'elemento dati etichettato. Anche i decoder che non hanno tali rappresentazioni native disponibili nel proprio ambiente possono eseguire il controllo su quei tag a loro noti e reagire di conseguenza.
Stringa UTF-8 non valida: Un decoder potrebbe voler verificare o meno che la sequenza di byte in una stringa UTF-8 (tipo principale 3) sia effettivamente UTF-8 valido e reagire di conseguenza.
3.5. Gestione di valori semplici e tag sconosciuti
Un decoder che incontra un valore semplice (Sezione 2.3) che non riconosce, come un valore aggiunto al registro IANA dopo la distribuzione del decoder o un valore che il decoder ha scelto di non implementare, potrebbe emettere un avviso, potrebbe interrompere del tutto l'elaborazione, potrebbe gestire l'errore rendendo il valore sconosciuto disponibile all'applicazione come tale (come ci si aspetta dai decoder generici), o intraprendere qualche altro tipo di azione.
Un decoder che incontra un tag (Sezione 2.4) che non riconosce, come un tag aggiunto al registro IANA dopo la distribuzione del decoder o un tag che il decoder ha scelto di non implementare, potrebbe emettere un avviso, potrebbe interrompere del tutto l'elaborazione, potrebbe gestire l'errore e presentare all'applicazione il valore del tag sconosciuto insieme all'elemento dati contenuto (come ci si aspetta dai decoder generici), potrebbe ignorare il tag e presentare semplicemente all'applicazione il solo elemento dati contenuto, o intraprendere qualche altro tipo di azione.
3.6. Numeri
Ai fini di questa specifica, tutte le rappresentazioni numeriche per lo stesso valore numerico sono equivalenti. Ciò significa che un encoder può codificare un valore in virgola mobile 0.0 come l'intero 0. Tuttavia, significa anche che un'applicazione che si aspetta di trovare solo valori interi potrebbe trovare valori in virgola mobile se l'encoder decide che questi sono desiderabili, ad esempio quando il valore in virgola mobile è più compatto di un intero a 64 bit.
Un'applicazione o un protocollo che usa CBOR potrebbe limitare le rappresentazioni dei numeri. Ad esempio, un protocollo che si occupa solo di interi potrebbe dire che i numeri in virgola mobile non possono essere usati e che i decoder di quel protocollo non devono essere in grado di gestire numeri in virgola mobile. Analogamente, un protocollo o un'applicazione che usa CBOR potrebbe dire che i decoder devono essere in grado di gestire entrambi i tipi di numero.
I protocolli basati su CBOR dovrebbero tenere conto del fatto che ambienti linguistici diversi impongono restrizioni diverse all'intervallo e alla precisione dei numeri rappresentabili. Ad esempio, il sistema numerico di JavaScript tratta tutti i numeri come virgola mobile, il che può comportare una perdita silenziosa di precisione nella decodifica di interi con più di 53 bit significativi. Un protocollo che usa i numeri dovrebbe definire le proprie aspettative sulla gestione dei numeri non banali nei decoder e nelle applicazioni riceventi.
Un protocollo basato su CBOR che include numeri in virgola mobile può limitare quali dei tre formati (mezza precisione, precisione singola e doppia precisione) devono essere supportati. Per un'applicazione che usa solo interi, un protocollo potrebbe voler escludere completamente l'uso dei valori in virgola mobile.
Un protocollo basato su CBOR progettato per la compattezza potrebbe voler escludere specifiche codifiche intere più lunghe del necessario per l'applicazione, ad esempio per evitare la necessità di implementare interi a 64 bit. Ci si aspetta che gli encoder usino la rappresentazione intera più compatta in grado di rappresentare un dato valore. Tuttavia, un'applicazione compatta dovrebbe accettare valori che usano una codifica più lunga del necessario (come codificare "0" come 0b000_11101 seguito da due byte di 0x00) purché l'applicazione possa decodificare un intero della dimensione data.
3.7. Specifica delle chiavi per le mappe
Le applicazioni di codifica e decodifica devono concordare quali tipi di chiavi saranno usati nelle mappe. Nelle applicazioni che devono interoperare con applicazioni basate su JSON, le chiavi probabilmente dovrebbero essere limitate alle sole stringhe UTF-8; altrimenti, deve esistere una mappatura specificata dagli altri tipi CBOR ai caratteri Unicode, e ciò spesso porta a errori di implementazione. Nelle applicazioni in cui le chiavi sono di natura numerica e l'ordinamento numerico delle chiavi è importante per l'applicazione, è utile usare direttamente i numeri come chiavi.
Se devono essere usati più tipi di chiavi, si dovrebbe considerare come questi tipi sarebbero rappresentati negli specifici ambienti di programmazione che saranno usati. Ad esempio, negli oggetti JavaScript, una chiave dell'intero 1 non può essere distinta da una chiave della stringa "1". Ciò significa che, se si usano chiavi intere, deve essere evitato l'uso simultaneo di chiavi stringa che sembrano numeri. Ancora una volta, ciò porta alla conclusione che le chiavi dovrebbero essere di un unico tipo CBOR.
I decoder che consegnano gli elementi dati annidati all'interno di un elemento dati CBOR immediatamente al momento della loro decodifica ("decoder di streaming") spesso non mantengono lo stato necessario per accertare l'unicità di una chiave in una mappa. Analogamente, un encoder che può iniziare a codificare elementi dati prima che l'elemento dati che li contiene sia completamente disponibile ("encoder di streaming") potrebbe voler ridurre significativamente il proprio sovraccarico affidandosi alla propria sorgente dati per mantenere l'unicità.
Un protocollo basato su CBOR dovrebbe prendere una decisione intenzionale su cosa fare quando un'applicazione ricevente vede effettivamente più chiavi identiche in una mappa. La regola risultante nel protocollo dovrebbe rispettare il modello dati CBOR: non può prescrivere una gestione specifica delle voci con chiavi identiche, tranne per il fatto che potrebbe avere una regola secondo cui avere chiavi identiche in una mappa indica una mappa malformata e che il decoder deve interrompersi con un errore. Le chiavi duplicate sono anche proibite dai decoder CBOR che usano la modalità strict (Sezione 3.10).
Il modello dati CBOR per le mappe non consente di attribuire semantica all'ordine delle coppie chiave/valore nella rappresentazione della mappa. Pertanto, sarebbe una pessima pratica definire un protocollo basato su CBOR in modo tale che cambiare l'ordine delle coppie chiave/valore in una mappa cambi la semantica, a parte aspetti banali (uso della cache, ecc.). (Un protocollo basato su CBOR può prescrivere un ordine specifico di serializzazione, ad esempio per la canonicalizzazione.)
Le applicazioni per dispositivi vincolati che hanno mappe con 24 o meno chiavi usate di frequente dovrebbero considerare l'uso di piccoli interi (e quelle con fino a 48 chiavi usate di frequente dovrebbero considerare anche l'uso di piccoli interi negativi) perché le chiavi possono allora essere codificate in un singolo byte.
3.8. Valori indefiniti
In alcuni protocolli basati su CBOR, il valore semplice (Sezione 2.3) Undefined potrebbe essere usato da un encoder come sostituto di un elemento dati con un problema di codifica, al fine di consentire che il resto degli elementi dati che lo contengono venga codificato senza danni.
3.9. CBOR canonico
Alcuni protocolli potrebbero voler far emettere agli encoder CBOR solo in un particolare formato canonico; tali protocolli potrebbero anche far verificare ai decoder che il loro input sia canonico. Tali protocolli sono liberi di definire cosa intendono per formato canonico e cosa ci si aspetta che facciano encoder e decoder. Questa sezione elenca alcuni suggerimenti per tali protocolli.
Se un protocollo considera "canonico" il fatto che due implementazioni di encoder che partono dagli stessi dati di input producano lo stesso output CBOR, le seguenti quattro regole sarebbero sufficienti:
-
Gli interi devono essere il più piccoli possibile.
-
da 0 a 23 e da -1 a -24 devono essere espressi nello stesso byte del tipo principale;
-
da 24 a 255 e da -25 a -256 devono essere espressi solo con un uint8_t aggiuntivo;
-
da 256 a 65535 e da -257 a -65536 devono essere espressi solo con un uint16_t aggiuntivo;
-
da 65536 a 4294967295 e da -65537 a -4294967296 devono essere espressi solo con un uint32_t aggiuntivo.
-
-
L'espressione delle lunghezze nei tipi principali da 2 a 5 deve essere il più breve possibile. Le regole per queste lunghezze seguono la regola precedente per gli interi.
-
Le chiavi in ogni mappa devono essere ordinate dal valore più basso al più alto. L'ordinamento viene eseguito sui byte della rappresentazione degli elementi dati chiave senza prestare attenzione alla suddivisione 3/5 bit per i tipi principali. (Si noti che questa regola consente mappe che hanno chiavi di tipi diversi, anche se probabilmente è una cattiva pratica che potrebbe portare a errori in alcune implementazioni di canonicalizzazione.) Le regole di ordinamento sono:
-
Se due chiavi hanno lunghezze diverse, quella più corta si ordina prima;
-
Se due chiavi hanno la stessa lunghezza, quella con il valore più basso in ordine lessicale (byte per byte) si ordina prima.
-
-
Gli elementi a lunghezza indefinita devono essere trasformati in elementi a lunghezza definita.
Se un protocollo consente i float IEEE, allora potrebbero dover essere aggiunte regole di canonicalizzazione aggiuntive. Una regola di esempio potrebbe essere far iniziare tutti i float come float a 64 bit, quindi eseguire una conversione di prova in un float a 32 bit; se il risultato è lo stesso valore numerico, usare il valore più corto e ripetere il processo con una conversione di prova in un float a 16 bit. (Questa regola seleziona il float a 16 bit anche per Infinity positivo e negativo.) Inoltre, esistono molte rappresentazioni per NaN. Se NaN è un valore consentito, deve sempre essere rappresentato come 0xf97e00.
I tag CBOR presentano considerazioni aggiuntive per la canonicalizzazione. L'assenza o la presenza di tag in un formato canonico è determinata dall'opzionalità dei tag nel protocollo. In un protocollo basato su CBOR che consente l'etichettatura opzionale ovunque, il formato canonico non deve consentirli. In un protocollo che richiede i tag in determinati punti, il tag deve comparire nel formato canonico. Un protocollo basato su CBOR che usa la canonicalizzazione potrebbe invece dire che tutti i tag che compaiono in un messaggio devono essere conservati indipendentemente dal fatto che siano opzionali.
3.10. Modalità strict
Alcune aree di applicazione di CBOR non richiedono la canonicalizzazione (Sezione 3.9) ma possono richiedere che decoder diversi raggiungano gli stessi risultati (semanticamente equivalenti), anche in presenza di dati potenzialmente malevoli. Ciò può essere richiesto se un'applicazione (come un firewall o un'altra entità protettiva) prende una decisione basata sui dati su cui si basa un'altra applicazione, che decodifica i dati in modo indipendente.
Normalmente, è responsabilità del mittente evitare dati decodificabili in modo ambiguo. Tuttavia, il mittente potrebbe essere un attaccante che confeziona appositamente dati CBOR in modo che siano interpretati diversamente da decoder diversi nel tentativo di sfruttarlo come vulnerabilità. I decoder generici usati in applicazioni in cui ciò potrebbe essere un problema devono supportare una modalità strict in cui è anche responsabilità del ricevente rifiutare i dati decodificabili in modo ambiguo. Ci si aspetta che i firewall e gli altri sistemi di sicurezza che decodificano CBOR decodifichino solo in modalità strict.
Un decoder in modalità strict rifiuterà in modo affidabile qualsiasi dato che potrebbe essere interpretato da altri decoder in modi diversi. Rifiuterà in modo affidabile gli elementi dati con errori di sintassi (Sezione 3.3). Profonderà anche lo sforzo di rilevare in modo affidabile altri errori di decodifica (Sezione 3.4). In particolare, un decoder strict deve avere un'API che segnali un errore (e non restituisca dati) per un elemento dati CBOR che contiene uno qualsiasi dei seguenti:
-
una mappa (tipo principale 5) che ha più di una voce con la stessa chiave
-
un tag usato su un elemento dati di tipo errato
-
un elemento dati formattato in modo errato per il tipo a esso assegnato, come UTF-8 non valido o dati che non possono essere interpretati con lo specifico tag con cui è stato etichettato
Un decoder in modalità strict può fare una di due cose quando incontra un tag o un valore semplice che non riconosce:
-
Può segnalare un errore (e non restituire dati).
-
Può emettere l'elemento sconosciuto (tipo, valore e, per i tag, l'elemento dati etichettato decodificato) all'applicazione che chiama il decoder, con l'indicazione che il decoder non ha riconosciuto quel tag o valore semplice.
Quest'ultimo approccio, che è anche appropriato per i decoder non strict, supporta la compatibilità in avanti con tag e valori semplici di nuova registrazione senza il requisito di aggiornare l'encoder contemporaneamente all'applicazione chiamante. (A tal fine, l'API del decoder deve avere un modo per contrassegnare gli elementi sconosciuti, in modo che l'applicazione chiamante possa gestirli in modo appropriato per il programma.)
Poiché parte di questa elaborazione può avere un costo apprezzabile (in particolare con il rilevamento dei duplicati per le mappe), il supporto della modalità strict non è un requisito imposto a tutti i decoder CBOR.
Alcuni encoder si affideranno alle loro applicazioni per fornire dati di input in modo tale da ottenere risultati CBOR decodificabili in modo non ambiguo. Un encoder generico potrebbe anche voler fornire una modalità strict in cui limita in modo affidabile il proprio output a CBOR decodificabile in modo non ambiguo, indipendentemente dal fatto che la sua applicazione fornisca o meno dati conformi all'API.