From 9b636a9f40d24d79dafe0dcc6457e774fd3b5d51 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 1 Sep 2026 11:47:08 +0300 Subject: [PATCH] feat(setprotocol): define system payload schemas --- c/set-protocol/PROTOCOL.md | 58 ++++++++++++- c/set-protocol/include/set_protocol.h | 54 ++++++++++++ c/set-protocol/src/set_protocol.c | 105 +++++++++++++++++++++++ c/set-protocol/tests/test_set_protocol.c | 61 +++++++++++++ doc/setprotocol.html | 4 + 5 files changed, 279 insertions(+), 3 deletions(-) diff --git a/c/set-protocol/PROTOCOL.md b/c/set-protocol/PROTOCOL.md index 7fe4bfd..858f70b 100644 --- a/c/set-protocol/PROTOCOL.md +++ b/c/set-protocol/PROTOCOL.md @@ -93,7 +93,59 @@ broadcast; на broadcast-запрос отвечать нельзя, если Неизвестный тип не является ошибкой кадрирования. Устройство отвечает `UNSUPPORTED`, если запрос был адресован ему и требовал ответа. -## 5. Телеметрия реального времени +## 5. Системные payload schema v1 + +Все описанные ниже поля входят в body ответа **после** обязательного +`status u16`. Строки передаются в UTF-8 без завершающего нуля. Версия схемы +отделена от версии кадра, чтобы поля можно было расширять независимо. + +### DEVICE_INFO + +```text +schema_version u16 = 1 +device_class u16 +hardware_version u32 +firmware_version u32 +dictionary_version u32 +serial_number u64 +model_length u8 (0..63) +model u8[model_length], UTF-8 +``` + +Числовые версии являются значениями изделия; рекомендуемая упаковка +`major.minor.patch.build` — по одному байту от старшего к младшему. +`device_class` назначается в реестре изделий SET, а `dictionary_version` +меняется при несовместимом изменении адресов или типов общей карты. + +### CAPABILITIES + +```text +schema_version u16 = 1 +max_payload u16 (1..512 для базового профиля) +interface_mask u32 (бит с номером setp_iface_t) +feature_flags u32 +max_read_items u16 +max_write_items u16 +max_subscriptions u16 +max_publish_items u16 +``` + +Стабильные `feature_flags`: `READ=bit0`, `WRITE=bit1`, `CATALOG=bit2`, +`SUBSCRIBE=bit3`, `LOG_READ=bit4`, `FIRMWARE=bit5`, `DIAGNOSTICS=bit6`. +Нулевой лимит означает, что соответствующая функция не поддерживается. + +Эталонные body без status: + +```text +DEVICE_INFO (SET-01): +01 00 34 12 02 00 01 00 04 00 03 00 44 33 22 11 +EF CD AB 89 67 45 23 01 06 53 45 54 2D 30 31 + +CAPABILITIES (RS232 + CAN, READ + CATALOG + SUBSCRIBE): +01 00 00 02 0A 00 00 00 0D 00 00 00 40 00 20 00 04 00 18 00 +``` + +## 6. Телеметрия реального времени ### SUBSCRIBE @@ -131,7 +183,7 @@ repeat item_count times: Телеметрия имеет меньший приоритет, чем ответы и прошивка. При переполнении очереди разрешено отбросить старый `PUBLISH`, но нельзя частично передать кадр. -## 6. Прошивка +## 7. Прошивка Типы: @@ -175,7 +227,7 @@ offset u32 | data_length u16 | flags u16 | data_crc32 u32 | data[] CRC32 защищает линию, SHA-256 — целостность образа, подпись — происхождение. Один CRC не является защитой от подмены прошивки. -## 7. Привязки к физическим интерфейсам +## 8. Привязки к физическим интерфейсам ### RS-232, RS-485, USB CDC diff --git a/c/set-protocol/include/set_protocol.h b/c/set-protocol/include/set_protocol.h index b3e35ee..34e7992 100644 --- a/c/set-protocol/include/set_protocol.h +++ b/c/set-protocol/include/set_protocol.h @@ -23,6 +23,12 @@ extern "C" { #define SETP_NODE_LOCAL 0x0000U #define SETP_NODE_BROADCAST 0xFFFFU +#define SETP_DEVICE_INFO_SCHEMA_VERSION 1U +#define SETP_DEVICE_INFO_FIXED_SIZE 25U +#define SETP_DEVICE_MODEL_MAX 63U +#define SETP_CAPABILITIES_SCHEMA_VERSION 1U +#define SETP_CAPABILITIES_SIZE 20U + /* Frame flags. Reserved bits must be transmitted as zero. */ #define SETP_FLAG_RESPONSE 0x01U #define SETP_FLAG_EVENT 0x02U @@ -86,6 +92,40 @@ typedef enum { SETP_STATUS_WRONG_STATE = 15 } setp_status_t; +/* CAPABILITIES feature_flags. */ +#define SETP_FEATURE_READ (1UL << 0) +#define SETP_FEATURE_WRITE (1UL << 1) +#define SETP_FEATURE_CATALOG (1UL << 2) +#define SETP_FEATURE_SUBSCRIBE (1UL << 3) +#define SETP_FEATURE_LOG_READ (1UL << 4) +#define SETP_FEATURE_FIRMWARE (1UL << 5) +#define SETP_FEATURE_DIAGNOSTICS (1UL << 6) + +/* CAPABILITIES interface_mask; bit index equals setp_iface_t. */ +#define SETP_IFACE_MASK(iface) (1UL << (uint32_t)(iface)) + +typedef struct { + uint16_t schema_version; + uint16_t device_class; + uint32_t hardware_version; + uint32_t firmware_version; + uint32_t dictionary_version; + uint64_t serial_number; + uint8_t model_length; + const uint8_t *model; +} setp_device_info_t; + +typedef struct { + uint16_t schema_version; + uint16_t max_payload; + uint32_t interface_mask; + uint32_t feature_flags; + uint16_t max_read_items; + uint16_t max_write_items; + uint16_t max_subscriptions; + uint16_t max_publish_items; +} setp_capabilities_t; + typedef struct { uint8_t flags; uint16_t message_type; @@ -116,8 +156,10 @@ 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); +uint64_t setp_get_u64(const uint8_t *data); void setp_put_u16(uint8_t *data, uint16_t value); void setp_put_u32(uint8_t *data, uint32_t value); +void setp_put_u64(uint8_t *data, uint64_t value); uint32_t setp_crc32(const uint8_t *data, size_t length); @@ -141,6 +183,18 @@ size_t setp_status_encode(uint16_t status, uint8_t *out, size_t capacity); bool setp_status_decode(const setp_frame_t *frame, uint16_t *status, const uint8_t **body, uint16_t *body_length); +/** Encode/decode DEVICE_INFO response body (without the status prefix). */ +size_t setp_device_info_encode(const setp_device_info_t *info, + uint8_t *out, size_t capacity); +bool setp_device_info_decode(const uint8_t *data, uint16_t length, + setp_device_info_t *out); + +/** Encode/decode CAPABILITIES response body (without the status prefix). */ +size_t setp_capabilities_encode(const setp_capabilities_t *capabilities, + uint8_t *out, size_t capacity); +bool setp_capabilities_decode(const uint8_t *data, uint16_t length, + setp_capabilities_t *out); + #ifdef __cplusplus } #endif diff --git a/c/set-protocol/src/set_protocol.c b/c/set-protocol/src/set_protocol.c index d10d206..68989fd 100644 --- a/c/set-protocol/src/set_protocol.c +++ b/c/set-protocol/src/set_protocol.c @@ -15,6 +15,12 @@ uint32_t setp_get_u32(const uint8_t *data) | ((uint32_t)data[3] << 24); } +uint64_t setp_get_u64(const uint8_t *data) +{ + return (uint64_t)setp_get_u32(data) + | ((uint64_t)setp_get_u32(&data[4]) << 32); +} + void setp_put_u16(uint8_t *data, uint16_t value) { data[0] = (uint8_t)(value & 0xFFU); @@ -29,6 +35,12 @@ void setp_put_u32(uint8_t *data, uint32_t value) data[3] = (uint8_t)(value >> 24); } +void setp_put_u64(uint8_t *data, uint64_t value) +{ + setp_put_u32(data, (uint32_t)(value & 0xFFFFFFFFULL)); + setp_put_u32(&data[4], (uint32_t)(value >> 32)); +} + uint32_t setp_crc32(const uint8_t *data, size_t length) { uint32_t crc = 0xFFFFFFFFUL; @@ -278,3 +290,96 @@ bool setp_status_decode(const setp_frame_t *frame, uint16_t *status, } return true; } + +size_t setp_device_info_encode(const setp_device_info_t *info, + uint8_t *out, size_t capacity) +{ + size_t total; + if ((info == NULL) || (out == NULL) + || (info->schema_version != SETP_DEVICE_INFO_SCHEMA_VERSION) + || (info->model_length > SETP_DEVICE_MODEL_MAX) + || ((info->model_length != 0U) && (info->model == NULL))) { + return 0U; + } + total = (size_t)SETP_DEVICE_INFO_FIXED_SIZE + info->model_length; + if (capacity < total) { + return 0U; + } + setp_put_u16(&out[0], info->schema_version); + setp_put_u16(&out[2], info->device_class); + setp_put_u32(&out[4], info->hardware_version); + setp_put_u32(&out[8], info->firmware_version); + setp_put_u32(&out[12], info->dictionary_version); + setp_put_u64(&out[16], info->serial_number); + out[24] = info->model_length; + if (info->model_length != 0U) { + (void)memcpy(&out[SETP_DEVICE_INFO_FIXED_SIZE], + info->model, info->model_length); + } + return total; +} + +bool setp_device_info_decode(const uint8_t *data, uint16_t length, + setp_device_info_t *out) +{ + uint8_t model_length; + if ((data == NULL) || (out == NULL) || (length < SETP_DEVICE_INFO_FIXED_SIZE)) { + return false; + } + model_length = data[24]; + if ((setp_get_u16(&data[0]) != SETP_DEVICE_INFO_SCHEMA_VERSION) + || (model_length > SETP_DEVICE_MODEL_MAX) + || (length != (uint16_t)(SETP_DEVICE_INFO_FIXED_SIZE + model_length))) { + return false; + } + out->schema_version = setp_get_u16(&data[0]); + out->device_class = setp_get_u16(&data[2]); + out->hardware_version = setp_get_u32(&data[4]); + out->firmware_version = setp_get_u32(&data[8]); + out->dictionary_version = setp_get_u32(&data[12]); + out->serial_number = setp_get_u64(&data[16]); + out->model_length = model_length; + out->model = &data[SETP_DEVICE_INFO_FIXED_SIZE]; + return true; +} + +size_t setp_capabilities_encode(const setp_capabilities_t *capabilities, + uint8_t *out, size_t capacity) +{ + if ((capabilities == NULL) || (out == NULL) + || (capacity < SETP_CAPABILITIES_SIZE) + || (capabilities->schema_version != SETP_CAPABILITIES_SCHEMA_VERSION) + || (capabilities->max_payload == 0U) + || (capabilities->max_payload > SETP_MAX_PAYLOAD)) { + return 0U; + } + setp_put_u16(&out[0], capabilities->schema_version); + setp_put_u16(&out[2], capabilities->max_payload); + setp_put_u32(&out[4], capabilities->interface_mask); + setp_put_u32(&out[8], capabilities->feature_flags); + setp_put_u16(&out[12], capabilities->max_read_items); + setp_put_u16(&out[14], capabilities->max_write_items); + setp_put_u16(&out[16], capabilities->max_subscriptions); + setp_put_u16(&out[18], capabilities->max_publish_items); + return SETP_CAPABILITIES_SIZE; +} + +bool setp_capabilities_decode(const uint8_t *data, uint16_t length, + setp_capabilities_t *out) +{ + if ((data == NULL) || (out == NULL) || (length != SETP_CAPABILITIES_SIZE) + || (setp_get_u16(&data[0]) != SETP_CAPABILITIES_SCHEMA_VERSION) + || (setp_get_u16(&data[2]) == 0U) + || (setp_get_u16(&data[2]) > SETP_MAX_PAYLOAD)) { + return false; + } + out->schema_version = setp_get_u16(&data[0]); + out->max_payload = setp_get_u16(&data[2]); + out->interface_mask = setp_get_u32(&data[4]); + out->feature_flags = setp_get_u32(&data[8]); + out->max_read_items = setp_get_u16(&data[12]); + out->max_write_items = setp_get_u16(&data[14]); + out->max_subscriptions = setp_get_u16(&data[16]); + out->max_publish_items = setp_get_u16(&data[18]); + return true; +} diff --git a/c/set-protocol/tests/test_set_protocol.c b/c/set-protocol/tests/test_set_protocol.c index 004e22a..aaf1416 100644 --- a/c/set-protocol/tests/test_set_protocol.c +++ b/c/set-protocol/tests/test_set_protocol.c @@ -125,6 +125,66 @@ static void test_status(void) CHECK(body[0] == 0xAAU && body[1] == 0x55U); } +static void test_system_payloads(void) +{ + static const uint8_t expected_info[] = { + 0x01U, 0x00U, 0x34U, 0x12U, 0x02U, 0x00U, 0x01U, 0x00U, + 0x04U, 0x00U, 0x03U, 0x00U, 0x44U, 0x33U, 0x22U, 0x11U, + 0xEFU, 0xCDU, 0xABU, 0x89U, 0x67U, 0x45U, 0x23U, 0x01U, + 0x06U, 0x53U, 0x45U, 0x54U, 0x2DU, 0x30U, 0x31U + }; + static const uint8_t expected_capabilities[] = { + 0x01U, 0x00U, 0x00U, 0x02U, 0x0AU, 0x00U, 0x00U, 0x00U, + 0x0DU, 0x00U, 0x00U, 0x00U, 0x40U, 0x00U, 0x20U, 0x00U, + 0x04U, 0x00U, 0x18U, 0x00U + }; + static const uint8_t model[] = "SET-01"; + setp_device_info_t info; + setp_device_info_t decoded_info; + setp_capabilities_t capabilities; + setp_capabilities_t decoded_capabilities; + uint8_t payload[SETP_MAX_PAYLOAD]; + size_t length; + + (void)memset(&info, 0, sizeof(info)); + info.schema_version = SETP_DEVICE_INFO_SCHEMA_VERSION; + info.device_class = 0x1234U; + info.hardware_version = 0x00010002UL; + info.firmware_version = 0x00030004UL; + info.dictionary_version = 0x11223344UL; + info.serial_number = 0x0123456789ABCDEFULL; + info.model_length = 6U; + info.model = model; + length = setp_device_info_encode(&info, payload, sizeof(payload)); + CHECK(length == sizeof(expected_info)); + CHECK(memcmp(payload, expected_info, sizeof(expected_info)) == 0); + CHECK(setp_device_info_decode(payload, (uint16_t)length, &decoded_info)); + CHECK(decoded_info.serial_number == info.serial_number); + CHECK(decoded_info.model_length == info.model_length); + CHECK(memcmp(decoded_info.model, model, sizeof(model) - 1U) == 0); + payload[24] = SETP_DEVICE_MODEL_MAX + 1U; + CHECK(!setp_device_info_decode(payload, (uint16_t)length, &decoded_info)); + + (void)memset(&capabilities, 0, sizeof(capabilities)); + capabilities.schema_version = SETP_CAPABILITIES_SCHEMA_VERSION; + capabilities.max_payload = SETP_MAX_PAYLOAD; + capabilities.interface_mask = SETP_IFACE_MASK(SETP_IFACE_RS232) + | SETP_IFACE_MASK(SETP_IFACE_CAN); + capabilities.feature_flags = SETP_FEATURE_READ | SETP_FEATURE_CATALOG + | SETP_FEATURE_SUBSCRIBE; + capabilities.max_read_items = 64U; + capabilities.max_write_items = 32U; + capabilities.max_subscriptions = 4U; + capabilities.max_publish_items = 24U; + length = setp_capabilities_encode(&capabilities, payload, sizeof(payload)); + CHECK(length == sizeof(expected_capabilities)); + CHECK(memcmp(payload, expected_capabilities, sizeof(expected_capabilities)) == 0); + CHECK(setp_capabilities_decode(payload, (uint16_t)length, + &decoded_capabilities)); + CHECK(decoded_capabilities.interface_mask == capabilities.interface_mask); + CHECK(decoded_capabilities.feature_flags == capabilities.feature_flags); +} + typedef struct { setp_can_frame_t frames[96]; uint16_t count; @@ -294,6 +354,7 @@ int main(void) test_crc_and_reference_frame(); test_parser(); test_status(); + test_system_payloads(); test_can_binding(); test_firmware(); test_telemetry(); diff --git a/doc/setprotocol.html b/doc/setprotocol.html index 5b3206f..258e50f 100644 --- a/doc/setprotocol.html +++ b/doc/setprotocol.html @@ -62,6 +62,10 @@