> Le protocole CoAP a été créé il y a quelques années pour permettre aux objets connectés aux ressources limitées de transmettre des données de façon standardisée. Cette introduction va nous permettre de manipuler et de comprendre le protocole , puis verrons par la suite une mise en œuvre sur un microcontrôleur.
Je ne vais pas copier-coller Internet, cela ne sert à rien, car nous allons découvrir le protocole de façon pratique, par contre je mets quelques références sur le protocole en lui-même :
la RFC 7252 est la spécification "de base" du protocole. À celle-là s'ajoute une tripotée de spécifications additionnelles, les deux premières sont optionnelles, la troisième nous servira lors de nos essais :
La première option vous permettra de transférer des "gros" paquets segmentés, la deuxième vous permettra de faire l'équivalent d'un "push" : une donnée asynchrone est envoyée du serveur au client.
Voici la tête de la trame, c'est un extrait de la RFC :
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Ver| T | TKL | Code | Message ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Token (if any, TKL bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 1 1 1 1 1 1 1| Payload (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Donc, si vous avez lu les quelques documents présentés ci-dessus, vous voyez que le format d'en-tête est relativement petit, la trame minimale fait 4 octets. Voici les atouts majeurs de CoAP :
Voilà, c'est un résumé très grossier, il y a plein d'autres avantages et options/extensions possible mais dans un premier temps cela suffira.
Le site http://coap.me propose un serveur de test doté de plusieurs URL. Il est possible d'effectuer les requêtes via le site, qui est doté d'une passerelle, ou directement avec le protocole. Essayons : http://coap.me/coap://coap.me/hello retourne un "world".
Plus intéressant, voyons ce que répond l'URL de base : http://coap.me/coap://coap.me/.well-known/core. C'est une URL assez bizarre en apparence, mais néanmoins complètement standard : il s'agit de l'implémentation de la RFC6690. En quoi consiste-t-elle ? C'est très simple, elle liste les services du serveur (les URL, n'oubliez pas on est en API REST). On retrouve donc notre '/hello' précédent, et bien d'autres que nous allons découvrir.
Le format renvoyé par une requête GET sur cet URL est un peu spécifique et décrit notamment dans la RFC8288 (Web Linking). Mais en gros, pour chaque URL gérée (href) par le serveur, on peut y associer un gros tas d'attributs sous la forme d'une paire de clé/donnée. Voici quelques attributs standardisés dans CoAP :
Bref, ces attributs ne vont pas nous servir beaucoup ; cela peut être pratique pour savoir comment parler à une machine dont on ne possède pas de documentation. Notez que l'on peut a priori effectuer des recherche/filtres via l'URL.
Ok, maintenant tentons avec un client CoAP, nous en aurons besoin de toute façon pour tester notre serveur.
Quelle librairie et logiciel client choisir ? Alors là c'est assez compliqué ; au moment de l'écriture de cet article, peu d'implémentations sont en même temps vraiment complètes et bien maintenues. Il ne reste guère de choix dès que l'on requiert du DTLS, c'est un minimum en 2018.
Au bout du compte, on a :
Bien que l'on pourrait faire nos essais avec libcoap uniquement, il est toujours intéressant de tester un dialogue client/serveur avec deux implémentations différentes : si ça communique, on peut dire que le système doit globalement respecter la norme.
Commençons avec coap-shell. Il suffit de télécharger le binaire pré-compilé (un fichier JAR, l'URL se trouve dans le README à la racine du dépôt), puis de lancer la console interactive via la commande ava -jar coap-shell-1.0.7.jar. Connectez-vous au serveur, puis lorsque l'on lance la commande de découverte, on obtient les mêmes informations que via le site web.
server-unknown:>connect coap://coap.me
available
coap://coap.me:>discover
Que se passe-t-il au niveau du protocole ? Regardons un exemple tiré de la RFC :
REQ: GET /.well-known/core
RES: 2.05 Content
;rt="temperature-c";if="sensor",
;rt="light-lux";if="sensor"
Cette requête de découverte nous retourne deux URLs situées sous '/sensors' permettant a priori de récupérer les données de plusieurs capteurs.
Réalisons un simple get pour obtenir les données de l'URL /hello :
coap://coap.me:>get /hello
----------------------------------- Response -----------------------------------
GET coap://coap.me/hello
MID: 18961, Type: ACK, Token: [98d43c14048de4a1], RTT: 54ms
Options: {"Content-Format":"text/plain"}
Status : 205-Reset Content, Payload: 5B
................................... Payload ....................................
world
--------------------------------------------------------------------------------
Voyons avec Wireshark ce que donne cette requête :
Nous avons ici la pile de protocoles classique longue de 69 octets :
Notez que pour des paquets encore plus courts il faudrait passer sur de l'IPv6 et une une en-tête compressée 6LowPAN. Le contenu de la trame CoAP ne contient que peu d'informations :
La place est prise essentiellement par l'URI hello, le nom de l'hôte coap.me et un token sur 8 octets. Ce 'token' est généré par le client, le serveur ne faisant que le recopier. Il sert au client à trier les réponses reçues en fonction de son applicatif. Cet token s'avérera utile pour une utilisation en observateur: il indiquera la nature la trame reçue de façon asynchrone. Le message ID est quant à lui plus utilisé au niveau protocolaire pour détecter des paquets re-transmis. On peut ajouter tout un tas d'options à la trame (le token est lui aussi une option !).
Voici un essai avec la méthode PUT, permettant d'envoyer des données :
coap://coap.me:>put /sink --payload "Coucou"
----------------------------------- Response -----------------------------------
PUT coap://coap.me/sink
MID: 48890, Type: ACK, Token: [8ccee7e647b4990d], RTT: 55ms
Options: {"Content-Format":"text/plain"}
Status : 204-No Content, Payload: 6B
................................... Payload ....................................
PUT OK
--------------------------------------------------------------------------------
coap://coap.me:>get /sink
----------------------------------- Response -----------------------------------
GET coap://coap.me/sink
MID: 48891, Type: ACK, Token: [a234b4173035967e], RTT: 55ms
Options: {"ETag":0xb9a6b9ae201a9d71, "Content-Format":"text/plain"}
Status : 205-Reset Content, Payload: 20B
................................... Payload ....................................
you put here: Coucou
--------------------------------------------------------------------------------
Dernier détail rigolo du protocole, l'URI est découpée en options. Exit les slash, chaque "répertoire" à la REST est envoyé sous forme d'option. Cela facilite le travail du serveur et on comprend bien l'orientation très REST du protocole. Par exemple ici nous avons réalisé un GET /seg1/seg2/seg3 :
Voilà un bref tour de la partie protocolaire de CoAP, orienté application comme le HTTP, mais en "compressé".
Essayons maintenant un autre serveur hébergé chez la fondation Eclipse. On se connecte et on liste les services observables.
connect coap://californium.eclipse.org
discover --query obs
┌────────────────┬──────────────────┬─────────────────┬──────────────┬─────────┬────────────────┐
│Path [href] │Resource Type [rt]│Content Type [ct]│Interface [if]│Size [sz]│Observable [obs]│
├────────────────┼──────────────────┼─────────────────┼──────────────┼─────────┼────────────────┤
│/obs │observe │text/plain (0) │ │ │observable │
│/obs-large │observe │ │ │ │observable │
│/obs-non │observe │ │ │ │observable │
│/obs-pumping │observe │ │ │ │observable │
│/obs-pumping-non│observe │ │ │ │observable │
└────────────────┴──────────────────┴─────────────────┴──────────────┴─────────┴────────────────┘
Ok, un get /obs nous renvoie l'heure, c'est un serveur de temps.
oap://californium.eclipse.org:>get /obs
----------------------------------- Response -----------------------------------
GET coap://californium.eclipse.org/obs
MID: 37018, Type: ACK, Token: [ee175ae6ba1c2ec3], RTT: 124ms
Options: {"Content-Format":"text/plain", "Max-Age":5}
Status : 205-Reset Content, Payload: 8B
................................... Payload ....................................
20:30:37
--------------------------------------------------------------------------------
Maintenant, enregistrons nous pour recevoir des trames dès que l'heure change.
coap://californium.eclipse.org:>observe /obs
OBSERVE START coap://californium.eclipse.org/obs
Attendons un peu, et listons les messages reçus :
coap://californium.eclipse.org[OBS]:>observe show messages
----------------------------------- Response -----------------------------------
OBSERVE Response (coap://californium.eclipse.org/obs):
MID: 37021, Type: ACK, Token: [1f765efaeebc7cc2], RTT: 199ms
Options: {"Observe":155554, "Content-Format":"text/plain", "Max-Age":5}
Status : 205-Reset Content, Payload: 8B
................................... Payload ....................................
20:37:22
--------------------------------------------------------------------------------
----------------------------------- Response -----------------------------------
OBSERVE Response (coap://californium.eclipse.org/obs):
MID: 50642, Type: CON, Token: [1f765efaeebc7cc2], RTT: 1ms
Options: {"Observe":155555, "Content-Format":"text/plain", "Max-Age":5}
Status : 205-Reset Content, Payload: 8B
................................... Payload ....................................
20:37:27
--------------------------------------------------------------------------------
----------------------------------- Response -----------------------------------
OBSERVE Response (coap://californium.eclipse.org/obs):
MID: 50643, Type: CON, Token: [1f765efaeebc7cc2], RTT: 0ms
Options: {"Observe":155556, "Content-Format":"text/plain", "Max-Age":5}
Status : 205-Reset Content, Payload: 8B
................................... Payload ....................................
20:37:32
--------------------------------------------------------------------------------
Nous avons reçu trois messages espacés de 5 secondes. C'est le serveur CoAP qui décide de la fréquence d'envoi, soit en dur dans le code soit on peut imaginer un système de configuration.
Dans un prochain article, nous mettrons en œuvre le protocole côté serveur, en C sur PC, et nous le validerons avec un client.