CoAP
Introduction
Le protocole CoAP (Constrained Application Protocol) est un protocole de type requête/réponse, basé comme HTTP sur l’architecture REST. Le protocole est optimisé, comme son nom l’indique, pour les périphériques et les réseaux à ressources limitées. Le protocole est en particulier adapté pour les réseaux de capteurs sans fil adoptant une architecture Web of Things, à savoir une architecture Web pour les objets connectés.
Comme CoAP adopte une architecture REST, il permet d’accéder aux ressources d’objets de l’Internet par des Uniform Resource Identifiers (URI) via un modèle client-serveur. CoAP partage donc un grand nombre de similarités avec HTTP, mais il ne peut pas être considéré comme une version compressée de HTTP, car il diffère également de HTTP sur de nombreux points. CoAP est un des candidats à devenir un protocole d’application omniprésent dans le Web of Things.
Pourquoi pas HTTP ?
Étant donné les similitudes entre CoAP et HTTP, il est légitime de se demander pourquoi HTTP ne fait pas l’affaire comme protocole pour le Web of Things ? La réponse peut se résumer aux éléments suivants :
- Le protocole HTTP n’est pas approprié aux dispositifs à ressources limitées, car il est un protocole basé texte qui requiert beaucoup de mémoire pour l’encodage et le décodage et utilise de la bande passante inutilement sur les réseaux basse consommation.
- Il est basé sur la couche de transport TCP, qui est connu pour un comportement problématique sur les réseaux sans fil avec pertes 1.
- Il ne réalise pas certains mécanismes importants pour les scénarios du Web of Things, comme la communication bi-directionnelle ou la découverte des ressources ou des dispositifs.
Un protocole proposant une approche similaire à HTTP mais résolvant les problèmes mentionnés ci-dessus doit donc :
- permettre un scale down pour une réalisation sur des appareils à ressources limitées.
- permettre un scale up pour un fonctionnement avec des milliards d’appareils connectés.
Une architecture de type REST permettant le caching et un réalisation client-serveur par couches avec des intermédiaires entre client et serveur permet de réaliser le scale up. Dans ce sens, CoAP réaliser une architecture REST, tous en permettant le scale down pour une réalisation sur des dispositifs à ressources limitées.
Ces considérations ont mené à la conception et à la standardisation du protocole par un groupe de travail travaillant sur ce concept dès 2010 pour aboutir à une spécification du protocole CoAP en 2014. Les spécifications utilisent la terminologie définie pour les Constrained-Node Networks. Dans cette terminologie, trois classes de dispositifs sont définies sur la base de la mémoire RAM et de la mémoire Flash disponibles. Seuls les dispositifs de classe 2 disposant de plus de 50 KiB de RAM et de plus de 250 KiB de Flash sont considérés comme pouvant réaliser le protocole HTTP sécurisé. Les dispositifs de classe 1 disposant de plus de 10 KiB de RAM et de plus de 100 KiB de Flash sont considérés comme pouvant réaliser le protocole CoAP sécurisé. Il faut noter également que même les dispositifs de classe 2 peuvent bénéficier de l’utilisation d’un protocole comme CoAP en permettant d’économiser des ressources et de l’énergie.
Les caractéristiques principales
Le protocole CoAP a donc été réalisé en respectant les exigences suivantes :
- permettre le scale up en respectant les principes RESTful.
- être compact et permettre un décodage/encodage de faible complexité.
- réaliser un comportement adéquat dans le cas de liens de communication avec pertes.
- réaliser les mécanismes de découverte et de communication bi-directionnelle.
Pour rappel, les principes RESTful signifient :
- une architecture client-serveur permettant le caching et le proxying.
- des ressources adressables par des URI.
- une réalisation stateless (le serveur ne sauvegarde pas l’état du client).
- l’utilisation des Internet Media Types pour représenter les ressources.
Le caching des réponses permet à un intermédiaire de réutiliser une réponse passée afin de répondre à une nouvelle requête. Le proxying permet de déléguer le service d’une requête à différents serveurs. Dans une architecture RESTful, les proxies peuvent être explicitement sélectionnés par les clients (forward proxies) ou au contraire ils peuvent être ajoutés à une infrastructure existante afin de délivrer des réponses aux requêtes adressées au serveur d’origine (reverse proxies). En termes de protocoles, les proxies peuvent être CoAP-to-CoAP ou au contraire transformer les requêtes d’un protocole vers un autre protocole (par exemple HTTP-to-CoAP).
La sémantique requête-réponse
Comme HTTP, CoAP fonctionne selon le principe requête-réponse. Un endpoint CoAP agissant comme un client envoie une requête CoAP à un serveur CoAP. Le serveur répond à la requête en envoyant au client une réponse CoAP. Au contraire de HTTP, les requêtes et les réponses ne sont pas échangées sur une connexion établie pour l’échange, mais elles sont échangées de façon asynchrone à l’aide de message CoAP. La couche de transport utilisée pour échanger les messages CoAP peut varier, mais elle est souvent la couche UDP. UDP présente des meilleures performances que TCP sur des réseaux basse consommation sans fil, grâce à un overhead réduit et à l’absence de connexion. La couche de contrôle de qualité de service est donc réalisée dans la couche CoAP elle-même (dans une sous-couche de contrôle nommée message sub-layer).
Les sous-couches CoAP et le format des messages
CoAP est organisé en deux sous-couches :
La sous-couche Message a pour mission de gérer la qualité de service (retransmission et déduplication des messages). La sous-couche Request/Response a pour mission de gérer les messages CoAP suivant le modèle requête-réponse.
CoAP réalise un seul modèle de message, qui sert à échanger toute information entre le client et le serveur. Le format de ces messages est binaire et est défini comme illustré ci-dessous :
Nous pouvons noter les points suivants concernant ce format de message :
- Chaque message est précédé d’un entête de taille fixe (4 octets). Cet entête contient 1 octet pour la version (2 bits), le type (2 bits) et la taille du token (4 bits), 1 octet pour le code et 2 octets pour le Message ID. Le MID est utilisé par la sous-couche Message et est unique à un moment donné entre deux endpoints.
- L’entête est suivi d’un token qui peut avoir une taille de 0 à 8 octets. Le token est utilisé par la sous-couche Request/Response afin d’associer les réponses aux requêtes correspondantes. Un token est également unique à un moment donné entre deux endpoints.
- La section Token est suivi d’une section Options, qui peut contenir de 0 à n options. Cette section se termine avec un marker 0xFF.
- Le message contient finalement le payload.
Exercice
Exercice CoAP/1
- Installez le plug-in Copper pour votre navigateur Chrome selon les indications données sur Copper
- Au démarrage du plug-in, ouvrez l’adresse coap://appint03.tic.heia-fr.ch:5683/. Le plugin CoAP devrait s’ouvrir.
- Cliquez sur « Discover » afin d’obtenir l’ensemble des ressources disponibles sur le serveur. Cette requête est équivalent à effectuer un GET sur la ressource .well-known/core.
- Dans la fenêtre de droite, cochez la case « Debug Control ».
- Choisissez la ressource « multi-format », choisissez « text/plain » comme valeur de l’option « Accept » et observez le trafic à l’aide de WireShark. Pour générer du trafic, vous devez générer une requête à l’aide du bouton correspondant, par ex. une requête GET dans ce premier exercice. Analysez le contenu de la requête et de la réponse et expliquez ce contenu.
La sous-couche Message
La sous-couche Message est responsable de la retransmission et de la déduplication des messages. Pour ce faire, le standard définit 4 types de messages différents :
- Les messages de type CON (Confirmable Message): le message est retransmis jusqu’à ce que le destinataire confirme sa réception ou jusqu’à l’atteinte d’un timeout.
- Les messages de type ACK (Acknowledgement Message) : le message confirmant la réception d’un message de type CON. Ce message doit utiliser le même MID que le message CON correspondant. Il peut également être piggybacked et contenir le contenu de la réponse au message CON.
- Les messages de type NON (Non-confirmable Message) : le message est délivré une seule fois. Les réponses à une requête de type NON peuvent être des messages de type NON ou CON.
- Les messages de type RST (Reset Message) : le message signale une erreur de traitement d’un message reçu de type CON ou NON.
Le mécanisme de retransmission d’un message est illustré dans le diagramme ci-dessous :
Comme illustré, un message de type CON est retransmis tant que le timeout n’est pas atteint et tant que le message ACK correspondant n’est pas reçu par l’expéditeur du message CON. L’expéditeur est donc responsable de la retransmission des messages. Dans le diagramme ci-dessus, nous pouvons également constater que le même message est reçu trois fois par le récepteur. Ce message doit donc être identifié comme dupliqué et le récepteur a la mission de dédupliquer les messages reçus plusieurs fois. Dans tous les cas, le MID est utilisé afin d’identifier le message, ce qui implique qu’un MID doit être unique pour un expéditeur donné tant que ce message est en vie (non quittancé ou pouvant encore être retransmis). Le timeout de retransmission est une valeur aléatoire limitée par un nombre maximum et le temps entre les retransmissions suit le mécanisme d’exponential back-off (comme dans d’autres protocoles).
La sous-couche Request/Response
La sous-couche Request/Response réalise une architecture RESTful et partage les mêmes bases que HTTP/1.1.
Les messages CoAP contiennent tous un code, qui représente une méthode pour une requête et un statut pour une réponse. L’ensemble des codes de méthode est un sous-ensemble de 4 méthodes définies également par HTTP/1.1 : GET, PUT, POST, et DELETE. La sémantique est la même et cette similitude permet de construire facilement un mapping de et vers HTTP. Les méthodes FETCH et PATCH de HTTP ont récemment été ajoutées au protocole CoAP. Pour les réponses, le code représente le statut de la réponse. Les codes de statut sont définis par analogie avec HTTP.
Pour tous les messages, le code est défini par un nombre codé sur 8 bits.
Pour les requêtes, le code est simplement le numéro de la méthode. Pour les
réponses, le code est calculé par la formule CODE = CLASS * 32 + DETAIL
,
où la classe est une des classes suivantes :
- Success : 2
- Client error : 4
- Server error : 5
Les codes de messages ainsi que les codes HTTP correspondants sont résumés dans la table ci-dessous :
Code | Description | Remark | Correspondence to HTTP |
0.xx | Methods | ||
1 | GET | safe, idempotent | GET |
2 | POST | -- | POST |
3 | PUT | idempotent | PUT |
4 | DELETE | idempotent | DELETE |
2.xx | Success | ||
2.01 | Created | In response to POST or PUT | 201 |
2.02 | Deleted | In response to DELETE or POST | 204/200 |
2.03 | Valid | In response to GET with ETag | 304 |
2.04 | Changed | In response to POST or PUT | 204/200 |
2.05 | Content | In response to GET | 200 |
4.xx | Client Error | ||
4.00 | Bad request | 400 | |
4.01 | Unauthorized | The HTTP code 401 Unauthorized MUST NOT be used, since in CoAP there is no equivalent defined of the required WWW-Authenticate header. | 400 |
4.02 | Bad Option | For unrecoginzed of malformed options | 400 |
4.03 | Forbidden | For general denial independent from authentication | 403 |
4.04 | Not found | 404 | |
4.05 | Method Not Allowed | The HTTP code "405 Method Not Allowed" MUST NOT be used since CoAP does not provide enough information to determine a value for the required "Allow" response-header field. | 400 |
4.12 | Precondition Failed | Based on preconditions defined through options | 412 |
4.13 | Request Entity Too Large | The maximum size can be included in a response option | 413 |
4.15 | Unsupported Content-Format | Unsupported request payload | 415 |
5.xx | Server Error | ||
5.00 | Internal Server Error | 500 | |
5.01 | Not Implemented | 501 | |
5.02 | Bad Gateway | 502 | |
5.03 | Service Unavailable | 503 | |
5.04 | Gateway Timeout | 504 | |
5.05 | Proxying Not Supported | 502 |
Pour la réalisation des réponses à une requête, le standard CoAP permet d’utiliser une réponse piggybacked ou une réponse séparée. Une réponse piggybacked est une réponse qui sert également de message d’acknowledgement au message de la requête (message CON). Le message peut contenir un code de succès ou d’échec et le serveur décide seul d’utiliser le piggybacking ou non. Une réponse est ou peut être séparée si la requête est un message NON ou si le serveur a besoin de plus de temps que le temps admissible par le mécanisme d’acknowledgement pour générer la réponse. Si la réponse à une requête CON est faite séparément, alors le message d’acknowledgement est un message vide. Les réponses séparées peuvent être des messages CON ou NON. Si la réponse est un message NON, alors le client doit envoyer un message d’acknowledgement vide.
Le Token
Dans la sous-couche Request/Response, la correspondance entre une requête et une réponse est effectuée au moyen du token et non du MID (qui est lui utilisé par la sous-couche Message). Cela signifie que dans une réponse piggybacked, les MID et les token du message de requête CON et du message de réponse ACK doivent correspondre. Dans une réponse séparée, les token de la requête et de la réponse doivent correspondre.
Chaque message contient donc un token, qui peut être de longueur nulle (avec la valeur intrinsèque 0). Chaque requête utilise un token généré par le client, que le serveur doit utiliser sans modification dans la réponse à la requête. La valeur d’un token doit donc être unique parmi les tokens actifs entre une source et une destination. Si une seule requête est active, alors le token peut avoir une longueur nulle. Il faut noter que les tokens devraient être générés de manière aléatoire et non trivial, en particulier lorsque DTLS n’est pas utilisé.
Les options CoAP
Comme présenté dans le format d’un message, celui-ci peut contenir des options, que le message soit une requête ou une réponse. Chaque option est identifiée par un nombre et, comme son nom l’indique, une option n’est jamais obligatoire. Sa présence peut par contre être critique (numéro d’option impair) ou élective (numéro d’option pair). La différence entre une option critique et élective réside dans la manière dont une option non reconnue est traitée par le destinataire d’un message :
- À la réception d’un message, une option elective qui n’est pas reconnue peut être simplement ignorée.
- Une option non reconnue critique contenue dans une requête CON cause une réponse avec un code 4.02 (Bad Option).
- Une option non reconnue critique contenue dans une réponse CON ou dans une réponse piggybacked cause un rejet de la réponse.
- Une option non reconnue critique contenue dans un message NON cause un rejet du message.
En plus de leur classification en option critique ou elective, les options sont également classifiées selon différents critères influençant le caching des réponses. Ce point n’est pas traité dans les détails dans ce cours. Finalement, une option peut être répétée ou non, c’est-à-dire présente plus d’une fois dans un message ou non.
La structure des options
La structure des options CoAP est illustrée dans le diagramme ci-dessous :
Au lieu de spécifier le nombre de l’option directement, les options sont disposées dans l’ordre croissant de leur nombre d’option et un encodage par delta du nombre est utilisé : le nombre de chaque option est calculé comme étant la somme de son delta et du nombre de l’option précédente. Pour la première option, le delta de l’option est le nombre de l’option. Plusieurs instances de la même option peuvent être inclus dans un message en utilisant une valeur de delta de 0. Ce mécanisme permet de représenter les options de manière compacte et permet également à un décodeur une analyse très simple de l’ensemble des options.
De plus, dans le but d’encoder le delta et la taille de l’option efficacement, l’entête de l’option contient un seul octet, avec 4 bits réservés pour le delta et 4 bits réservés pour la taille. Si le delta ou la taille est plus petit que 13, alors 4 bits suffisent. Si l’un ou l’autre est plus grand, alors les 4-bits de l’entête peuvent contenir la valeur 13 ou 14, avec pour signification :
- valeur 13 : delta/length = entier 8-bit de valeur 13 + extended option delta/length
- valeur 14 : delta/length = entier 16-bit de valeur 269 + extended option delta/length
Exercice CoAP/2
Pourquoi avoir utilisé 13 et 14 et non 14 et 15 ? A quoi sert alors la valeur 15 ?
un encodage par delta est utilisé. Les nombres et les tailles ne sont pas encodés directement
Exercice CoAP/3
Quel est l’intérêt de l’encodage par delta ?
Les options et leurs caractéristiques
Une liste (non exhaustive) des options est donnée ci-dessous. Dans cette table, les colonnes C, U et N indiquent les propriétés Critical, Unsafe et NoCacheKey, respectivement. Comme la propriété NoCacheKey n’a de signification que pour les options qui sont Safe-to-Forward (donc non marquée Unsafe), la colonne NoCacheKey contient un - dans ce cas.
No | C | U | N | R | Name | Format | Length | Default |
1 | x | x | If-match | opaque | 0-8 | none | ||
3 | x | x | - | Uri-host | string | 1-255 | IP literal representing the destination IP address | |
4 | x | ETag | opaque | 1-8 | none | |||
5 | x | If-None_Match | empty | 0 | none | |||
7 | x | x | - | Uri-Port | uint | 0-2 | Destination UDP port | |
8 | x | Location-Path | string | 0-255 | none | |||
11 | x | x | - | x | Uri-Path | string | 0-255 | none |
12 | Content-Format | uint | 0-2 | none | ||||
14 | x | - | Max-Age | uint | 0-4 | 60 | ||
15 | x | x | - | x | Uri-Query | string | 0-255 | none |
17 | x | Accept | uint | 0-2 | none | |||
20 | x | Location-Query | string | 0-255 | none | |||
35 | x | x | - | Proxy-Uri | string | 1-1034 | none | |
39 | x | x | - | Proxy-Scheme | string | 1-255 | none | |
60 | x | Size1 | uint | 0-4 | none |
Il est intéressant de noter que les options Content-Format et Accept sont représentées à l’aide d’entiers et non de string. Les entêtes HTTP Content-Type et Content-Encoding sont fusionnées dans une seule option CoAP Content-Format et les formats sont représentés avec les nombres suivants :
Media type | ID | Reference |
---|---|---|
text/plain | 0 | [RFC2046] [RFC3676] |
application/link-format | 40 | [RFC6690] |
application/xml | 41 | [RFC3023] |
application/octet-stream | 42 | [RFC2045] [RFC2046] |
application/exi | 47 | [REC-exi-20140211] |
application/json | 50 | [RFC7159] |
Un client peut, à l’aide de l’option Accept, indiquer quel format il accepte. Cette option est critique et non répétable. Si aucune option Accept n’est présente, cela signifie que le client ne spécifie pas de préférence pour le format du contenu de la réponse. Dans ce cas, aucune valeur par défaut n’existe et le serveur peut choisir librement un format qui sera indiqué à l’aide de l’option Content-Format. Si une option Accept est spécifiée et que ce format n’est pas supporté par le serveur, alors celui-ci doit répondre avec un code de réponse 4.06 (Not Acceptable) ou 4.02 (Bad Option).
Le contenu d’une option peut avoir un des formats suivants :
- empty (vide) désigne une sequence de bytes de taille 0.
- opaque désigne une séquence opaque de bytes.
- uint désigne une entier non négatif, représenté dans le network byte order (BigEndian) avec le nombre d’octets spécifié dans le champ taille de l’option.
- string désigne une chaîne de caractères Unicode encodée en UTF-8.
Exercice
Exercice CoAP/4
Un message CoAP contient les options suivantes. Expliquer toutes les options contenues dans ce message avec leur valeur et leur signification. Calculer la taille totale des options.
Solution
Les options du message sont (dans l’ordre) :
- option avec le nombre 11 et taille 7: Uri-Path, valeur “example“
- option avec le nombre 11 et taille 4: Uri-Path, valeur “post“
- option avec le nombre 12 et taille 0: Content-Format, valeur 0 = “text/plain“
- option avec le nombre 60 et taille 2: Size1, valeur = 300
Les options occupent 18 octets.
Les options CoAP relatives aux URI
Un ensemble important d’options est celui qui concerne les URI. Avec CoAP et contrairement à HTTP, les URI sont spécifiés à l’aide d’options, qui peuvent être répétées. Le schéma d’URI pour CoAP est similaire à HTTP et peut être formulé comme :
coap-URI = “coap:” “//” host [ “:” port] path-abempty [ “?”query ]
Les différentes parties de l’URI sont spécifiées à l’aide des options suivantes :
- Uri-Host : la valeur par défaut de cette option (lorsque l’option n’est pas présente) est la valeur littérale représentant l’adresse IP de la destination.
- Uri-Port : la valeur par défaut de cette option (lorsque l’option n’est pas présente) est la valeur du port UDP de la destination.
- Uri-Path : cette option peut être répétée afin de spécifier chaque segment du chemin de l’URI de la ressource.
- Uri-Query : cette option peut être répétée afin de spécifier chaque argument paramètre de la requête.
- Proxy-Uri et Proxy-Scheme : ces options sont utilisées afin de formuler une requête vers un forward-proxy.
- Location-Path et Location-Query : lorsque qu’une ressource est créée (en principe avec une requête POST), la réponse à cette requête devrait contenir une ou plusiers options Location-Path spécifiant le chemin de la ressource créée et les éventuelles options Location-Query spécifiant les arguments pouvant être utilisés pour paramétrer une requête sur cette ressource.
Exercice
Exercice CoAP/5
Poursuivez le premier exercice permettant d’analyser le traffic CoAP afin de comprendre et d’analyser les options suivantes :
- Uri-Path : formulez une requête contenant au moins deux options répétées Uri-Path.
- Uri-Query : formulez une requête contenant au moins deux options répétées Uri-Query. Les options Uri-Query doivent être ajoutées manuellement dans le panneau Debug Control.
- Accept : formulez une requête contenant une option Accept. Utilisez une ressource qui peut délivrer une représentation dans le format choisi et une autre qui ne peut pas le faire. Observez les différences dans les réponses.
Formulez une requête afin de créer une ressource large-create avec un body content de “application/json”. Observez la réponse et comprenez la signification de l’option Location-Path. Formulez une requête afin d’obtenir une représentation de la ressource que vous venez de créer.
Les extensions CoAP
Par nature, le protocole CoAP a été conçu de façon modulaire. Ainsi, les périphériques et les serveurs peuvent réaliser uniquement les parties du protocole dont ils ont réellement besoin. Le protocole CoAP peut ainsi évoluer par la spécification d’extension, qui ne doivent pas être réalisées si non utiles.
Il est utile de mentionner quelques extensions existantes à ce stade :
- L’observation de ressources permettant de réaliser un mécanisme server push efficace.
- Le contrôle de congestion avancé (CoCoA) permettant d’adapter les timeouts de retransmission, sans réinventer TCP.
- Les transferts par blocs, permettant de séparer les payloads en blocs contenus dans plusieurs messages. Avec ce mécanisme, la fragmentation des messages est gérée par la couche CoAP elle-même et, la taille des blocs étant variable, cela permet d’éviter la fragmentation aux couches inférieures du réseau. Cela permet également de créer des représentations de ressources de manière incrémentale afin de limiter les besoins en mémoire sur le dispositif.
- Les couches de transports alternatives : bien que CoAP ait été conçu à l’origine pour fonctionner sur la couche de transport UDP, il est également possible d’utiliser d’autres couches de transport, comme le Short Message Service (SMS) ou même TCP.
L’observation de ressources
La possibilité de pouvoir observer une ressource et d’obtenir une représentation de cette ressource fiable sans interroger le service à intervalle régulier est une fonctionnalité très importante de l’IoT. Avec un tel mécanisme, les serveurs peuvent pousser des notifications vers les clients de manière efficace, en limitant le trafic et en assurant une représentation fiable des ressources au niveau des clients. La fonctionnalité d’observation a naturellement été conçue dès l’origine du protocole CoAP, comme une option elective construite sur les requêtes GET :
- Le client génère une requête GET en ajoutant une option observe avec la valeur 0.
- Si le serveur ne supporte pas l’option, il retourne une réponse sans l’option observe et le mécanisme d’observation n’est pas enclenché.
- Si le serveur supporte l’option, il retourne une réponse avec l’option observe et le mécanisme d’observation est enclenché. Le serveur ajoute alors le client dans la liste des observateurs pour la ressource. Lorsque l’état de la ressource est modifié, alors le serveur pousse une nouvelle représentation de cette ressource vers les clients observateurs. Le mécanisme requête/réponse est ainsi modifié en un mécanisme une requête/réponses multiples.
Les messages de notification peuvent faire usage du mécanisme de contrôle de cache et dans ce cas, le serveur enverra normalement un nouveau message avant l’expiration du message précédent. Les observations peuvent être annulées par le client de manière réactive (en répondant avec un message RST à un message de notification) ou proactive (en envoyant une requête de dé-enregistrement).
Exercice CoAP/6
Poursuivez l’exercice précédent permettant d’analyser le traffic CoAP afin de comprendre le mécanisme d’observation :
- Choisissez la ressource obs et effectuez une requête Observe sur cette ressource. Observez les messages transmis dans Wireshark, en particulier le contenu des options observe.
- Faîtes de même avec la ressource obs-non. Observez les différences en rapport avec la ressource obs.
- Observez la manière dont une observation peut être interrompue.