CoAP (Constrained Application Protocol) является бинарным аналогом протокола HTTP, но в отличии от последнего предназначен для других целей, а именно межмашинного взаимодействия в устройствах интернета вещей.

На базе CoAP создан протокол LwM2M (Lightweight Machine to Machine). Исчерпывающее описание CoAP нужно искать в описании стандарта RFC7252 .

Здесь я постараюсь в краткой и доступной форме акцентировать ваше внимание на основных особенностях данного протокола.

Прежде всего нужно разобраться в практической стороне вопроса, где и для каких целей можно применить данный протокол.

Кроме IoT устройств CoAP можно использовать для обновления прошивки микроконтроллеров с Ethernet/WiFi интерфейсом. Для этих целей существует расширение CoAP Block -Wise Transfers, описанное в  RFC7959 .

Суть его в использовании блоков данных фиксированного размера для передачи большого объема данных, например файлов.

В конце каждого блока есть флаг продолжения (More bit), который указывает был ли данный блок последним(единственным) в цепочке передаваемых(принимаемых) блоков или нет.

Те-же возможности предоставляет HTTP протокол с поддержкой порционной передачи данных (с указанием размера блока и смещения внутри файла), однако не все HTTP сервера могут поддерживать порционную отправку данных.

Кроме того CoAP протокол изначально базировался только на UDP протоколе без установления соединения, что упрощало его реализацию на устройствах с ограниченными ресурсами. 

Немного позже в CoAP была добавлена поддержка TCP и WebSockets, описанная в RFC8323.

Все ресурсы (например файл прошивки, показания датчика температуры, влажности и т.д) в CoAP представлены в виде URI(Universal Resource Identifier), CoAP не регламентирует структуру URI (в отличии от LwM2M).

Например, это может быть путь к файлу прошивки на сервере /data/firmware или значение измерения температуры в датчике /DHT11/temperature, либо влажности /DHT11/humidity.

Также CoAP не регламентирует архитектуру сервера и клиента в отличии от того-же LwM2M. Например,  для считывания значения с датчиков, подключенных к устройству, устройство может работать как сервер, а для загрузки обновленной версии прошивки то-же устройство может выступать в роли клиента , обращающегося к серверу, на котором расположена новая версия прошивки.

Вы можете сами придумать архитектуру вашего приложения, использующего CoAP, либо воспользоваться стандартизированной надстройкой над CoAP под названием LwM2M.

Рассмотрим сетевую модель стека TCP/IP и место CoAP в ней.

Формально CoAP является протоколом прикладного уровня, однако как видно из рисунка над ним необходимо реализовать свое приложение или использовать LwM2M.

Ниже CoAP в сетевой модели находиться UDP или DTLS протокол. То есть полезная нагрузка UDP пакета — это CoAP пакет. DTLS протокол предоставляет шифрование UDP пакетов, чтобы содержимое передаваемых данных не было доступно посторонним. 

Рассмотрим структуру пакета CoAP.

CoAP пакет содержит следующие поля:

  • Ver —  версия протокола (2 бита). Значение по-умолчанию  0b01
  • T  — тип пакета (2 бита) :
    • 0b00 — Confirmable (пакет, требующий подтверждения)
    • 0b01 — Non-confirmable (пакет без подтверждения)
    • 0b10 — Acknowledgement (подтверждение приема пакета)
    • 0b11 — Reset (невозможно обработать принятый пакет )
  • TKL — длина токена (4 бита). Токен — это уникальный идентификатор пакета. Значение длины токена варьируется от 0 до 8 байт, значения 9 — 15 зарезервированы. 
  • Code — код сообщения (8 бит). Разделен на две части : класс (3 бита) и детализация (5 бит). В документации класс и детализация разделены точкой, например:
    • 2.01   Created
    • 4.00   Bad request
    • 5.00   Internal server error

В данном случае класс 2 сигнализирует о положительном коде ответа на сообщение, а класс 4 об отрицательном. Класс 5 показывает ошибки сервера.

Однако код может указывать не только на результат обработки предыдущего пакета в ответе, а и на команду в запросе:

  • 0.01 GET
  • 0.02 POST
  • 0.03 PUT
  • 0.04 DELETE

Команды  такие же как в протоколе HTTP

  • Message ID — идентификатор сообщения(16 бит) в сетевом порядке байт(Big Endian), предназначен для устранения дублирования сообщений и сопоставления запроса с ответом.
  • Token — уникальный идентификатор (0 — 8 байт), размер задан в поле TKL. Предназначен для сопоставления запроса и ответа. Может показаться, что роль токена избыточна, поскольку в пакете уже есть 16 битный Message ID. Однако Message ID — это просто порядковый номер сообщения, который не является уникальным и может повторяться.
  • Options — опции сообщения , размер может варьироваться в очень широких пределах.

Опции всегда начинаются после токена, а окончание опций, после которых следует полезная нагрузка сообщения , индицируется специальным байтом-разделителем 0xFF. Точнее байт 0xFF индицирует следующую за ним полезную нагрузку — байты с данными сообщения.

Роль опций в CoAP очень важна, в них, например, передается URI для доступа к ресурсу и другие параметры, которые предопределены и не могут быть включены в полезную нагрузку.

Каждую опцию можно рассматривать как элемент односвязного списка, содержащий информацию для доступа к следующему элементу в списке (следующей опции).

Номера опций всегда отсортированы по возрастанию, то есть, если есть опции  Uri-Path(11), Accept(17) и Content-Format(12), то порядок расположения этих опций всегда будет одинаковый:

  • Option #1:  Uri-Path(11)
  • Option #2:  Content-Format(12)
  • Option #3:  Accept(17)

Если в сообщении есть несколько одинаковых опций, то они расположены последовательно.

Структура опций следующая :

Учитывая сказанное (номера опций отсортированы по возрастанию), легко понять назначение поля Option Delta.

Рассмотрим поля опций:

  • Option Delta (4 бита) — это арифметическая разница между предыдущей и текущей опцией. Может принимать значения от 0 до 12, значения 13 — 15 зарезервированы для больших номеров опций.

Воспользовавшись нашим примером из трех опций посчитаем значения Delta для первых двух:

  • Option #1 : Uri-Path(11)         Delta = 11
  • Option #2 : Content-Format(12)     Delta = 1

Для Option #3:  Accept(17) Delta = 16, что не входит в диапазон 0 — 12, поэтому данная опция будет закодирована описанным ниже способом.

  • Option Length (4 бита) — длина значения опции от 0 до 12 байт. Значения 13 — 15 зарезервированы на тот случай, если длина опции окажется  больше 12 байт.
  • Option Value — значение опции, размер которой указан в поле Option Length.

Если значение опции больше 12 байт, то для хранения длины опции будет использоваться дополнительное поле Option Length (extended), точно так же для соседних опций, разница в номерах которых больше 12 , будет использоваться дополнительное поле Option Delta(extended).

  • Option Delta (extended) (0 — 2 байта) — расширенная разница между соседними опциями, формируется аналогично  Option Length (extended),  описание ниже по тексту.
  • Option Length (extended) (0 — 2 байта) — расширенная длина опции.

Предположим , значение длины опции больше 12 байт, но меньше 269 (13 + 256), тогда для хранения дополнительной длины опции нам достаточно выделить один байт.

Соответственно, чтобы понять, что выделен один байт , в поле Option Length указывается значение 13, которое так и называется “Length minus 13” (“Delta minus 13” для дельты).

То есть значение 0 в этом поле соответствует длине опции в 13 байт , а значение 255 соответствует значению длины в 268 (13 + 255) байт.

Что делать, если значение опции больше чем 268 байт, вы, скорее всего уже догадались. Необходимо в поле длины записать зарезервированное значение 14 (“Length minus 269” ), которое будет означать, что под расширенную длину опции мы будем использовать двухбайтное поле.

Соответственно значение 0 поля Option Length (extended) будет соответствовать длине 269 байт, а значение 0xFFFF(65535) будет соответствовать длине 65804(269 + 65535) . 

Зарезервированное значение длины/дельты 15 не используется, поскольку в настоящее время не существует опций с дельтой или длиной больше, чем 65804. 

Теперь мы в состоянии рассчитать дельту для третьей опцию из нашего примера:

  • Option #3:  Accept(17) Delta = 13,  Delta (extended) = 3 (16 = 13 + 3)

Такое сложное на первый взгляд кодирование сделано для экономии каждого байта сообщения, выделенного для хранения опции, поскольку опций в сообщении может быть очень много, а их длина заранее неизвестна.

Поскольку размер заголовка сообщения (имеется в виду вся служебная информация до полезной нагрузки ) может быть рассчитан, а размер полезной нагрузки в UDP пакете  известен, то хранить размер полезной нагрузки CoAP сообщения нет никакой необходимости, его легко рассчитать (CoAP Payload Size = UDP Payload Size — CoAP Header Size).

Так же как и HTTP протокол CoAP построен на модели взаимодействия “запрос-ответ”, сервер ожидает входящего сообщения от клиентов, после чего обрабатывает принятый пакет, а дальше реакция сервера на различные типы пакетов может быть разной.

На пакеты, не требующие подтверждения,  сервер может ничего не отправлять в ответ.

В ответ на пакет, требующий подтверждения, сервер может ответить пакетом с результатами обработки запроса сразу или через какое-то время, однако в ответ на такой пакет сервер должен сразу отправить подтверждение ACK того, что пакет был принят. 

Для обеспечения надежности передачи данных в протоколе CoAP используется подтверждаемый тип пакета Confirmable (посмотрите поле T в структуре пакета).

Такое сообщение будет отправляться повторно пока не будет получен ответ ACK или RST , либо истечет максимальное количество попыток отправки сообщения.

Идентификаторы сообщений (Message ID) в запросе и ответе должны быть одинаковыми.

Если надежность доставки сообщения не требуется (например для нотификаций со значением температуры, которые отправляются каждую секунду), то в типе сообщения T будет указан Non-confirmable. Сервер не будет отвечать на такое сообщение, однако если он не в состоянии обработать данное сообщение, то сервер может отправить в ответ RST.

Как упоминалось выше, CoAP построен на модели взаимодействия “запрос-ответ”, поэтому поле Code в запросе должно содержать код команды (GET, POST, PUT, DELETE), а это же поле в ответном сообщении может содержать код ошибки или код успешной обработки запроса.

Далее показан пример двух так называемых “совмещенных сообщений”, это означает, что в подтверждении приема ACK сразу же отправляется результат обработки входной команды.

Если сервер не может обработать запрос от клиента немедленно, то он отправляет сначала пустое подтверждение ACK, чтобы клиент перестал повторно слать запросы.

После обработки входного сообщения сервер отправляет второй ответ ACK с результатом обработки запроса, на который клиент в свою очередь также должен отправить подтверждение о получении результата обработки команды.

Такой тип сообщений называется “разделенный ответ”.

Если запрос от клиента отправляется как  сообщение, не требующее подтверждение,  то сервер может ответить  новым  сообщением такого-же типа или же новым сообщением, требующим подтверждения.

Обратите внимание, что Message ID в запросе и ответе разные, а поле Token одинаковое. По токену клиент понимает какому из его запросов соответствует полученный ответ.

Поддержка URI на стороне сервера была упрощена благодаря соответствующей обработке URI на стороне клиента.

Клиент разделяет URI на отдельные компоненты host, port, path и query, каждый из компонентов передается в виде соответствующей опции.

coap-URI = «coap:» «//» host [ «:» port ] path-abempty [ «?» query ]

  • host — IP адрес или доменное имя хоста, опция Uri-Host
  • port — номер прослушиваемого порта на сервере (например 5683), опция Uri-Port
  • path — путь к запрашиваемому ресурсу, опция Uri-Path
  • query — запрос, опция Uri-Query

На мой взгляд наилучшим способом изучения протокола CoAP является анализ Wireshark логов, захваченных в процессе сеанса связи между CoAP сервером и клиентом.

Существует достаточно большое количество реализаций протокола CoAP , в том числе на языках C и C++. Одна из таких  библиотек — libcoap.

Для установки libcoap под  операционную систему Linux выполните следующие действия:


$ git clone https://github.com/obgm/libcoap
$ cd libcoap
$ mkdir build & cd build
$ cmake .. & make

Запустите сначала сервер, указав ему номер порта 5683 для прослушивания входящих соединений от клиентов :


$ ./coap-server -p 5683

Дальше запускаем Wireshark для прослушивания сеанса связи между сервером и клиентом. После чего можно отправлять отдельный GET запрос к серверу, параметры которого укажем в командной строке клиента:


$ ./coap-client -m get coap://127.0.0.1:5683/
This is a test server made with libcoap (see https://libcoap.net)
Copyright (C) 2010--2021 Olaf Bergmann <bergmann@tzi.org> and others</bergmann@tzi.org>


$ ./coap-client -m get -T cafe coap://127.0.0.1:5683/time
Jun 03 11:53:38

Смотрим в Wireshark лог, анализируем пакеты, читаем RFC7252 в случае, если что-то непонятно в структуре пакета.

На этом пока все, желаю успехов в изучении протокола CoAP!

Viewed 152593 times by 29866 viewers

Last modified: 03/06/2021

Author

Comments

А чем CoAP круче того же SNMP, к примеру? Тот тоже бинарный, куча библиотек и ПО поддерживает, база OID удобная. Прошивку обновлять правда не получиться, наверное.

    Эти протоколы используются для разных целей.
    CoAP разрабатывался для управления устройствами интернета вещей, а SNMP для управления маршрутизаторами и другим сетевым оборудованием.
    В CoAP также есть возможность отправки нотификаций (я об этом не упомянул) для разных ресурсов. Более корректно сравнивать CoAP с MQTT, хотя они
    и абсолютно разные, зато относяться к одной области применений, — сбор и передача данных с различных датчиков в IoT.
    На основе CoAP создан более известный (благодаря использованию в 3G/LTE сетях) LwM2M протокол https://www.avsystem.com/blog/lightweight-m2m-lwm2m-overview/

Write a Reply or Comment