feat: add unified SET protocol v2

This commit is contained in:
2026-08-24 19:19:32 +03:00
parent 033c7ab9e8
commit 085eb3c8bd
15 changed files with 1953 additions and 0 deletions

238
c/set-protocol/PROTOCOL.md Normal file
View File

@@ -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 в одном потоке.