Protocole JNTP Version 0.18


Protocole JNTP Version 0.18.
Cette version rend obsolète toutes les versions précédentes.

Statut du présent mémoire

Ce document définit un nouveau protocole pour la diffusion de données sur un réseau décentralisé sur internet, et appelle à discussion et suggestions en vue de son amélioration. Il est distribué comme une RFC pour rendre ces informations facilement accessibles aux internautes. La distribution de ce document est illimitée.

Résumé

Le protocole JNTP « JSON News Transfer Protocol » définit un format d’échange de données en vue de créer un réseau décentralisé et standardisé de contenus sur internet, il permet aux différents serveurs qui constituent les nœuds de ce réseau d’échanger des données et d’en assurer la gestion. À l’origine le protocole JNTP a été créé pour les besoins du projet Nemo qui propose une interface opérationnelle pour assurer l’interopérabilité des différents réseaux et logiciels de discussion, dans toute leur diversité : webforums, newsgroups, réseaux sociaux, groupes JNTP, etc… Le projet Nemo s’articule autour d’un ensemble de technologies et de protocoles dont le protocole JNTP.

1. Introduction

Le protocole définit les règles de distribution de contenu sur un ensemble de serveurs, qui forment collectivement un réseau JNTP. Le protocole JNTP permet de transporter des messages au format texte, mais il peut également transporter tous types de médias : sons, fichiers, images, vidéos…

Le protocole JNTP s’appuie sur un mécanisme transactionnel par le biais de requêtes. Une requête JNTP ainsi que sa réponse sont toutes deux constituées d’une chaîne de caractères définie par le format de données JSON. Le protocole JNTP suppose un protocole de requêtes de la couche applicative pour encapsuler la requête JNTP et sa réponse, il s’agira généralement de HTTP. Lorsque JNTP est transporté par HTTP la requête JNTP doit être adressée en méthode POST à l’url http://serveur/jntp/ où serveur désigne le FQDN du serveur JNTP.

Toutes les commandes transmises par les clients et les serveurs JNTP sont des objets JSON. Le protocole JNTP est sensible à la casse.

1.1. Conventions Used in This Document

Les mots clés "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", et "OPTIONAL" de ce document doivent être interprétés comme décrits dans [RFC2119].

Les règles grammaticales de ce document doivent être interprétées comme décrits dans [RFC4234].

1.2. Définitions

* Un nœud JNTP ou serveur JNTP désigne un logiciel servant des requêtes qui respectent le protocole JNTP. Le logiciel doit s’exécuter sur une machine physique qui dispose d’au moins une adresse IP, d’un nom de domaine reconnu de manière globale, et d’au moins une paire de clés asymétriques. [norme X.509, protocole NTP ?]

* Un lecteur JNTP désigne un logiciel conçu pour se connecter sur un serveur JNTP et qui permet de diffuser et d’interpréter les données qui y sont transportées.

* Une Data désigne une information destinée à être transportée et diffusée sur un réseau JNTP.

* Un DataType désigne un format de données qui structure une Data.

* Un paquet JNTP désigne l’objet JSON qui encapsule une Data.

* Le Jid désigne l’identifiant unique d’un paquet sur un réseau JNTP.

* Une passerelle désigne un logiciel qui permet de dialoguer d’un côté avec un serveur JNTP, de l’autre avec un serveur de transport de données (distinct de JNTP).

2. Grammaire JNTP

Toutes les données échangées et manipulées par le protocole JNTP sont représentées en utilisant la syntaxe Javascript Object Notation (JSON) [RFC4627].

Plus précisément, un sous-ensemble de JSON est utilisé, car des restrictions sont apportées aux types ‘object’ et ‘number’.

2.1. Restrictions sur le type ‘object’

Dans la section 2.2 de JSON [RFC4627], la structure ‘object’ est définie comme un ensemble de membres qui sont des paires nom/valeur, le nom étant de type ‘string’ sans aucune restriction.

Dans JNTP, le ‘nom’ de chaque paire nom/valeur, que l’on désignera par le terme ‘clé’ dans le reste de ce document, ne peut contenir que des lettres, des chiffres, ou des caractères "-", "_" ou ".", éventuellement précédés par un "!" ou un "#".

– object = begin-object [ member *( value-separator member ) ] end-object
– member = key name-separator value
– key = quotation-mark [ directive ] key-name quotation-mark
– directive = "!" / "#"
– key-name = 1*key-char
– key-char = ALPHA / DIGIT / "-" / "_" / "."

La partie optionnelle ‘directive’ est expliquée dans la section suivante de ce document.

La partie ‘key-name’ DOIT contenir au moins 1 caractère et DEVRAIT contenir au maximum 20 caractères. Les clés «  », « ! » et « # » sont donc interdites en JNTP alors qu’elles sont autorisées en JSON.

Par ailleurs, alors que JSON demandait seulement par un SHOULD que les noms soient uniques au sein d’un même objet, dans JNTP cette contrainte est un MUST, et elle concerne la partie ‘key-name’ de la clé, indépendamment de la présence ou de l’absence d’une directive. Ainsi, une seule des trois clés "content", "!content" ou "#content" peut se trouver dans un objet donné.

2.2 Directives "!" et "#"

JNTP offre aux serveurs la possibilité de distribuer aux clients des paquets allégés, en substituant aux valeurs volumineuses de simples hashs SHA-1 (séquences de 40 caractères, plus 2 pour les guillemets). Les clients peuvent ensuite récupérer les valeurs manquantes quand ils en ont besoin.

Pour ce faire, lorsque la partie ‘valeur’ d’un couple ‘clé/valeur’ risque de devenir volumineuse, préfixer la clé de la directive "!" indique que l’on peut (et dans certains cas que l’on doit) remplacer le "!" par un "#" et en même temps remplacer la valeur par son hash SHA-1. L’algorithme de calcul de ce hash sera précisé ultérieurement, dans la section XXX.

Lorsqu’une clé commence par "#", la valeur est alors forcément de type string, et outre les guillemets de début et de fin elle DOIT contenir exactement 40 caractères pris parmi les chiffres de 0 à 9 ou parmi les lettres de a à f. Ces lettres DOIVENT être en minuscules.

– hash = quotation-mark 40hexdig quotation-mark
– hexdig = DIGIT / %x61-66 ; 0-9 or lowercase a-f

Lorsqu’une clé commence par "!", la valeur peut être n’importe quelle valeur JNTP valide, pas seulement de type string, mais de n’importe quel type, y compris l’un des types structurés object ou array.

[Note de l’auteur : décider si une clé avec "!" peut contenir dans un objet ou un sous-objet une autre clé avec "!", ou si au contraire c’est interdit ; dans chacun des deux cas, le préciser dans cette doc]

2.3. Restrictions sur le type ‘number’

Dans la section 2.4 de JSON [RFC4627], les nombres sont définis comme ayant une partie entière, qui peut être préfixée par un signe moins, et qui peut être suivie par une partie fractionnaire ou (and/or) par une partie exposant.

Dans JNTP, seuls sont autorisés les nombres entiers positifs ou négatifs, sans partie fractionnaire ni exposant.

– number = [ minus ] int
– digit1-9 = %x31-39 ; 1-9
– int = zero / ( digit1-9 *DIGIT )
– minus = %x2D ; –
– zero = %x30 ; 0

De plus, la valeur d’un entier doit être comprise au sens large entre -9007199254740992 et 9007199254740992 (-2**53 et 2**53), ce qui permet d’assurer que tout entier licite sera représentable dans le format 64-bits double-précision de la norme IEEE 754.

2.4. Forme compacte d’une valeur JNTP

Dans JNTP comme dans JSON, des caractères blancs (espaces, tabs, sauts de ligne et retours chariot) sont autorisés avant et après n’importe lesquels des six caractères structurels, ainsi qu’il est décrit dans la section 2 de JSON [RFC4627].

Dans certaines situations il est nécessaire pour JNTP de définir une forme compacte, qui est exactement la même que la forme standard sauf que la règle ‘ws’ est toujours vide. Cela revient à redéfinir les règles suivantes ainsi :

– begin-array = %x5B ; [ left square bracket
– begin-object = %x7B ; { left curly bracket
– end-array = %x5D ; ] right square bracket
– end-object = %x7D ; } right curly bracket
– name-separator = %x3A ; : colon
– value-separator = %x2C ; , comma

2.4.1. Forme compacte unique d’une valeur JNTP

Retirer les blancs optionnels ne suffit pas à rendre vraiment identiques (à l’octet près) deux objets JNTP équivalents. En effet, d’une part l’ordre des clés dans un objet n’a pas d’importance, d’autre part il existe généralement plusieurs façons de représenter un même caractère au sein d’une string.

Pour s’assurer de l’unicité de la représentation d’un objet JNTP donné, on appliquera les deux transformations suivantes, de façon récursive.

1) Trier selon les clés dans chaque objet

Dans un objet, tous les couples ‘clé/valeur’ seront triés par ordre lexicographique des clés.

Chaque clé n’étant constituée que de caractères US-ASCII [RFC0020], le tri se fera facilement car chaque caractère est représenté par un seul octet de valeur numérique comprise entre 0 et 127. Pour mémoire, voici l’ordre des caractères que l’on peut trouver dans une clé :

!#-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz

On voit en particulier que toutes les clés avec directive "!" ou "#" seront triées avant toutes les autres clés, et que l’ordre sera le même entre un paquet où aucun hash n’a été fait (que des clés sans directives ou avec directives "!") et le même paquet où tous les hashs ont été faits (que des clés sans directives ou avec directives "#"). En revanche, le tri pourrait être différent s’il y avait à la fois des clés avec directives "!" et d’autres avec directives "#" ». Dans la pratique, ce cas ne nous gênera pas car la forme compacte unique ne nous intéressera que pour les paquets où tous les hashs seront effectués.

[Note de l’auteur : tout cela est très compliqué, et je me demande s’il ne vaudrait pas mieux faire un tri sur la seule partie ‘key-name’ de la clé, même si c’est un poil plus complexe à programmer]

2) Écrire chaque string sous forme canonique

La plupart des caractères peuvent s’écrire de plusieurs façons possibles au sein d’une string. Par exemple, une tabulation peut s’écrire avec un simple caractère de valeur %x09, ou sous la forme de deux caractères "\t" (%x5C.74), ou encore sous la forme de six caractères "\u0009" (%x5C.75.30.30.30.39).

Voici donc, pour chaque caractère Unicode possible, la représentation dite ‘canonique’ qu’il FAUT utiliser pour obtenir la forme compacte unique.

a) Représentation en deux caractères pour tous les caractères qui en ont une :

%x5C.22 ; \" quotation mark U+0022
%x5C.5C ; \\ reverse solidus U+005C
%x5C.2F ; \/ solidus U+002F
%x5C.62 ; \b backspace U+0008
%x5C.66 ; \f form feed U+000C
%x5C.6E ; \n line feed U+000A
%x5C.72 ; \r carriage return U+000D
%x5C.74 ; \t tab U+0009

b) Représentation en six caractères pour tous les caractères entre U+0000 et U+001F qui n’ont pas de représentation en deux caractères, et aussi pour U+007F :
%x5C.75 4hexdig ; \uXXXX U+XXXX
[Note de l’auteur : vérifier le choix minuscules/majuscules]

c) Le caractère lui-même pour tous les autres cas.

Noter que ceci ne tient absolument pas compte des équivalences de caractères Unicode. Deux représentations Unicode différentes d’un même caractère, par exemple l’un en forme précomposée et un autre en forme décomposée, donneront deux valeurs JNTP différentes.

3. Format d’un paquet JNTP

packet = 
{ 
	"Jid": String,
	"Route": Array,
	"ID": Number,
	"ServerSign": String,
	"Data": Object,
	"Meta": Object
}

L’ordre des clés d’un paquet JNTP n’a aucune importance.
Une clé d’un paquet JNTP est une chaîne de caractères qui contient les caractères  :
"!#-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
dans la limite de 20 caractères. Les caractères "!" et "#" ne peuvent figurer que dans le premier caractère de la clé et lorsque c’est le cas ce caractère définit la directive de la clé.

On définit le chemin d’une valeur contenue dans un objet JSON par une chaîne de caractères qui contient la hiérarchie de toutes les clés parentes séparées entre elles par le caractère "/". Lorsque la valeur est incluse dans un tableau, le chemin est suffixé du caractère ":" suivi d’un entier qui identifie la position de la valeur dans le tableau, le premier élément étant indicé à 1.

Exemple avec l’objet JSON:

{
	"Name" : "Julien Arlandis", 
	"Projet RFC_v0.12" : {
		"name" : "Nemo", 
		"contributeurs" : ["Joe", "Jim", "Jack"],
		"!content" : "Ce document définit un nouveau protocole pour la diffusion de données sur un réseau décentralisé sur internet"
	}
}

"Name" désigne "Julien Arlandis",
"Projet RFC_v0.12" désigne

{ 
	"name" : "Nemo", 
	"contributeurs" : ["Joe", "Jim", "Jack"] ,
	"!content" : "Ce document définit un nouveau protocole pour la diffusion de données sur un réseau décentralisé sur internet"
},

"Projet RFC_v0.12/name" désigne "Nemo",
"Projet RFC_v0.12/contributeurs" désigne ["Joe", "Jim", "Jack"],
"Projet RFC_v0.12/contributeurs:1" désigne "Joe",
"Projet RFC_v0.12/contributeurs:2" désigne "Jim",
"Projet RFC_v0.12/contributeurs:3" désigne "Jack".
"Projet RFC_v0.12/#content" désigne "Ce document définit un nouveau protocole pour la diffusion de données sur un réseau décentralisé sur internet".

3.1. Jid (String, obligatoire)

– Jid est la chaîne de caractères qui identifie un paquet sur le réseau.
– Jid = SHA-1(minifyPacket(Data)) "@" ServerOrigin
– SHA-1 = fonction qui retourne l’empreinte numérique d’une chaîne de caractères passée en paramètres.
[Note de l’auteur : expliquer le passage des caractères aux octets (voire aux bits) utilisés par le mécanisme SHA-1.]
– ServerOrigin désigne le FQDN du nœud émetteur du paquet, sa valeur doit être identique à Data/ServerOrigin.

* Algorithme de la fonction minifyPacket

La fonction minifyPacket offre une représentation compacte et unique d’un objet JSON, voici l’algorithme de cette fonction :
– on « minifie » le tout, c’est-à-dire que l’on supprime les blancs et sauts de ligne inutiles et que l’on simplifie l’écriture (par exemple "\n" au lieu de "\u000a" et "é" au lieu de "\u00e9").
– si une clé contient la directive "!" celle-ci est remplacée par "#" et la valeur de la clé est remplacée par une empreinte SHA-1 empreinte SHA-1 du minifyPacket de sa valeur.
Cette transformation offre la possibilité aux serveurs de distribuer aux clients des paquets allégés, en substituant les valeurs volumineuses par de simples hashs SHA-1 (séquence de 40 caractères). JNTP offre ainsi la possibilité aux clients de récupérer de manière asynchrone les valeurs du paquet.
– si la clé « Data/DataID » est présente et si sa valeur est identique à la valeur du Jid alors sa valeur doit être remplacée par la chaîne vide "".
– dans les objets, on trie les éléments par ordre lexicographique des clés.

Exemple :

Data = { 
	"Name" : "Julien Arlandis", 
	"Projet RFC_v0.12" : { 
		"name" : "Nemo", 
		"contributeurs" : ["Joe", "Jim", "Jack"],
		"!content" : "Ce document définit un nouveau protocole pour la diffusion de données sur un réseau décentralisé sur internet"
	} 
}

minifyPacket(Data) = ‘{"Name":"Julien Arlandis","Projet RFC_v0.12":{"#!content":"ec360492b691b586739f535d5bde051bbbc08b66","contributeurs":["Joe","Jim","Jack"],"name":"Nemo"}}’

Le Jid est calculé par le serveur.

3.2. Route (Array, obligatoire)

La route liste dans un tableau les différents nœuds empruntés par un paquet, dans l’ordre (le plus ancien en premier, le plus récent en dernier). La Route est calculée par le serveur.

"Route": ["nemo.pasdenom.info", "news.nemoweb.net"] signifie que le paquet est passé successivement par le serveur nemo.pasdenom.info puis news.nemoweb.net.

3.3. ID (Number, obligatoire)

L’ID est un nombre entier positif qui identifie localement un paquet sur un serveur donné, l’ID est incrémenté chaque fois que le serveur insère un nouveau paquet dans sa base de données, l’ID ne doit pas dépasser la valeur 2^53-1. Il est principalement utilisé comme un moyen de pointer les paquets.

3.4. ServerSign (String, obligatoire)

Ce champ constitue la signature numérique qui garantit que le paquet a bien été émis par le serveur mentionné dans la partie droite du Jid réservé au nom de domaine du serveur.ServerSign = chiffrementAsymétrique(Jid, PrivateKey) où PrivateKey désigne la clé privée du serveur.

On peut donc vérifier que Jid = chiffrementAsymétrique(ServerSign, PublicKey).
Ce champ est calculé par le serveur.

3.5. Data (Object, obligatoire)

La Data est l’objet qui contient toutes les informations relatives au contenu publié.

Data = {
	"DataType" : String,
	"DataID" : String,
	"InjectionDate" : String,
	"OriginServer" : String,
	"ServerPublicKey" : Object
(...)
}

3.5.1. Data/DataType (String, obligatoire)

Identifie le format de données de la Data.
La structure de la Data à l’exception des clés Data/DataID, Data/InjectionDate, Data/OriginServer et Data/ServerPublicKey est entièrement définie par le format de données désigné par Data/DataType qui fera l’objet d’une spécification séparée.Le DataType « ProtoData » définit une Data qui ne fait l’objet d’aucune spécification, et dont le format de données n’obéit qu’aux seules règles définies dans le présent document.

3.5.2. Data/DataID (String, facultative)

Cette clé permet de désigner des objets identiques qui ne peuvent pas être identifiés par un même Jid faute d’une représentation unique de la Data. Par exemple il peut arriver que des données générées en dehors de JNTP soient appelées à être injectées sur JNTP sous des formes différentes et par des passerelles différentes sans qu’il ne soit possible d’assurer l’unicité du Jid pour ces objets. La clé Data/DataID permet à la fois de les identifier et d’empêcher leur redondance au sein d’un réseau JNTP.
La valeur de Data/ID doit être unique sur l’ensemble du réseau pour un DataType bien déterminé. La clé Data/DataID peut par construction du Jid (voir la fonction minifyPacket) contenir la même valeur que le Jid du paquet.

3.5.3. Data/InjectionDate (String, obligatoire)

Contient la date au format UTC à laquelle le paquet a été émis sur le réseau JNTP.
Data/InjectionDate = AAAA"-" MM "-" JJ "T" HH ":" MM ":" SS "Z"
Ce champ est renseigné par le serveur.

3.5.4. Data/OriginServer (String, obligatoire)

ServerOrigin désigne le FQDN du nœud émetteur du paquet. Ce FQDN doit disposer d’un enregistrement DNS A, AAAA ou CNAME sur le réseau internet.

3.5.5. Data/ServerPublicKey (Object, obligatoire)

Contient la clé publique du serveur correspondant à la clé privée qui a servi à signer le paquet. Contient également des informations spécifiques comme les dates de validité.
[Note de l’auteur : partie à détailler]

3.6. Meta (Object, facultatif)

Cet objet contient les informations annexes à un paquet, il s’agit de valeurs précalculées qui ont pour objectif de fournir des informations utiles aux clients ou aux autres serveurs.
Cet objet est susceptible de contenir des clés dont la description est fournie par le DataType du paquet.

4. Accès aux ressources

Toute valeur d’un paquet JNTP est identifiée par une ressource au format URI.
URI = "jntp:" Jid "/" chemin
Jid = Jid d’un paquet
chemin = chemin de la valeur tel que défini dans la présente RFC.

Exemple :
"jntp:b9512ad4721a57b5225652bbdaf381393812de11@news2.nemoweb.net@Data/Subject" pointe vers la chaîne de caractères "Protocole JNTP Version 0.17".

Pour accéder physiquement à la ressource sur un serveur JNTP donné on utilise l’URL
URL = "http://" host "/jntp/" Jid "/" chemin.

où host est le nom de domaine d’un serveur JNTP.

Exemple :
http://news.nemoweb.net/jntp/b9512ad4721a57b5225652bbdaf381393812de11@news2.nemoweb.net/Data/Subject

5. Les commandes du protocole JNTP

Syntaxe générale d’une commande JNTP :

commande = [name, query]
name = chaîne de caractère qui désigne le nom de la commande.
query = objet JSON.

5.1. get : Récupère un ou plusieurs paquets.

5.2. diffuse : Permet de diffuser ou de demander la diffusion d’un paquet ou une Data sur le réseau.Il faut distinguer deux modes de diffusion, la diffusion d’une Data et la diffusion d’un paquet.

5.2.1. Diffusion d’une Data.

Il s’agit du mode de diffusion utilisé par les clients JNTP, n’étant pas en mesure de forger eux même un paquet complet, ils transmettent à leur serveur les champs obligatoires de la Data requis par le DataType mentionné :

["diffuse", {"Data": ObjectJson}]

ObjectJson = {
	"DataType": datatype,
	(...)
}

5.2.2. Diffusion d’un paquet.

Il s’agit du mode de diffusion utilisé par les serveurs JNTP pour s’échanger les paquets :

["diffuse", {"Packet": paquet, "From": from}]

où paquet désigne un paquet JNTP,
from le FQDN du serveur qui exécute la requête.

5.2.3. Demande de diffusion d’un paquet.

Permet à un serveur d’informer un autre serveur de la nature du paquet qu’il s’apprête à lui transmettre :

["diffuse", {"Propose": partialPacket, "From": from}]

où partialPacket est un objet JSON qui contient au minimum :

– Le Jid de l’article + d’autres champs facultatifs susceptibles d’apporter une information tangible pour aider le serveur à refuser ou à accepter l’article.
Exemple :

partialPacket = {
	"Jid": Jid,
	"Meta": {
		"Size":[16000000]
	}
}

indique un paquet qui contient 16MO de données.

-La clé Data/DataID et le DataType du paquet + d’autres champs facultatifs.
Exemple :

partialPacket = {
	"Data": {
		"DataID": "m4lcm7$1ehd$1@cabale.usenet-fr.net",
		"DataType": "Article"
	}
}

5.3. auth : Permet de s’authentifier sur un serveur JNTP.

5.4. whoami : Renvoie les informations relatives à la connexion (login, session…).

5.5. quit : Permet de fermer la connexion avec le serveur JNTP.

X. References

[ECMA] European Computer Manufacturers Association, « ECMAScript Language Specification 3rd Edition », December 1999, .

[RFC0020] Cerf, V., « ASCII format for network interchange », RFC 20, October 1969.

[RFC2119] Bradner, S., « Key words for use in RFCs to Indicate Requirement Levels », BCP 14, RFC 2119, March 1997.

[RFC4234] Crocker, D. and P. Overell, « Augmented BNF for Syntax Specifications: ABNF », RFC 4234, October 2005.

[RFC4627] Crockford D., « The application/json Media Type for JavaScript Object Notation (JSON) », RFC 4627, July 2006.

[UNICODE] The Unicode Consortium, « The Unicode Standard Version 4.0 », 2003, .