diff --git a/README.md b/README.md index 7a3f44e..96baac4 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ templates/ | [`c/can-sensor`](c/can-sensor) | передача 64-битных ROM датчиков парой CAN-кадров | `stdint.h` | отправка и приём CAN-кадра | | [`c/ds18b20`](c/ds18b20) | термометры DS18B20 поверх программной 1-Wire | `stdint.h` | Init, DelayUs, Reset, WriteBit, ReadBit — **порт STM32F1 в комплекте** | | [`c/protocan-transport`](c/protocan-transport) | транспорт ProtoCAN: кадр, канал, CRC, общее адресное пространство, каталог GUI | `stdint.h` | запись в поток и запрос свободного места — **порт STM32F4 в комплекте** | +| [`c/set-protocol`](c/set-protocol) | единый SET protocol v2: управление, real-time телеметрия и обновление прошивки через UART/CAN/USB/Ethernet | C99 | доставка целого stream/datagram-кадра, часы, backend карты и загрузчика | | [`c/rtc-service`](c/rtc-service) | RTC с резервированным backup-томом | `stdint.h` | доступ к RTC и backup-памяти — **порт K1921VK028 в комплекте** | ### Python @@ -73,6 +74,7 @@ git subtree pull --prefix lib/templates https://git.rd12.ru/Andrey/templates.git | `OpticalTester` | st7789, keypad, menu, eeprom-ft24c256 | | `CAN_to_RS485` | protocan-transport, python/protocan | | `SETGUI` | python/protocan | +| новые устройства SET и `SETGUI` v2 | set-protocol | | `k1921vk028` | rtc-service | ## Что сюда не попало и почему diff --git a/c/set-protocol/CMakeLists.txt b/c/set-protocol/CMakeLists.txt new file mode 100644 index 0000000..52fc7f3 --- /dev/null +++ b/c/set-protocol/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.13) +project(set_protocol C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +add_library(set_protocol STATIC + src/set_protocol.c + src/set_can.c + src/set_firmware.c + src/set_telemetry.c +) + +target_include_directories(set_protocol PUBLIC include) + +if(MSVC) + target_compile_options(set_protocol PRIVATE /W4 /WX) +else() + target_compile_options(set_protocol PRIVATE -Wall -Wextra -Wpedantic -Werror) +endif() + +option(SETP_BUILD_TESTS "Build host tests" ON) +if(SETP_BUILD_TESTS) + enable_testing() + add_executable(test_set_protocol tests/test_set_protocol.c) + target_link_libraries(test_set_protocol PRIVATE set_protocol) + add_test(NAME set_protocol COMMAND test_set_protocol) +endif() diff --git a/c/set-protocol/MIGRATION.md b/c/set-protocol/MIGRATION.md new file mode 100644 index 0000000..317ba13 --- /dev/null +++ b/c/set-protocol/MIGRATION.md @@ -0,0 +1,63 @@ +# Переход на SET protocol v2 + +Цель перехода — не поддерживать два развивающихся протокола. GUI protocol v1 +и ProtoCAN после начала миграции получают только исправления критических ошибок; +новые команды добавляются исключительно в SETP v2. + +## Этап 1. Транспорт и обнаружение + +1. Подключить `set_protocol.c` к прошивке. +2. Реализовать v2 `PING`, `DEVICE_INFO` и `CAPABILITIES`. +3. В SETGUI при подключении отправлять v2 PING. +4. Только после полного тайм-аута пробовать v1 PING для старого оборудования. +5. После первого ответа фиксировать версию до закрытия соединения. + +Нельзя разбирать v1 и v2 одним header после поля version: их размеры различны. + +## Этап 2. Одна карта данных + +Все измерения, уставки и состояния получают стабильный 32-битный адрес и +описание в `CATALOG`. Обычные операции изделия переводятся на: + +- `READ` — разовое получение; +- `WRITE` — изменение конфигурации; +- `SUBSCRIBE/PUBLISH` — значения для экранов и графиков. + +Команды изделия из диапазона `0x1000..0x7FFF` остаются только для действий, +которые не являются чтением или записью данных: поиск 1-Wire, нажатие экранной +кнопки, запуск самотеста и аналогичные операции. + +## Этап 3. Real-time + +SETGUI получает каталог, строит подписку из реально видимых сигналов и обновляет +графики по `timestamp_ms` устройства. Полные снимки всего прибора по таймеру не +передаются. При перегрузке устройство отбрасывает старую телеметрию, сохраняя +ответы на команды. + +## Этап 4. Загрузчик + +1. Реализовать staging/A-B backend и `FW_STATUS`. +2. Проверить resume после обрыва на каждом допустимом offset. +3. Проверить битый CRC блока и битый SHA-256 образа. +4. Для серийной прошивки включить обязательную подпись и закрепить public key в + bootloader/защищённой области. +5. Проверить rollback при reset до подтверждения успешного старта приложения. + +До выполнения этих пунктов кнопка удалённой активации не считается безопасной. + +## Этап 5. Носители + +- UART/RS-232/RS-485 и USB CDC используют один stream parser. +- Ethernet TCP подключается к тому же parser без новой логики команд. +- Ethernet UDP использует `setp_frame_decode_datagram()`. +- CAN использует `set_can.c`; после reassembly кадр передаётся тому же router. + +## Этап 6. Завершение + +Когда все поддерживаемые изделия отвечают на v2: + +1. убрать fallback v1 из штатного режима SETGUI; +2. оставить отдельную legacy-утилиту только для обслуживания старых приборов; +3. удалить дублирующиеся карты регистров и device-specific сообщения, которые + заменены общей картой; +4. зафиксировать v2 test vectors в CI прошивок и SETGUI. diff --git a/c/set-protocol/PORTING.md b/c/set-protocol/PORTING.md new file mode 100644 index 0000000..14d6d4c --- /dev/null +++ b/c/set-protocol/PORTING.md @@ -0,0 +1,57 @@ +# Перенос set-protocol + +## Общая граница + +Ядро кодирует и разбирает кадры, но не владеет UART, CAN-контроллером, сокетом, +Flash или таймером. Порт приложения должен предоставить: + +- неблокирующую очередь передачи целого кадра; +- передачу принятых chunks в `setp_parser_feed()`; +- монотонное время для телеметрии; +- callback-и общей карты и загрузчика прошивки. + +## UART / RS-232 / RS-485 + +DMA/ISR складывает байты в кольцевой буфер. Главный цикл отдаёт непрерывные +участки parser-у. Для RS-485 DE остаётся включённым до физического окончания +последнего stop-бита. Кадр нельзя считать отправленным по событию «DMA закончил +читать память». + +## USB CDC + +USB packet boundaries не являются границами SETP-кадра. Каждый принятый USB +chunk передаётся обычному stream-parser. + +## Ethernet TCP + +Каждый результат `recv()` передаётся stream-parser. `send()` может принять не +весь буфер, поэтому порт хранит offset до полной отправки кадра. Закрытие TCP +соединения удаляет его подписки на телеметрию. + +## Ethernet UDP + +UDP payload сначала проверяется parser-ом. Принимается ровно один кадр и ноль +остаточных байт. Адрес peer хранится портом до отправки ответа. Для broadcast и +multicast запрещены WRITE, FW_BEGIN и FW_ACTIVATE. + +## CAN + +Не вызывайте прикладной router из ISR. ISR принимает CAN-кадры в очередь, +сегментация собирается в главном цикле, и только полный SETP-кадр передаётся +parser/router. Сборка должна иметь timeout, контроль номера сегмента и одного +активного отправителя на канал. + +## Firmware backend + +Протокол не заменяет безопасный загрузчик. Backend обязан: + +1. писать только в неактивный слот или staging-область; +2. проверять границы до erase/program; +3. принимать повтор подтверждённого блока идемпотентно; +4. хранить `next_offset` так, чтобы пережить reset; +5. проверить CRC32, SHA-256 и, если требуется, подпись; +6. атомарно пометить слот READY; +7. иметь rollback, если новая прошивка не подтвердила успешный старт. + +Во время обновления ответы и FW_* имеют приоритет над PUBLISH. Подписки можно +временно приостановить, но соединение и `FW_STATUS` должны продолжать работать. diff --git a/c/set-protocol/PROTOCOL.md b/c/set-protocol/PROTOCOL.md new file mode 100644 index 0000000..7fe4bfd --- /dev/null +++ b/c/set-protocol/PROTOCOL.md @@ -0,0 +1,238 @@ +# SET protocol v2 — wire contract + +## 1. Назначение + +SETP v2 — единственный прикладной протокол новых устройств SET. Он решает три +задачи одним контрактом: + +1. запросы и ответы: конфигурация, диагностика, журналы и общая карта данных; +2. события: значения для отрисовки в реальном времени; +3. обновление прошивки с продолжением после разрыва соединения. + +Носитель не меняет типы сообщений или payload. Меняется только способ доставки +целого SETP-кадра. + +## 2. Общий кадр + +```text +offset size field +0 2 SOF = A5 5A +2 1 version = 02 +3 1 flags +4 2 message_type u16 LE +6 2 source u16 LE +8 2 destination u16 LE +10 2 sequence u16 LE +12 2 payload_length u16 LE +14 N payload +14+N 4 CRC32 IEEE u32 LE +``` + +CRC32 считается от `version` (offset 2) до последнего байта payload. Полином +`0xEDB88320`, init/final XOR `0xFFFFFFFF`; проверочное значение строки +`123456789` — `0xCBF43926`. + +Максимальный payload базового профиля — 512 байт. Реализация может объявить +меньший предел через `CAPABILITIES`, но не может молча принять начало большого +кадра и отбросить его конец. + +Эталонный PING к узлу `0x002A`, sequence `0x1234`, с `ACK_REQUIRED`: + +```text +A5 5A 02 08 01 00 00 00 2A 00 34 12 00 00 33 EC 33 04 +``` + +## 3. Флаги и транзакции + +| Бит | Имя | Смысл | +|---:|---|---| +| 0 | `RESPONSE` | ответ; тип и sequence повторяют запрос | +| 1 | `EVENT` | самостоятельная публикация, не ответ | +| 2 | `ERROR` | status ответа не равен `OK` | +| 3 | `ACK_REQUIRED` | отправитель требует явный ответ | +| 4 | `MORE` | за этим логическим куском последуют другие | +| 5 | `PRIORITY` | приоритет над обычной телеметрией | +| 7..6 | — | передавать нулями | + +Запрос содержит `RESPONSE=0`, `EVENT=0`. Ответ содержит `RESPONSE=1`, тот же +`message_type` и `sequence`, а первые два байта payload всегда являются +`status u16`. Push-телеметрия содержит `EVENT=1`; её `sequence` — счётчик +кадров источника и позволяет заметить потерю. + +`source/destination = 0` означает локальный узел в точке-точке. `0xFFFF` — +broadcast; на broadcast-запрос отвечать нельзя, если прикладная команда явно +не задаёт безопасное окно ответа. + +## 4. Стабильные типы сообщений + +| Диапазон | Назначение | +|---|---| +| `0x0000..0x00FF` | системные команды и карта данных | +| `0x0100..0x01FF` | обновление прошивки | +| `0x0200..0x0FFF` | зарезервировано общей спецификацией | +| `0x1000..0x7FFF` | команды конкретного изделия | +| `0x8000..0xFFFF` | зарезервировано | + +Общие команды: + +| Код | Имя | Назначение | +|---:|---|---| +| `0x0001` | `PING` | доступность и uptime | +| `0x0002` | `DEVICE_INFO` | модель, версии, серийный номер | +| `0x0003` | `CAPABILITIES` | интерфейсы, MTU, функции, лимиты | +| `0x0008` | `DIAGNOSTICS` | счётчики транспорта и приложения | +| `0x0009` | `READ` | чтение 32-битно адресуемой карты | +| `0x000A` | `WRITE` | транзакционная запись карты | +| `0x0010` | `LOG_READ` | чтение журналов блоками | +| `0x0011` | `CATALOG` | метаданные общей карты | +| `0x0012` | `SUBSCRIBE` | создать/изменить поток данных | +| `0x0013` | `PUBLISH` | пакет значений для отрисовки | +| `0x0014` | `UNSUBSCRIBE` | удалить подписку | +| `0x0100..0105` | `FW_*` | обновление и активация прошивки | + +Неизвестный тип не является ошибкой кадрирования. Устройство отвечает +`UNSUPPORTED`, если запрос был адресован ему и требовал ответа. + +## 5. Телеметрия реального времени + +### SUBSCRIBE + +```text +subscription_id u16 +period_ms u32 (0 = по изменению) +address_count u16 +addresses u32[address_count] +``` + +Ответ сообщает status и фактически принятый период. Устройство вправе увеличить +слишком короткий период. Подписка принадлежит соединению/источнику и удаляется +при его закрытии либо командой `UNSUBSCRIBE`. + +### PUBLISH + +```text +subscription_id u16 +sample_sequence u16 +timestamp_ms u32 +item_count u16 + +repeat item_count times: + address u32 + encoding u8 (U16/I16/U32/I32/F32/BYTES) + element_count u8 + data_length u16 + data u8[data_length] +``` + +`timestamp_ms` — монотонное время устройства. GUI строит графики по нему, а не +по моменту прихода в Windows. Большой массив, например спектр, разбивается на +несколько `PUBLISH` с `MORE`; адрес и `sample_sequence` остаются теми же. + +Телеметрия имеет меньший приоритет, чем ответы и прошивка. При переполнении +очереди разрешено отбросить старый `PUBLISH`, но нельзя частично передать кадр. + +## 6. Прошивка + +Типы: + +| Код | Команда | +|---:|---| +| `0x0100` | `FW_BEGIN` | +| `0x0101` | `FW_DATA` | +| `0x0102` | `FW_END` | +| `0x0103` | `FW_ABORT` | +| `0x0104` | `FW_STATUS` | +| `0x0105` | `FW_ACTIVATE` | + +`FW_BEGIN` содержит размер, CRC32 и SHA-256 образа, версию, базовый адрес, +целевой слот, желаемый размер блока, ID ключа и необязательную подпись. Подпись +проверяется над каноническим manifest, а не над полученными по частям данными: + +```text +ASCII "SETPFW2\0" || image_size || image_crc32 || image_version || +base_address || slot || sha256 +``` + +Числа manifest также little-endian. Рекомендуемая подпись — Ed25519 (64 байта). +Конкретный загрузчик может потребовать подписанный образ и вернуть `AUTH_FAILED` +для неподписанного. + +`FW_DATA`: + +```text +offset u32 | data_length u16 | flags u16 | data_crc32 u32 | data[] +``` + +Ответ возвращает status и `next_offset u32`. Повтор уже записанного блока с теми +же данными обязан быть идемпотентным. После потери связи GUI запрашивает +`FW_STATUS` и продолжает с `next_offset`. + +`FW_END` повторяет размер, CRC32 и SHA-256. Устройство проверяет весь образ и +только затем переводит слот в `READY`. `FW_ACTIVATE` меняет загрузочный слот; +операция обновления не должна перезаписывать единственный рабочий образ. Для +серийных устройств требуется A/B или эквивалентный механизм rollback. + +CRC32 защищает линию, SHA-256 — целостность образа, подпись — происхождение. +Один CRC не является защитой от подмены прошивки. + +## 7. Привязки к физическим интерфейсам + +### RS-232, RS-485, USB CDC + +Кадры передаются подряд как поток байтов. Parser обязан восстанавливаться после +мусора и битого CRC. На RS-485 используются `source/destination`; передача +broadcast не должна запускать прошивку или запись конфигурации. + +### Ethernet TCP + +TCP несёт тот же поток кадров без дополнительной длины: она уже есть в header. +Один `recv()` может вернуть часть кадра или несколько кадров. Порт по умолчанию +задаётся приложением; рекомендуемое значение проекта — `25060`, оно не считается +зарегистрированным IANA. Для внешних сетей используется TLS, SETP внутри TLS не +меняется. + +### Ethernet UDP + +Одна UDP-датаграмма содержит ровно один полный SETP-кадр. Датаграммы с хвостом, +двумя кадрами или несовпадающей длиной отбрасываются. Базовый payload 512 байт +не превышает безопасный IPv4 MTU. Прошивка по UDP допустима только в режиме +stop-and-wait с `ACK_REQUIRED`; предпочтителен TCP. + +### CAN + +Через классический CAN передаются байты того же полного SETP-кадра. Повторно +кодировать команды в поля CAN ID нельзя. + +Extended CAN ID: + +```text +28..24 prefix = 0x12 +23..16 destination (младшие 8 бит SETP destination) +15..8 source (младшие 8 бит SETP source) +7 priority +6..0 channel +``` + +CAN-профиль использует node `1..254`; `0` и `255` сохраняют смысл local и +broadcast. Поля полного SETP-заголовка остаются обязательными и должны совпасть +с CAN ID после reassembly. + +PCI классического CAN: + +```text +FIRST: data[0]=0x10, data[1..2]=total_length u16 LE, data[3..7]=5 байт +CONSECUTIVE: data[0]=0x20|SN, data[1..7]=до 7 байт, SN начинается с 1 +FLOW_CONTROL:data[0]=0x30|status, data[1]=block_size, data[2]=st_min_ms +``` + +`status`: 0 continue, 1 wait, 2 overflow. Номер сегмента идёт по модулю 16. +Timeout сборки по умолчанию 500 мс. Новый FIRST заменяет незавершённую сборку +того же канала. CAN-FD может увеличить данные сегмента в следующей версии +binding, не меняя SETP frame/message/payload. + +## 8. Совместимость + +GUI protocol v1 и SETP v2 несовместимы. Автоопределение допускается только во +время миграции: клиент посылает v2 PING, затем при полном тайм-ауте пробует v1. +После первого корректного ответа формат соединения фиксируется до отключения. +Новое устройство не должно одновременно публиковать v1 и v2 в одном потоке. diff --git a/c/set-protocol/README.md b/c/set-protocol/README.md new file mode 100644 index 0000000..8f21233 --- /dev/null +++ b/c/set-protocol/README.md @@ -0,0 +1,75 @@ +# set-protocol + +Единый переносимый протокол SET для новых устройств и SETGUI. Один и тот же +прикладной кадр используется для управления, чтения общей карты, потоковой +телеметрии и безопасной передачи прошивки через: + +- RS-232 и RS-485; +- USB CDC; +- CAN с сегментацией; +- Ethernet TCP; +- Ethernet UDP, когда нужен обмен без соединения. + +Протокол не привязан к HAL, ОС или микроконтроллеру. Реализация на C99 не +использует динамическую память. Все многобайтные значения little-endian. + +## Что уже реализовано + +| Файл | Назначение | +|---|---| +| `set_protocol.*` | кадр v2, CRC32, потоковый parser, статусы ответов | +| `set_can.*` | extended CAN ID, сегментация, flow-control и reassembly timeout | +| `set_firmware.*` | BEGIN/DATA/END/STATUS, resume, CRC блока, SHA-256 и подпись | +| `set_telemetry.*` | подписки и типизированные push-пакеты с timestamp | +| `PROTOCOL.md` | обязательный wire-контракт и привязки к носителям | +| `MIGRATION.md` | порядок перехода SETGUI и существующих прошивок с v1 | +| `PORTING.md` | подключение UART, CAN, USB и Ethernet | +| `tests/` | host-тесты и фиксированный эталонный кадр | + +Файл [`PROTOCOL.md`](PROTOCOL.md) является источником истины. Числа из него +нельзя менять без выпуска следующей версии протокола. + +## Минимальный stream-приёмник + +```c +#include "set_protocol.h" + +static setp_parser_t parser; + +static void on_frame(const setp_frame_t *frame, void *user) +{ + (void)user; + /* frame->payload действует только до возврата из callback. */ +} + +void protocol_init(void) +{ + setp_parser_init(&parser); +} + +void protocol_feed(const uint8_t *data, uint16_t length) +{ + (void)setp_parser_feed(&parser, data, length, on_frame, NULL); +} +``` + +Для RS-232, RS-485, USB CDC и TCP в parser передаются любые принятые chunks. +Для UDP один UDP payload должен содержать ровно один полный SETP-кадр. + +## Сборка + +```text +cmake -B build +cmake --build build +ctest --test-dir build +``` + +Либо добавьте три файла из `src/` и каталог `include/` непосредственно в +проект прошивки. + +## Версии + +- GUI protocol v1 остаётся только переходным форматом старых устройств. +- Все новые устройства используют SET protocol v2 (`SETP_VERSION = 2`). +- v1 и v2 имеют одинаковый SOF `A5 5A`, поэтому номер версии проверяется до + разбора остальных полей. diff --git a/c/set-protocol/include/set_can.h b/c/set-protocol/include/set_can.h new file mode 100644 index 0000000..9931542 --- /dev/null +++ b/c/set-protocol/include/set_can.h @@ -0,0 +1,89 @@ +#ifndef SET_CAN_H +#define SET_CAN_H + +#include "set_protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SETP_CAN_ID_PREFIX 0x12U +#define SETP_CAN_ID_PREFIX_MASK 0x1F000000UL +#define SETP_CAN_ID_MASK 0x1FFFFFFFUL + +#define SETP_CAN_PCI_FIRST 0x10U +#define SETP_CAN_PCI_CONSECUTIVE 0x20U +#define SETP_CAN_PCI_FLOW_CONTROL 0x30U +#define SETP_CAN_PCI_TYPE_MASK 0xF0U +#define SETP_CAN_PCI_VALUE_MASK 0x0FU + +#define SETP_CAN_FC_CONTINUE 0x00U +#define SETP_CAN_FC_WAIT 0x01U +#define SETP_CAN_FC_OVERFLOW 0x02U + +#define SETP_CAN_FIRST_DATA 5U +#define SETP_CAN_CONSECUTIVE_DATA 7U + +#ifndef SETP_CAN_REASSEMBLY_TIMEOUT_MS +#define SETP_CAN_REASSEMBLY_TIMEOUT_MS 500U +#endif + +typedef struct { + uint32_t id; + uint8_t length; + uint8_t data[8]; +} setp_can_frame_t; + +typedef struct { + uint8_t source; + uint8_t destination; + uint8_t priority; + uint8_t channel; +} setp_can_id_t; + +typedef enum { + SETP_CAN_RX_NONE = 0, + SETP_CAN_RX_COMPLETE = 1, + SETP_CAN_RX_FORMAT_ERROR = 2, + SETP_CAN_RX_SEQUENCE_ERROR = 3, + SETP_CAN_RX_TIMEOUT = 4 +} setp_can_rx_result_t; + +typedef struct { + uint8_t buffer[SETP_FRAME_MAX]; + uint16_t expected_length; + uint16_t received_length; + uint32_t can_id; + uint32_t deadline_ms; + uint8_t next_sequence; + uint8_t active; +} setp_can_rx_t; + +typedef struct { + const uint8_t *data; + uint16_t length; + uint32_t can_id; +} setp_can_packet_t; + +typedef bool (*setp_can_send_fn)(const setp_can_frame_t *frame, void *user); + +uint32_t setp_can_id_pack(const setp_can_id_t *value); +bool setp_can_id_unpack(uint32_t raw, setp_can_id_t *out); + +/** Segment one complete SETP frame. Flow-control pacing belongs to the port. */ +bool setp_can_segment(const uint8_t *packet, uint16_t length, uint32_t can_id, + setp_can_send_fn send, void *user); + +void setp_can_rx_init(setp_can_rx_t *state); +setp_can_rx_result_t setp_can_rx_feed(setp_can_rx_t *state, + const setp_can_frame_t *frame, + uint32_t now_ms, setp_can_packet_t *out); + +bool setp_can_flow_control(setp_can_frame_t *out, uint32_t can_id, + uint8_t status, uint8_t block_size, uint8_t st_min_ms); + +#ifdef __cplusplus +} +#endif + +#endif /* SET_CAN_H */ diff --git a/c/set-protocol/include/set_firmware.h b/c/set-protocol/include/set_firmware.h new file mode 100644 index 0000000..521d7d4 --- /dev/null +++ b/c/set-protocol/include/set_firmware.h @@ -0,0 +1,91 @@ +#ifndef SET_FIRMWARE_H +#define SET_FIRMWARE_H + +#include "set_protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SETP_SHA256_SIZE 32U +#define SETP_FW_BEGIN_FIXED_SIZE 58U +#define SETP_FW_DATA_HEADER_SIZE 12U +#define SETP_FW_END_SIZE 40U +#define SETP_FW_STATUS_SIZE 16U +#define SETP_FW_MANIFEST_SIZE 57U + +#define SETP_FW_FLAG_RESUME 0x01U +#define SETP_FW_FLAG_SIGNED 0x02U +#define SETP_FW_FLAG_ERASE_SLOT 0x04U +#define SETP_FW_FLAG_ACTIVATE 0x08U + +typedef enum { + SETP_FW_IDLE = 0, + SETP_FW_RECEIVING = 1, + SETP_FW_VERIFYING = 2, + SETP_FW_READY = 3, + SETP_FW_ACTIVE = 4, + SETP_FW_FAILED = 5 +} setp_fw_state_t; + +typedef struct { + uint32_t image_size; + uint32_t image_crc32; + uint32_t image_version; + uint32_t base_address; + uint8_t slot; + uint8_t flags; + uint16_t block_size; + uint8_t sha256[SETP_SHA256_SIZE]; + uint32_t signing_key_id; + uint16_t signature_length; + const uint8_t *signature; +} setp_fw_begin_t; + +typedef struct { + uint32_t offset; + uint16_t data_length; + uint16_t flags; + uint32_t data_crc32; + const uint8_t *data; +} setp_fw_data_t; + +typedef struct { + uint32_t image_size; + uint32_t image_crc32; + uint8_t sha256[SETP_SHA256_SIZE]; +} setp_fw_end_t; + +typedef struct { + uint8_t state; + uint8_t active_slot; + uint16_t max_block_size; + uint32_t next_offset; + uint32_t image_size; + uint16_t last_error; + uint16_t flags; +} setp_fw_status_t; + +size_t setp_fw_begin_encode(const setp_fw_begin_t *value, uint8_t *out, size_t capacity); +bool setp_fw_begin_decode(const uint8_t *payload, uint16_t length, setp_fw_begin_t *out); + +size_t setp_fw_data_encode(uint32_t offset, uint16_t flags, + const uint8_t *data, uint16_t data_length, + uint8_t *out, size_t capacity); +bool setp_fw_data_decode(const uint8_t *payload, uint16_t length, setp_fw_data_t *out); + +size_t setp_fw_end_encode(const setp_fw_end_t *value, uint8_t *out, size_t capacity); +bool setp_fw_end_decode(const uint8_t *payload, uint16_t length, setp_fw_end_t *out); + +size_t setp_fw_status_encode(const setp_fw_status_t *value, uint8_t *out, size_t capacity); +bool setp_fw_status_decode(const uint8_t *payload, uint16_t length, setp_fw_status_t *out); + +/** Canonical bytes verified by a firmware-signature backend. */ +size_t setp_fw_manifest_encode(const setp_fw_begin_t *value, + uint8_t *out, size_t capacity); + +#ifdef __cplusplus +} +#endif + +#endif /* SET_FIRMWARE_H */ diff --git a/c/set-protocol/include/set_protocol.h b/c/set-protocol/include/set_protocol.h new file mode 100644 index 0000000..b3e35ee --- /dev/null +++ b/c/set-protocol/include/set_protocol.h @@ -0,0 +1,148 @@ +#ifndef SET_PROTOCOL_H +#define SET_PROTOCOL_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SETP_VERSION 0x02U +#define SETP_SOF0 0xA5U +#define SETP_SOF1 0x5AU +#define SETP_HEADER_SIZE 14U +#define SETP_CRC_SIZE 4U + +#ifndef SETP_MAX_PAYLOAD +#define SETP_MAX_PAYLOAD 512U +#endif + +#define SETP_FRAME_MAX (SETP_HEADER_SIZE + SETP_MAX_PAYLOAD + SETP_CRC_SIZE) +#define SETP_NODE_LOCAL 0x0000U +#define SETP_NODE_BROADCAST 0xFFFFU + +/* Frame flags. Reserved bits must be transmitted as zero. */ +#define SETP_FLAG_RESPONSE 0x01U +#define SETP_FLAG_EVENT 0x02U +#define SETP_FLAG_ERROR 0x04U +#define SETP_FLAG_ACK_REQUIRED 0x08U +#define SETP_FLAG_MORE 0x10U +#define SETP_FLAG_PRIORITY 0x20U +#define SETP_FLAG_KNOWN_MASK 0x3FU +#define SETP_FLAG_RESERVED_MASK 0xC0U + +typedef enum { + SETP_IFACE_NONE = 0, + SETP_IFACE_RS232 = 1, + SETP_IFACE_RS485 = 2, + SETP_IFACE_CAN = 3, + SETP_IFACE_USB_CDC = 4, + SETP_IFACE_ETHERNET_TCP = 5, + SETP_IFACE_ETHERNET_UDP = 6 +} setp_iface_t; + +typedef enum { + SETP_MSG_PING = 0x0001, + SETP_MSG_DEVICE_INFO = 0x0002, + SETP_MSG_CAPABILITIES = 0x0003, + SETP_MSG_DIAGNOSTICS = 0x0008, + SETP_MSG_READ = 0x0009, + SETP_MSG_WRITE = 0x000A, + SETP_MSG_LOG_READ = 0x0010, + SETP_MSG_CATALOG = 0x0011, + SETP_MSG_SUBSCRIBE = 0x0012, + SETP_MSG_PUBLISH = 0x0013, + SETP_MSG_UNSUBSCRIBE = 0x0014, + + SETP_MSG_FW_BEGIN = 0x0100, + SETP_MSG_FW_DATA = 0x0101, + SETP_MSG_FW_END = 0x0102, + SETP_MSG_FW_ABORT = 0x0103, + SETP_MSG_FW_STATUS = 0x0104, + SETP_MSG_FW_ACTIVATE = 0x0105, + + SETP_MSG_DEVICE_BASE = 0x1000, + SETP_MSG_DEVICE_LAST = 0x7FFF +} setp_message_type_t; + +typedef enum { + SETP_STATUS_OK = 0, + SETP_STATUS_INVALID_ARGUMENT = 1, + SETP_STATUS_INVALID_LENGTH = 2, + SETP_STATUS_NOT_FOUND = 3, + SETP_STATUS_ACCESS_DENIED = 4, + SETP_STATUS_BUSY = 5, + SETP_STATUS_NO_PROVIDER = 6, + SETP_STATUS_INTERNAL = 7, + SETP_STATUS_CRC = 8, + SETP_STATUS_SEQUENCE = 9, + SETP_STATUS_NO_SPACE = 10, + SETP_STATUS_UNSUPPORTED = 11, + SETP_STATUS_VERIFY_FAILED = 12, + SETP_STATUS_AUTH_FAILED = 13, + SETP_STATUS_TIMEOUT = 14, + SETP_STATUS_WRONG_STATE = 15 +} setp_status_t; + +typedef struct { + uint8_t flags; + uint16_t message_type; + uint16_t source; + uint16_t destination; + uint16_t sequence; + uint16_t payload_length; + const uint8_t *payload; +} setp_frame_t; + +typedef struct { + uint32_t frames; + uint32_t crc_errors; + uint32_t version_errors; + uint32_t length_errors; + uint32_t flag_errors; + uint32_t stray_bytes; + uint32_t overflows; +} setp_parser_stats_t; + +typedef struct { + uint8_t buffer[SETP_FRAME_MAX]; + uint16_t length; + setp_parser_stats_t stats; +} setp_parser_t; + +typedef void (*setp_frame_fn)(const setp_frame_t *frame, void *user); + +uint16_t setp_get_u16(const uint8_t *data); +uint32_t setp_get_u32(const uint8_t *data); +void setp_put_u16(uint8_t *data, uint16_t value); +void setp_put_u32(uint8_t *data, uint32_t value); + +uint32_t setp_crc32(const uint8_t *data, size_t length); + +/** Encode one complete stream/UDP frame. Returns zero on invalid arguments. */ +size_t setp_frame_encode(const setp_frame_t *frame, uint8_t *out, size_t capacity); + +/** Decode exactly one complete UDP/datagram frame; trailing bytes are rejected. */ +bool setp_frame_decode_datagram(const uint8_t *data, size_t length, setp_frame_t *out); + +void setp_parser_init(setp_parser_t *parser); +void setp_parser_reset(setp_parser_t *parser); + +/** Feed an arbitrary stream chunk. Returns the number of delivered frames. */ +size_t setp_parser_feed(setp_parser_t *parser, const uint8_t *data, size_t length, + setp_frame_fn callback, void *user); + +/** Encode the common two-byte status prefix used by every response. */ +size_t setp_status_encode(uint16_t status, uint8_t *out, size_t capacity); + +/** Split a response payload into status and optional response body. */ +bool setp_status_decode(const setp_frame_t *frame, uint16_t *status, + const uint8_t **body, uint16_t *body_length); + +#ifdef __cplusplus +} +#endif + +#endif /* SET_PROTOCOL_H */ diff --git a/c/set-protocol/include/set_telemetry.h b/c/set-protocol/include/set_telemetry.h new file mode 100644 index 0000000..d945714 --- /dev/null +++ b/c/set-protocol/include/set_telemetry.h @@ -0,0 +1,73 @@ +#ifndef SET_TELEMETRY_H +#define SET_TELEMETRY_H + +#include "set_protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SETP_SUBSCRIBE_HEADER_SIZE 8U +#define SETP_PUBLISH_HEADER_SIZE 10U +#define SETP_PUBLISH_ITEM_HEADER_SIZE 8U + +typedef enum { + SETP_VALUE_U16 = 1, + SETP_VALUE_I16 = 2, + SETP_VALUE_U32 = 3, + SETP_VALUE_I32 = 4, + SETP_VALUE_F32 = 5, + SETP_VALUE_BYTES = 6 +} setp_value_encoding_t; + +typedef struct { + uint8_t *output; + uint16_t capacity; + uint16_t length; + uint16_t item_count; +} setp_publish_builder_t; + +typedef struct { + const uint8_t *payload; + uint16_t length; + uint16_t offset; + uint16_t remaining; + uint16_t subscription_id; + uint16_t sample_sequence; + uint32_t timestamp_ms; +} setp_publish_view_t; + +typedef struct { + uint32_t address; + uint8_t encoding; + uint8_t element_count; + uint16_t data_length; + const uint8_t *data; +} setp_publish_item_t; + +size_t setp_subscribe_encode(uint16_t subscription_id, uint32_t period_ms, + const uint32_t *addresses, uint16_t address_count, + uint8_t *out, size_t capacity); +bool setp_subscribe_decode_header(const uint8_t *payload, uint16_t length, + uint16_t *subscription_id, uint32_t *period_ms, + uint16_t *address_count); +bool setp_subscribe_address(const uint8_t *payload, uint16_t length, + uint16_t index, uint32_t *address); + +bool setp_publish_begin(setp_publish_builder_t *builder, + uint16_t subscription_id, uint16_t sample_sequence, + uint32_t timestamp_ms, uint8_t *out, uint16_t capacity); +bool setp_publish_add(setp_publish_builder_t *builder, uint32_t address, + uint8_t encoding, uint8_t element_count, + const uint8_t *data, uint16_t data_length); +size_t setp_publish_finish(setp_publish_builder_t *builder); + +bool setp_publish_view_init(setp_publish_view_t *view, + const uint8_t *payload, uint16_t length); +bool setp_publish_next(setp_publish_view_t *view, setp_publish_item_t *item); + +#ifdef __cplusplus +} +#endif + +#endif /* SET_TELEMETRY_H */ diff --git a/c/set-protocol/src/set_can.c b/c/set-protocol/src/set_can.c new file mode 100644 index 0000000..00e84a7 --- /dev/null +++ b/c/set-protocol/src/set_can.c @@ -0,0 +1,185 @@ +#include "set_can.h" + +#include + +uint32_t setp_can_id_pack(const setp_can_id_t *value) +{ + if (value == NULL) { + return 0U; + } + return ((uint32_t)SETP_CAN_ID_PREFIX << 24) + | ((uint32_t)value->destination << 16) + | ((uint32_t)value->source << 8) + | ((uint32_t)(value->priority & 1U) << 7) + | (uint32_t)(value->channel & 0x7FU); +} + +bool setp_can_id_unpack(uint32_t raw, setp_can_id_t *out) +{ + if ((out == NULL) || ((raw & ~SETP_CAN_ID_MASK) != 0U) + || ((raw & SETP_CAN_ID_PREFIX_MASK) + != ((uint32_t)SETP_CAN_ID_PREFIX << 24))) { + return false; + } + out->destination = (uint8_t)(raw >> 16); + out->source = (uint8_t)(raw >> 8); + out->priority = (uint8_t)((raw >> 7) & 1U); + out->channel = (uint8_t)(raw & 0x7FU); + return true; +} + +bool setp_can_segment(const uint8_t *packet, uint16_t length, uint32_t can_id, + setp_can_send_fn send, void *user) +{ + setp_can_frame_t frame; + uint16_t offset; + uint8_t sequence = 1U; + + if ((packet == NULL) || (send == NULL) + || (length < SETP_HEADER_SIZE + SETP_CRC_SIZE) + || (length > SETP_FRAME_MAX) + || ((can_id & ~SETP_CAN_ID_MASK) != 0U)) { + return false; + } + (void)memset(&frame, 0, sizeof(frame)); + frame.id = can_id; + frame.length = 8U; + frame.data[0] = SETP_CAN_PCI_FIRST; + setp_put_u16(&frame.data[1], length); + (void)memcpy(&frame.data[3], packet, SETP_CAN_FIRST_DATA); + if (!send(&frame, user)) { + return false; + } + offset = SETP_CAN_FIRST_DATA; + + while (offset < length) { + uint16_t remaining = (uint16_t)(length - offset); + uint8_t chunk = remaining > SETP_CAN_CONSECUTIVE_DATA + ? SETP_CAN_CONSECUTIVE_DATA : (uint8_t)remaining; + (void)memset(frame.data, 0, sizeof(frame.data)); + frame.data[0] = (uint8_t)(SETP_CAN_PCI_CONSECUTIVE + | (sequence & SETP_CAN_PCI_VALUE_MASK)); + (void)memcpy(&frame.data[1], &packet[offset], chunk); + frame.length = (uint8_t)(1U + chunk); + if (!send(&frame, user)) { + return false; + } + offset = (uint16_t)(offset + chunk); + sequence = (uint8_t)((sequence + 1U) & SETP_CAN_PCI_VALUE_MASK); + } + return true; +} + +void setp_can_rx_init(setp_can_rx_t *state) +{ + if (state != NULL) { + (void)memset(state, 0, sizeof(*state)); + } +} + +static bool setp_can_expired(const setp_can_rx_t *state, uint32_t now_ms) +{ + return state->active && ((int32_t)(now_ms - state->deadline_ms) >= 0); +} + +setp_can_rx_result_t setp_can_rx_feed(setp_can_rx_t *state, + const setp_can_frame_t *frame, + uint32_t now_ms, setp_can_packet_t *out) +{ + uint8_t pci; + if ((state == NULL) || (frame == NULL) || (out == NULL) + || (frame->length == 0U) || (frame->length > 8U)) { + return SETP_CAN_RX_FORMAT_ERROR; + } + if (setp_can_expired(state, now_ms)) { + state->active = 0U; + return SETP_CAN_RX_TIMEOUT; + } + pci = (uint8_t)(frame->data[0] & SETP_CAN_PCI_TYPE_MASK); + if (pci == SETP_CAN_PCI_FIRST) { + uint16_t total; + if (frame->length != 8U) { + return SETP_CAN_RX_FORMAT_ERROR; + } + total = setp_get_u16(&frame->data[1]); + if ((total < SETP_HEADER_SIZE + SETP_CRC_SIZE) || (total > SETP_FRAME_MAX)) { + return SETP_CAN_RX_FORMAT_ERROR; + } + (void)memcpy(state->buffer, &frame->data[3], SETP_CAN_FIRST_DATA); + state->expected_length = total; + state->received_length = SETP_CAN_FIRST_DATA; + state->can_id = frame->id; + state->deadline_ms = now_ms + SETP_CAN_REASSEMBLY_TIMEOUT_MS; + state->next_sequence = 1U; + state->active = 1U; + return SETP_CAN_RX_NONE; + } + if (pci == SETP_CAN_PCI_CONSECUTIVE) { + uint8_t sequence = (uint8_t)(frame->data[0] & SETP_CAN_PCI_VALUE_MASK); + uint16_t remaining; + uint8_t chunk; + if (!state->active || (frame->id != state->can_id) + || (sequence != state->next_sequence) || (frame->length < 2U)) { + state->active = 0U; + return SETP_CAN_RX_SEQUENCE_ERROR; + } + remaining = (uint16_t)(state->expected_length - state->received_length); + chunk = (uint8_t)(frame->length - 1U); + if (chunk > remaining) { + state->active = 0U; + return SETP_CAN_RX_FORMAT_ERROR; + } + (void)memcpy(&state->buffer[state->received_length], &frame->data[1], chunk); + state->received_length = (uint16_t)(state->received_length + chunk); + state->next_sequence = (uint8_t)((state->next_sequence + 1U) + & SETP_CAN_PCI_VALUE_MASK); + state->deadline_ms = now_ms + SETP_CAN_REASSEMBLY_TIMEOUT_MS; + if (state->received_length == state->expected_length) { + setp_can_id_t id; + uint16_t source; + uint16_t destination; + uint8_t priority; + if (!setp_can_id_unpack(state->can_id, &id) + || (state->expected_length < SETP_HEADER_SIZE + SETP_CRC_SIZE) + || (state->buffer[0] != SETP_SOF0) + || (state->buffer[1] != SETP_SOF1)) { + state->active = 0U; + return SETP_CAN_RX_FORMAT_ERROR; + } + source = setp_get_u16(&state->buffer[6]); + destination = setp_get_u16(&state->buffer[8]); + priority = (state->buffer[3] & SETP_FLAG_PRIORITY) != 0U ? 1U : 0U; + if ((source != id.source) || (destination != id.destination) + || (priority != id.priority)) { + state->active = 0U; + return SETP_CAN_RX_FORMAT_ERROR; + } + out->data = state->buffer; + out->length = state->expected_length; + out->can_id = state->can_id; + state->active = 0U; + return SETP_CAN_RX_COMPLETE; + } + return SETP_CAN_RX_NONE; + } + if (pci == SETP_CAN_PCI_FLOW_CONTROL) { + return SETP_CAN_RX_NONE; + } + return SETP_CAN_RX_FORMAT_ERROR; +} + +bool setp_can_flow_control(setp_can_frame_t *out, uint32_t can_id, + uint8_t status, uint8_t block_size, uint8_t st_min_ms) +{ + if ((out == NULL) || (status > SETP_CAN_FC_OVERFLOW) + || (st_min_ms > 0x7FU) || ((can_id & ~SETP_CAN_ID_MASK) != 0U)) { + return false; + } + (void)memset(out, 0, sizeof(*out)); + out->id = can_id; + out->length = 3U; + out->data[0] = (uint8_t)(SETP_CAN_PCI_FLOW_CONTROL | status); + out->data[1] = block_size; + out->data[2] = st_min_ms; + return true; +} diff --git a/c/set-protocol/src/set_firmware.c b/c/set-protocol/src/set_firmware.c new file mode 100644 index 0000000..131592f --- /dev/null +++ b/c/set-protocol/src/set_firmware.c @@ -0,0 +1,167 @@ +#include "set_firmware.h" + +#include + +size_t setp_fw_begin_encode(const setp_fw_begin_t *value, uint8_t *out, size_t capacity) +{ + size_t total; + if ((value == NULL) || (out == NULL) + || (value->block_size == 0U) + || (value->block_size > SETP_MAX_PAYLOAD - SETP_FW_DATA_HEADER_SIZE) + || ((value->signature_length != 0U) && (value->signature == NULL))) { + return 0U; + } + total = (size_t)SETP_FW_BEGIN_FIXED_SIZE + value->signature_length; + if ((total > SETP_MAX_PAYLOAD) || (capacity < total) + || (((value->flags & SETP_FW_FLAG_SIGNED) != 0U) + && (value->signature_length == 0U))) { + return 0U; + } + setp_put_u32(&out[0], value->image_size); + setp_put_u32(&out[4], value->image_crc32); + setp_put_u32(&out[8], value->image_version); + setp_put_u32(&out[12], value->base_address); + out[16] = value->slot; + out[17] = value->flags; + setp_put_u16(&out[18], value->block_size); + (void)memcpy(&out[20], value->sha256, SETP_SHA256_SIZE); + setp_put_u32(&out[52], value->signing_key_id); + setp_put_u16(&out[56], value->signature_length); + if (value->signature_length != 0U) { + (void)memcpy(&out[SETP_FW_BEGIN_FIXED_SIZE], value->signature, + value->signature_length); + } + return total; +} + +bool setp_fw_begin_decode(const uint8_t *payload, uint16_t length, setp_fw_begin_t *out) +{ + uint16_t signature_length; + if ((payload == NULL) || (out == NULL) || (length < SETP_FW_BEGIN_FIXED_SIZE)) { + return false; + } + out->image_size = setp_get_u32(&payload[0]); + out->image_crc32 = setp_get_u32(&payload[4]); + out->image_version = setp_get_u32(&payload[8]); + out->base_address = setp_get_u32(&payload[12]); + out->slot = payload[16]; + out->flags = payload[17]; + out->block_size = setp_get_u16(&payload[18]); + (void)memcpy(out->sha256, &payload[20], SETP_SHA256_SIZE); + out->signing_key_id = setp_get_u32(&payload[52]); + signature_length = setp_get_u16(&payload[56]); + if (length != SETP_FW_BEGIN_FIXED_SIZE + signature_length) { + return false; + } + out->signature_length = signature_length; + out->signature = signature_length != 0U ? &payload[SETP_FW_BEGIN_FIXED_SIZE] : NULL; + return (out->block_size != 0U) + && (out->block_size <= SETP_MAX_PAYLOAD - SETP_FW_DATA_HEADER_SIZE) + && (((out->flags & SETP_FW_FLAG_SIGNED) == 0U) || (signature_length != 0U)); +} + +size_t setp_fw_data_encode(uint32_t offset, uint16_t flags, + const uint8_t *data, uint16_t data_length, + uint8_t *out, size_t capacity) +{ + size_t total = (size_t)SETP_FW_DATA_HEADER_SIZE + data_length; + if ((data == NULL) || (out == NULL) || (data_length == 0U) + || (total > SETP_MAX_PAYLOAD) || (capacity < total)) { + return 0U; + } + setp_put_u32(&out[0], offset); + setp_put_u16(&out[4], data_length); + setp_put_u16(&out[6], flags); + setp_put_u32(&out[8], setp_crc32(data, data_length)); + (void)memcpy(&out[SETP_FW_DATA_HEADER_SIZE], data, data_length); + return total; +} + +bool setp_fw_data_decode(const uint8_t *payload, uint16_t length, setp_fw_data_t *out) +{ + uint16_t data_length; + if ((payload == NULL) || (out == NULL) || (length < SETP_FW_DATA_HEADER_SIZE)) { + return false; + } + data_length = setp_get_u16(&payload[4]); + if ((data_length == 0U) || (length != SETP_FW_DATA_HEADER_SIZE + data_length)) { + return false; + } + out->offset = setp_get_u32(&payload[0]); + out->data_length = data_length; + out->flags = setp_get_u16(&payload[6]); + out->data_crc32 = setp_get_u32(&payload[8]); + out->data = &payload[SETP_FW_DATA_HEADER_SIZE]; + return setp_crc32(out->data, out->data_length) == out->data_crc32; +} + +size_t setp_fw_end_encode(const setp_fw_end_t *value, uint8_t *out, size_t capacity) +{ + if ((value == NULL) || (out == NULL) || (capacity < SETP_FW_END_SIZE)) { + return 0U; + } + setp_put_u32(&out[0], value->image_size); + setp_put_u32(&out[4], value->image_crc32); + (void)memcpy(&out[8], value->sha256, SETP_SHA256_SIZE); + return SETP_FW_END_SIZE; +} + +bool setp_fw_end_decode(const uint8_t *payload, uint16_t length, setp_fw_end_t *out) +{ + if ((payload == NULL) || (out == NULL) || (length != SETP_FW_END_SIZE)) { + return false; + } + out->image_size = setp_get_u32(&payload[0]); + out->image_crc32 = setp_get_u32(&payload[4]); + (void)memcpy(out->sha256, &payload[8], SETP_SHA256_SIZE); + return true; +} + +size_t setp_fw_status_encode(const setp_fw_status_t *value, uint8_t *out, size_t capacity) +{ + if ((value == NULL) || (out == NULL) || (capacity < SETP_FW_STATUS_SIZE)) { + return 0U; + } + out[0] = value->state; + out[1] = value->active_slot; + setp_put_u16(&out[2], value->max_block_size); + setp_put_u32(&out[4], value->next_offset); + setp_put_u32(&out[8], value->image_size); + setp_put_u16(&out[12], value->last_error); + setp_put_u16(&out[14], value->flags); + return SETP_FW_STATUS_SIZE; +} + +bool setp_fw_status_decode(const uint8_t *payload, uint16_t length, setp_fw_status_t *out) +{ + if ((payload == NULL) || (out == NULL) || (length != SETP_FW_STATUS_SIZE)) { + return false; + } + out->state = payload[0]; + out->active_slot = payload[1]; + out->max_block_size = setp_get_u16(&payload[2]); + out->next_offset = setp_get_u32(&payload[4]); + out->image_size = setp_get_u32(&payload[8]); + out->last_error = setp_get_u16(&payload[12]); + out->flags = setp_get_u16(&payload[14]); + return true; +} + +size_t setp_fw_manifest_encode(const setp_fw_begin_t *value, + uint8_t *out, size_t capacity) +{ + static const uint8_t prefix[8] = { + 'S', 'E', 'T', 'P', 'F', 'W', '2', 0U + }; + if ((value == NULL) || (out == NULL) || (capacity < SETP_FW_MANIFEST_SIZE)) { + return 0U; + } + (void)memcpy(out, prefix, sizeof(prefix)); + setp_put_u32(&out[8], value->image_size); + setp_put_u32(&out[12], value->image_crc32); + setp_put_u32(&out[16], value->image_version); + setp_put_u32(&out[20], value->base_address); + out[24] = value->slot; + (void)memcpy(&out[25], value->sha256, SETP_SHA256_SIZE); + return SETP_FW_MANIFEST_SIZE; +} diff --git a/c/set-protocol/src/set_protocol.c b/c/set-protocol/src/set_protocol.c new file mode 100644 index 0000000..d10d206 --- /dev/null +++ b/c/set-protocol/src/set_protocol.c @@ -0,0 +1,280 @@ +#include "set_protocol.h" + +#include + +uint16_t setp_get_u16(const uint8_t *data) +{ + return (uint16_t)((uint16_t)data[0] | ((uint16_t)data[1] << 8)); +} + +uint32_t setp_get_u32(const uint8_t *data) +{ + return (uint32_t)data[0] + | ((uint32_t)data[1] << 8) + | ((uint32_t)data[2] << 16) + | ((uint32_t)data[3] << 24); +} + +void setp_put_u16(uint8_t *data, uint16_t value) +{ + data[0] = (uint8_t)(value & 0xFFU); + data[1] = (uint8_t)(value >> 8); +} + +void setp_put_u32(uint8_t *data, uint32_t value) +{ + data[0] = (uint8_t)(value & 0xFFU); + data[1] = (uint8_t)((value >> 8) & 0xFFU); + data[2] = (uint8_t)((value >> 16) & 0xFFU); + data[3] = (uint8_t)(value >> 24); +} + +uint32_t setp_crc32(const uint8_t *data, size_t length) +{ + uint32_t crc = 0xFFFFFFFFUL; + size_t index; + uint8_t bit; + + if ((data == NULL) && (length != 0U)) { + return 0U; + } + for (index = 0U; index < length; ++index) { + crc ^= data[index]; + for (bit = 0U; bit < 8U; ++bit) { + crc = (crc >> 1) ^ ((crc & 1U) ? 0xEDB88320UL : 0U); + } + } + return crc ^ 0xFFFFFFFFUL; +} + +size_t setp_frame_encode(const setp_frame_t *frame, uint8_t *out, size_t capacity) +{ + size_t total; + uint32_t crc; + + if ((frame == NULL) || (out == NULL)) { + return 0U; + } + if ((frame->flags & SETP_FLAG_RESERVED_MASK) != 0U) { + return 0U; + } + if ((frame->payload_length > SETP_MAX_PAYLOAD) + || ((frame->payload_length != 0U) && (frame->payload == NULL))) { + return 0U; + } + total = (size_t)SETP_HEADER_SIZE + frame->payload_length + SETP_CRC_SIZE; + if (capacity < total) { + return 0U; + } + + out[0] = SETP_SOF0; + out[1] = SETP_SOF1; + out[2] = SETP_VERSION; + out[3] = frame->flags; + setp_put_u16(&out[4], frame->message_type); + setp_put_u16(&out[6], frame->source); + setp_put_u16(&out[8], frame->destination); + setp_put_u16(&out[10], frame->sequence); + setp_put_u16(&out[12], frame->payload_length); + if (frame->payload_length != 0U) { + (void)memcpy(&out[SETP_HEADER_SIZE], frame->payload, frame->payload_length); + } + crc = setp_crc32(&out[2], (size_t)(SETP_HEADER_SIZE - 2U) + frame->payload_length); + setp_put_u32(&out[SETP_HEADER_SIZE + frame->payload_length], crc); + return total; +} + +bool setp_frame_decode_datagram(const uint8_t *data, size_t length, setp_frame_t *out) +{ + uint16_t payload_length; + size_t expected_length; + uint32_t expected_crc; + if ((data == NULL) || (out == NULL) || (length < SETP_HEADER_SIZE + SETP_CRC_SIZE) + || (data[0] != SETP_SOF0) || (data[1] != SETP_SOF1) + || (data[2] != SETP_VERSION) + || ((data[3] & SETP_FLAG_RESERVED_MASK) != 0U)) { + return false; + } + payload_length = setp_get_u16(&data[12]); + expected_length = (size_t)SETP_HEADER_SIZE + payload_length + SETP_CRC_SIZE; + if ((payload_length > SETP_MAX_PAYLOAD) || (length != expected_length)) { + return false; + } + expected_crc = setp_get_u32(&data[SETP_HEADER_SIZE + payload_length]); + if (setp_crc32(&data[2], (size_t)(SETP_HEADER_SIZE - 2U) + payload_length) + != expected_crc) { + return false; + } + out->flags = data[3]; + out->message_type = setp_get_u16(&data[4]); + out->source = setp_get_u16(&data[6]); + out->destination = setp_get_u16(&data[8]); + out->sequence = setp_get_u16(&data[10]); + out->payload_length = payload_length; + out->payload = &data[SETP_HEADER_SIZE]; + return true; +} + +void setp_parser_init(setp_parser_t *parser) +{ + if (parser != NULL) { + (void)memset(parser, 0, sizeof(*parser)); + } +} + +void setp_parser_reset(setp_parser_t *parser) +{ + if (parser != NULL) { + parser->length = 0U; + } +} + +static void setp_drop(setp_parser_t *parser, uint16_t count) +{ + if (count >= parser->length) { + parser->length = 0U; + return; + } + (void)memmove(parser->buffer, &parser->buffer[count], parser->length - count); + parser->length = (uint16_t)(parser->length - count); +} + +static uint16_t setp_find_sof(const setp_parser_t *parser) +{ + uint16_t index; + for (index = 0U; index + 1U < parser->length; ++index) { + if ((parser->buffer[index] == SETP_SOF0) + && (parser->buffer[index + 1U] == SETP_SOF1)) { + return index; + } + } + return UINT16_MAX; +} + +static size_t setp_parser_process(setp_parser_t *parser, + setp_frame_fn callback, void *user) +{ + size_t delivered = 0U; + + for (;;) { + uint16_t sof; + uint16_t payload_length; + uint16_t total; + uint32_t expected_crc; + uint32_t actual_crc; + setp_frame_t frame; + + if (parser->length < 2U) { + break; + } + sof = setp_find_sof(parser); + if (sof == UINT16_MAX) { + if (parser->buffer[parser->length - 1U] == SETP_SOF0) { + parser->stats.stray_bytes += parser->length - 1U; + parser->buffer[0] = SETP_SOF0; + parser->length = 1U; + } else { + parser->stats.stray_bytes += parser->length; + parser->length = 0U; + } + break; + } + if (sof != 0U) { + parser->stats.stray_bytes += sof; + setp_drop(parser, sof); + } + if (parser->length < SETP_HEADER_SIZE) { + break; + } + if (parser->buffer[2] != SETP_VERSION) { + ++parser->stats.version_errors; + setp_drop(parser, 1U); + continue; + } + if ((parser->buffer[3] & SETP_FLAG_RESERVED_MASK) != 0U) { + ++parser->stats.flag_errors; + setp_drop(parser, 1U); + continue; + } + payload_length = setp_get_u16(&parser->buffer[12]); + if (payload_length > SETP_MAX_PAYLOAD) { + ++parser->stats.length_errors; + setp_drop(parser, 1U); + continue; + } + total = (uint16_t)(SETP_HEADER_SIZE + payload_length + SETP_CRC_SIZE); + if (parser->length < total) { + break; + } + expected_crc = setp_get_u32(&parser->buffer[SETP_HEADER_SIZE + payload_length]); + actual_crc = setp_crc32(&parser->buffer[2], + (size_t)(SETP_HEADER_SIZE - 2U) + payload_length); + if (expected_crc != actual_crc) { + ++parser->stats.crc_errors; + setp_drop(parser, 1U); + continue; + } + + frame.flags = parser->buffer[3]; + frame.message_type = setp_get_u16(&parser->buffer[4]); + frame.source = setp_get_u16(&parser->buffer[6]); + frame.destination = setp_get_u16(&parser->buffer[8]); + frame.sequence = setp_get_u16(&parser->buffer[10]); + frame.payload_length = payload_length; + frame.payload = &parser->buffer[SETP_HEADER_SIZE]; + if (callback != NULL) { + callback(&frame, user); + } + ++parser->stats.frames; + ++delivered; + setp_drop(parser, total); + } + return delivered; +} + +size_t setp_parser_feed(setp_parser_t *parser, const uint8_t *data, size_t length, + setp_frame_fn callback, void *user) +{ + size_t index; + size_t delivered = 0U; + + if ((parser == NULL) || ((data == NULL) && (length != 0U))) { + return 0U; + } + for (index = 0U; index < length; ++index) { + if (parser->length >= SETP_FRAME_MAX) { + ++parser->stats.overflows; + parser->length = 0U; + } + parser->buffer[parser->length++] = data[index]; + delivered += setp_parser_process(parser, callback, user); + } + return delivered; +} + +size_t setp_status_encode(uint16_t status, uint8_t *out, size_t capacity) +{ + if ((out == NULL) || (capacity < 2U)) { + return 0U; + } + setp_put_u16(out, status); + return 2U; +} + +bool setp_status_decode(const setp_frame_t *frame, uint16_t *status, + const uint8_t **body, uint16_t *body_length) +{ + if ((frame == NULL) || (status == NULL) + || ((frame->flags & SETP_FLAG_RESPONSE) == 0U) + || (frame->payload_length < 2U) || (frame->payload == NULL)) { + return false; + } + *status = setp_get_u16(frame->payload); + if (body != NULL) { + *body = &frame->payload[2]; + } + if (body_length != NULL) { + *body_length = (uint16_t)(frame->payload_length - 2U); + } + return true; +} diff --git a/c/set-protocol/src/set_telemetry.c b/c/set-protocol/src/set_telemetry.c new file mode 100644 index 0000000..980c81e --- /dev/null +++ b/c/set-protocol/src/set_telemetry.c @@ -0,0 +1,155 @@ +#include "set_telemetry.h" + +#include + +size_t setp_subscribe_encode(uint16_t subscription_id, uint32_t period_ms, + const uint32_t *addresses, uint16_t address_count, + uint8_t *out, size_t capacity) +{ + size_t total = (size_t)SETP_SUBSCRIBE_HEADER_SIZE + ((size_t)address_count * 4U); + uint16_t index; + if ((out == NULL) || ((address_count != 0U) && (addresses == NULL)) + || (total > SETP_MAX_PAYLOAD) || (capacity < total)) { + return 0U; + } + setp_put_u16(&out[0], subscription_id); + setp_put_u32(&out[2], period_ms); + setp_put_u16(&out[6], address_count); + for (index = 0U; index < address_count; ++index) { + setp_put_u32(&out[SETP_SUBSCRIBE_HEADER_SIZE + (index * 4U)], addresses[index]); + } + return total; +} + +bool setp_subscribe_decode_header(const uint8_t *payload, uint16_t length, + uint16_t *subscription_id, uint32_t *period_ms, + uint16_t *address_count) +{ + uint16_t count; + if ((payload == NULL) || (subscription_id == NULL) + || (period_ms == NULL) || (address_count == NULL) + || (length < SETP_SUBSCRIBE_HEADER_SIZE)) { + return false; + } + count = setp_get_u16(&payload[6]); + if (length != SETP_SUBSCRIBE_HEADER_SIZE + (count * 4U)) { + return false; + } + *subscription_id = setp_get_u16(&payload[0]); + *period_ms = setp_get_u32(&payload[2]); + *address_count = count; + return true; +} + +bool setp_subscribe_address(const uint8_t *payload, uint16_t length, + uint16_t index, uint32_t *address) +{ + uint16_t subscription_id; + uint32_t period_ms; + uint16_t count; + if ((address == NULL) + || !setp_subscribe_decode_header(payload, length, &subscription_id, + &period_ms, &count) + || (index >= count)) { + return false; + } + (void)subscription_id; + (void)period_ms; + *address = setp_get_u32(&payload[SETP_SUBSCRIBE_HEADER_SIZE + (index * 4U)]); + return true; +} + +bool setp_publish_begin(setp_publish_builder_t *builder, + uint16_t subscription_id, uint16_t sample_sequence, + uint32_t timestamp_ms, uint8_t *out, uint16_t capacity) +{ + if ((builder == NULL) || (out == NULL) || (capacity < SETP_PUBLISH_HEADER_SIZE)) { + return false; + } + builder->output = out; + builder->capacity = capacity; + builder->length = SETP_PUBLISH_HEADER_SIZE; + builder->item_count = 0U; + setp_put_u16(&out[0], subscription_id); + setp_put_u16(&out[2], sample_sequence); + setp_put_u32(&out[4], timestamp_ms); + setp_put_u16(&out[8], 0U); + return true; +} + +bool setp_publish_add(setp_publish_builder_t *builder, uint32_t address, + uint8_t encoding, uint8_t element_count, + const uint8_t *data, uint16_t data_length) +{ + uint16_t offset; + size_t next; + if ((builder == NULL) || (builder->output == NULL) + || (element_count == 0U) || (data_length == 0U) || (data == NULL) + || (encoding < SETP_VALUE_U16) || (encoding > SETP_VALUE_BYTES)) { + return false; + } + next = (size_t)builder->length + SETP_PUBLISH_ITEM_HEADER_SIZE + data_length; + if ((next > builder->capacity) || (next > SETP_MAX_PAYLOAD)) { + return false; + } + offset = builder->length; + setp_put_u32(&builder->output[offset], address); + builder->output[offset + 4U] = encoding; + builder->output[offset + 5U] = element_count; + setp_put_u16(&builder->output[offset + 6U], data_length); + (void)memcpy(&builder->output[offset + SETP_PUBLISH_ITEM_HEADER_SIZE], data, data_length); + builder->length = (uint16_t)next; + ++builder->item_count; + return true; +} + +size_t setp_publish_finish(setp_publish_builder_t *builder) +{ + if ((builder == NULL) || (builder->output == NULL)) { + return 0U; + } + setp_put_u16(&builder->output[8], builder->item_count); + return builder->length; +} + +bool setp_publish_view_init(setp_publish_view_t *view, + const uint8_t *payload, uint16_t length) +{ + if ((view == NULL) || (payload == NULL) || (length < SETP_PUBLISH_HEADER_SIZE)) { + return false; + } + view->payload = payload; + view->length = length; + view->offset = SETP_PUBLISH_HEADER_SIZE; + view->subscription_id = setp_get_u16(&payload[0]); + view->sample_sequence = setp_get_u16(&payload[2]); + view->timestamp_ms = setp_get_u32(&payload[4]); + view->remaining = setp_get_u16(&payload[8]); + return true; +} + +bool setp_publish_next(setp_publish_view_t *view, setp_publish_item_t *item) +{ + uint16_t data_length; + size_t end; + if ((view == NULL) || (item == NULL) || (view->remaining == 0U) + || (view->offset + SETP_PUBLISH_ITEM_HEADER_SIZE > view->length)) { + return false; + } + data_length = setp_get_u16(&view->payload[view->offset + 6U]); + end = (size_t)view->offset + SETP_PUBLISH_ITEM_HEADER_SIZE + data_length; + if ((data_length == 0U) || (end > view->length)) { + return false; + } + item->address = setp_get_u32(&view->payload[view->offset]); + item->encoding = view->payload[view->offset + 4U]; + item->element_count = view->payload[view->offset + 5U]; + item->data_length = data_length; + item->data = &view->payload[view->offset + SETP_PUBLISH_ITEM_HEADER_SIZE]; + view->offset = (uint16_t)end; + --view->remaining; + if ((view->remaining == 0U) && (view->offset != view->length)) { + return false; + } + return true; +} diff --git a/c/set-protocol/tests/test_set_protocol.c b/c/set-protocol/tests/test_set_protocol.c new file mode 100644 index 0000000..004e22a --- /dev/null +++ b/c/set-protocol/tests/test_set_protocol.c @@ -0,0 +1,302 @@ +#include "set_firmware.h" +#include "set_can.h" +#include "set_protocol.h" +#include "set_telemetry.h" + +#include +#include + +static int failures; +static int checks; + +static void check_impl(int condition, const char *text, int line) +{ + ++checks; + if (!condition) { + ++failures; + (void)printf("FAIL line %d: %s\n", line, text); + } +} + +#define CHECK(condition) check_impl((condition) ? 1 : 0, #condition, __LINE__) + +typedef struct { + setp_frame_t frame; + uint8_t payload[SETP_MAX_PAYLOAD]; + int count; +} capture_t; + +static void capture_frame(const setp_frame_t *frame, void *user) +{ + capture_t *capture = (capture_t *)user; + capture->frame = *frame; + if (frame->payload_length != 0U) { + (void)memcpy(capture->payload, frame->payload, frame->payload_length); + } + capture->frame.payload = capture->payload; + ++capture->count; +} + +static void test_crc_and_reference_frame(void) +{ + static const uint8_t expected[] = { + 0xA5U, 0x5AU, 0x02U, 0x08U, 0x01U, 0x00U, 0x00U, 0x00U, + 0x2AU, 0x00U, 0x34U, 0x12U, 0x00U, 0x00U, 0x33U, 0xECU, + 0x33U, 0x04U + }; + setp_frame_t frame; + uint8_t output[SETP_FRAME_MAX]; + size_t length; + setp_frame_t decoded; + + CHECK(setp_crc32((const uint8_t *)"123456789", 9U) == 0xCBF43926UL); + (void)memset(&frame, 0, sizeof(frame)); + frame.flags = SETP_FLAG_ACK_REQUIRED; + frame.message_type = SETP_MSG_PING; + frame.destination = 0x002AU; + frame.sequence = 0x1234U; + length = setp_frame_encode(&frame, output, sizeof(output)); + CHECK(length == sizeof(expected)); + CHECK(memcmp(output, expected, sizeof(expected)) == 0); + CHECK(setp_frame_decode_datagram(output, length, &decoded)); + CHECK(decoded.message_type == SETP_MSG_PING); + CHECK(decoded.destination == 0x002AU); + CHECK(!setp_frame_decode_datagram(output, length - 1U, &decoded)); + CHECK(setp_frame_encode(&frame, output, 4U) == 0U); +} + +static void test_parser(void) +{ + setp_frame_t frame; + uint8_t raw[SETP_FRAME_MAX]; + uint8_t payload[] = { 0x11U, 0x22U, 0x33U }; + size_t length; + setp_parser_t parser; + capture_t capture; + + (void)memset(&frame, 0, sizeof(frame)); + frame.message_type = 0x3456U; /* Unknown extensions are valid frames. */ + frame.source = 7U; + frame.destination = 9U; + frame.sequence = 11U; + frame.payload_length = sizeof(payload); + frame.payload = payload; + length = setp_frame_encode(&frame, raw, sizeof(raw)); + + (void)memset(&capture, 0, sizeof(capture)); + setp_parser_init(&parser); + CHECK(setp_parser_feed(&parser, (const uint8_t *)"noise", 5U, + capture_frame, &capture) == 0U); + CHECK(setp_parser_feed(&parser, raw, 3U, capture_frame, &capture) == 0U); + CHECK(setp_parser_feed(&parser, &raw[3], length - 3U, + capture_frame, &capture) == 1U); + CHECK(capture.count == 1); + CHECK(capture.frame.message_type == 0x3456U); + CHECK(capture.frame.source == 7U); + CHECK(capture.frame.destination == 9U); + CHECK(capture.frame.sequence == 11U); + CHECK(capture.frame.payload_length == sizeof(payload)); + CHECK(memcmp(capture.payload, payload, sizeof(payload)) == 0); + CHECK(parser.stats.stray_bytes == 5U); + + raw[length - 1U] ^= 1U; + CHECK(setp_parser_feed(&parser, raw, length, capture_frame, &capture) == 0U); + CHECK(parser.stats.crc_errors == 1U); +} + +static void test_status(void) +{ + uint8_t payload[4] = { 0U }; + setp_frame_t frame; + uint16_t status; + uint16_t body_length; + const uint8_t *body; + + CHECK(setp_status_encode(SETP_STATUS_BUSY, payload, sizeof(payload)) == 2U); + payload[2] = 0xAAU; + payload[3] = 0x55U; + (void)memset(&frame, 0, sizeof(frame)); + frame.flags = SETP_FLAG_RESPONSE | SETP_FLAG_ERROR; + frame.payload = payload; + frame.payload_length = sizeof(payload); + CHECK(setp_status_decode(&frame, &status, &body, &body_length)); + CHECK(status == SETP_STATUS_BUSY); + CHECK(body_length == 2U); + CHECK(body[0] == 0xAAU && body[1] == 0x55U); +} + +typedef struct { + setp_can_frame_t frames[96]; + uint16_t count; +} can_capture_t; + +static bool capture_can(const setp_can_frame_t *frame, void *user) +{ + can_capture_t *capture = (can_capture_t *)user; + if (capture->count >= 96U) { + return false; + } + capture->frames[capture->count++] = *frame; + return true; +} + +static void test_can_binding(void) +{ + setp_can_id_t id = { 7U, 9U, 1U, 3U }; + setp_can_id_t decoded_id; + uint32_t raw_id = setp_can_id_pack(&id); + setp_frame_t frame; + uint8_t payload[40]; + uint8_t packet[SETP_FRAME_MAX]; + size_t packet_length; + can_capture_t capture; + setp_can_rx_t rx; + setp_can_packet_t complete; + setp_can_rx_result_t result = SETP_CAN_RX_NONE; + uint16_t index; + + CHECK(setp_can_id_unpack(raw_id, &decoded_id)); + CHECK(decoded_id.source == 7U); + CHECK(decoded_id.destination == 9U); + CHECK(decoded_id.priority == 1U); + CHECK(decoded_id.channel == 3U); + for (index = 0U; index < sizeof(payload); ++index) { + payload[index] = (uint8_t)index; + } + (void)memset(&frame, 0, sizeof(frame)); + frame.flags = SETP_FLAG_PRIORITY; + frame.message_type = SETP_MSG_PUBLISH; + frame.source = 7U; + frame.destination = 9U; + frame.sequence = 3U; + frame.payload = payload; + frame.payload_length = sizeof(payload); + packet_length = setp_frame_encode(&frame, packet, sizeof(packet)); + (void)memset(&capture, 0, sizeof(capture)); + (void)memset(&complete, 0, sizeof(complete)); + CHECK(setp_can_segment(packet, (uint16_t)packet_length, raw_id, + capture_can, &capture)); + CHECK(capture.count > 1U); + CHECK(capture.frames[0].data[0] == SETP_CAN_PCI_FIRST); + setp_can_rx_init(&rx); + for (index = 0U; index < capture.count; ++index) { + result = setp_can_rx_feed(&rx, &capture.frames[index], index, &complete); + } + CHECK(result == SETP_CAN_RX_COMPLETE); + CHECK(complete.length == packet_length); + CHECK(complete.can_id == raw_id); + CHECK(memcmp(complete.data, packet, packet_length) == 0); +} + +static void test_firmware(void) +{ + setp_fw_begin_t begin; + setp_fw_begin_t decoded_begin; + setp_fw_data_t decoded_data; + uint8_t signature[64]; + uint8_t image[32]; + uint8_t manifest[SETP_FW_MANIFEST_SIZE]; + uint8_t payload[SETP_MAX_PAYLOAD]; + size_t length; + uint16_t index; + + (void)memset(&begin, 0, sizeof(begin)); + (void)memset(signature, 0xA5, sizeof(signature)); + for (index = 0U; index < sizeof(image); ++index) { + image[index] = (uint8_t)index; + } + begin.image_size = 4096U; + begin.image_crc32 = 0x12345678UL; + begin.image_version = 0x00010203UL; + begin.base_address = 0x08010000UL; + begin.slot = 1U; + begin.flags = SETP_FW_FLAG_RESUME | SETP_FW_FLAG_SIGNED; + begin.block_size = 480U; + (void)memset(begin.sha256, 0x5A, sizeof(begin.sha256)); + begin.signing_key_id = 3U; + begin.signature_length = sizeof(signature); + begin.signature = signature; + length = setp_fw_begin_encode(&begin, payload, sizeof(payload)); + CHECK(length == SETP_FW_BEGIN_FIXED_SIZE + sizeof(signature)); + CHECK(setp_fw_begin_decode(payload, (uint16_t)length, &decoded_begin)); + CHECK(decoded_begin.image_size == begin.image_size); + CHECK(decoded_begin.block_size == 480U); + CHECK(decoded_begin.signing_key_id == 3U); + CHECK(decoded_begin.signature_length == sizeof(signature)); + CHECK(memcmp(decoded_begin.signature, signature, sizeof(signature)) == 0); + CHECK(setp_fw_manifest_encode(&decoded_begin, manifest, sizeof(manifest)) + == SETP_FW_MANIFEST_SIZE); + CHECK(memcmp(manifest, "SETPFW2", 7U) == 0); + CHECK(setp_get_u32(&manifest[8]) == begin.image_size); + + length = setp_fw_data_encode(1024U, 0U, image, sizeof(image), + payload, sizeof(payload)); + CHECK(length == SETP_FW_DATA_HEADER_SIZE + sizeof(image)); + CHECK(setp_fw_data_decode(payload, (uint16_t)length, &decoded_data)); + CHECK(decoded_data.offset == 1024U); + CHECK(decoded_data.data_length == sizeof(image)); + CHECK(memcmp(decoded_data.data, image, sizeof(image)) == 0); + payload[length - 1U] ^= 1U; + CHECK(!setp_fw_data_decode(payload, (uint16_t)length, &decoded_data)); +} + +static void test_telemetry(void) +{ + uint32_t addresses[] = { 0x10U, 0x12345678UL }; + uint8_t payload[SETP_MAX_PAYLOAD]; + uint16_t subscription_id; + uint16_t count; + uint32_t period; + uint32_t address; + size_t length; + setp_publish_builder_t builder; + setp_publish_view_t view; + setp_publish_item_t item; + uint8_t value1[] = { 0x34U, 0x12U }; + uint8_t value2[] = { 1U, 2U, 3U, 4U }; + + length = setp_subscribe_encode(7U, 50U, addresses, 2U, payload, sizeof(payload)); + CHECK(length == 16U); + CHECK(setp_subscribe_decode_header(payload, (uint16_t)length, + &subscription_id, &period, &count)); + CHECK(subscription_id == 7U); + CHECK(period == 50U); + CHECK(count == 2U); + CHECK(setp_subscribe_address(payload, (uint16_t)length, 1U, &address)); + CHECK(address == 0x12345678UL); + + CHECK(setp_publish_begin(&builder, 7U, 9U, 123456U, + payload, sizeof(payload))); + CHECK(setp_publish_add(&builder, 0x10U, SETP_VALUE_U16, 1U, + value1, sizeof(value1))); + CHECK(setp_publish_add(&builder, 0x20U, SETP_VALUE_BYTES, 4U, + value2, sizeof(value2))); + length = setp_publish_finish(&builder); + CHECK(length == SETP_PUBLISH_HEADER_SIZE + + (2U * SETP_PUBLISH_ITEM_HEADER_SIZE) + sizeof(value1) + sizeof(value2)); + CHECK(setp_publish_view_init(&view, payload, (uint16_t)length)); + CHECK(view.subscription_id == 7U); + CHECK(view.sample_sequence == 9U); + CHECK(view.timestamp_ms == 123456U); + CHECK(setp_publish_next(&view, &item)); + CHECK(item.address == 0x10U); + CHECK(item.encoding == SETP_VALUE_U16); + CHECK(item.data_length == sizeof(value1)); + CHECK(setp_publish_next(&view, &item)); + CHECK(item.address == 0x20U); + CHECK(item.element_count == 4U); + CHECK(view.remaining == 0U); + CHECK(!setp_publish_next(&view, &item)); +} + +int main(void) +{ + test_crc_and_reference_frame(); + test_parser(); + test_status(); + test_can_binding(); + test_firmware(); + test_telemetry(); + (void)printf("checks: %d, failures: %d\n", checks, failures); + return failures == 0 ? 0 : 1; +}